CI / Frozen lockfile install (pull_request) Successful in 43s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 30s
CI / Database-postgres import isolation (E00-S03-T02) (pull_request) Successful in 31s
CI / Compose config (E00-S03-T01) (pull_request) Successful in 24s
- database-postgres-imports.test.mjs: static scan of every workspace package source proves pg/kysely import specifiers resolve only to packages/database-postgres; owner manifest pins the driver and no other package declares it; mutation probes prove the scan catches a driver import injected into apps/server/src/index.ts; comment-stripping and specifier matcher unit probes; CI-enforcement assertion - workspace-layout / workspace-config / strict-tsconfig / typescript-pin: package-set fixtures updated to include packages/database-postgres - docs/development/non-container.md: workspace package table and build expectations updated for the new package
192 lines
7.7 KiB
JavaScript
192 lines
7.7 KiB
JavaScript
/**
|
|
* Workspace layout test — locks in the [E00-S01-T11] apps/packages/extensions
|
|
* separation for the workspace.
|
|
*
|
|
* Acceptance criteria covered (each test fails without the committed layout):
|
|
* - "apps, packages and extensions are separated in the workspace layout" →
|
|
* the repo root contains exactly the three top-level package group
|
|
* directories apps/, packages/, extensions/; every workspace package.json
|
|
* lives exactly one level below one of them (never at the root, never in
|
|
* another directory, never nested deeper); `pnpm-workspace.yaml` declares
|
|
* one glob per group and `dependency-boundaries.json` maps the same three
|
|
* groups to the same three directories.
|
|
* - "each package sits in the correct top-level directory" → the bootstrap
|
|
* packages are found where their group says: `@personal-blog/server` in
|
|
* apps/, `@personal-blog/core` in packages/, `@personal-blog/example-
|
|
* extension` in extensions/; the discovered workspace package set is
|
|
* exactly the expected one, and every discovered package has a lockfile
|
|
* importer (layout ↔ lockfile parity).
|
|
*
|
|
* Run: `node --test tests/workspace-layout.test.mjs`
|
|
* (node:test — built into Node >= 18; no dependencies, lockfile untouched.)
|
|
*/
|
|
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
const read = (relPath) => readFileSync(path.join(REPO_ROOT, relPath), 'utf8');
|
|
|
|
/** The three top-level package group directories of the separated layout. */
|
|
const TOP_LEVEL_GROUPS = ['apps', 'packages', 'extensions'];
|
|
|
|
/** Expected bootstrap packages: package name -> top-level directory it must sit in. */
|
|
const EXPECTED_PACKAGES = {
|
|
'@personal-blog/server': 'apps/server',
|
|
'@personal-blog/core': 'packages/core',
|
|
'@personal-blog/database-postgres': 'packages/database-postgres',
|
|
'@personal-blog/example-extension': 'extensions/example',
|
|
};
|
|
|
|
/** Directories never scanned as package sources. */
|
|
const IGNORED_DIRS = new Set(['node_modules', 'dist', 'coverage', '.git', '.pnpm-store']);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Layout helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function isDirectory(p) {
|
|
try {
|
|
return statSync(p).isDirectory();
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively collects every package.json path under a directory, as a
|
|
* repo-root-relative posix path (the root workspace manifest itself is
|
|
* included as "package.json").
|
|
*/
|
|
function collectPackageJsonPaths(dir) {
|
|
const results = [];
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
if (entry.isDirectory()) {
|
|
if (!IGNORED_DIRS.has(entry.name)) {
|
|
results.push(...collectPackageJsonPaths(path.join(dir, entry.name)));
|
|
}
|
|
} else if (entry.name === 'package.json') {
|
|
results.push(path.relative(REPO_ROOT, path.join(dir, entry.name)).split(path.sep).join('/'));
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Validates the placement of a repo-root-relative package.json path. A
|
|
* workspace package must be exactly `<group>/<name>/package.json` under one of
|
|
* the three top-level groups; the root workspace manifest ("package.json") is
|
|
* the one allowed exception. Returns an error message, or null when the
|
|
* placement is correct.
|
|
*/
|
|
function placementError(relPath) {
|
|
if (relPath === 'package.json') return null; // workspace root manifest, not a package
|
|
const parts = relPath.split('/');
|
|
if (parts.length !== 3 || parts[2] !== 'package.json') {
|
|
return `"${relPath}" must be <group>/<name>/package.json (exactly one level below a top-level group directory)`;
|
|
}
|
|
if (!TOP_LEVEL_GROUPS.includes(parts[0])) {
|
|
return `"${relPath}" must sit under apps/, packages/ or extensions/ (got "${parts[0]}")`;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test('the workspace root contains exactly the three separated top-level group directories', () => {
|
|
for (const group of TOP_LEVEL_GROUPS) {
|
|
assert.ok(
|
|
isDirectory(path.join(REPO_ROOT, group)),
|
|
`top-level directory "${group}/" must exist (apps/packages/extensions separated)`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('every workspace package.json sits exactly one level below its top-level group directory', () => {
|
|
const manifests = collectPackageJsonPaths(REPO_ROOT);
|
|
const packageManifests = manifests.filter((rel) => rel !== 'package.json');
|
|
assert.ok(packageManifests.length > 0, 'the workspace must contain at least one package manifest');
|
|
for (const rel of packageManifests) {
|
|
const error = placementError(rel);
|
|
assert.equal(error, null, error);
|
|
}
|
|
});
|
|
|
|
test('each expected package sits in the correct top-level directory', () => {
|
|
for (const [name, dir] of Object.entries(EXPECTED_PACKAGES)) {
|
|
const manifest = JSON.parse(read(path.join(dir, 'package.json')));
|
|
assert.equal(
|
|
manifest.name,
|
|
name,
|
|
`"${dir}/package.json" must declare name "${name}" (each package sits in its correct top-level directory)`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('the discovered workspace package set is exactly the expected bootstrap set', () => {
|
|
const manifests = collectPackageJsonPaths(REPO_ROOT).filter((rel) => rel !== 'package.json');
|
|
const discoveredDirs = new Set(manifests.map((rel) => rel.replace(/\/package\.json$/, '')));
|
|
assert.deepEqual(discoveredDirs, new Set(Object.values(EXPECTED_PACKAGES)));
|
|
});
|
|
|
|
test('pnpm-workspace.yaml declares one group glob per top-level group directory', () => {
|
|
const workspace = read('pnpm-workspace.yaml');
|
|
const packagesSection = workspace.slice(workspace.indexOf('packages:'));
|
|
const globs = [...packagesSection.matchAll(/^\s*-\s*'([^']+)'\s*$/gm)].map((m) => m[1]);
|
|
assert.deepEqual(
|
|
globs,
|
|
TOP_LEVEL_GROUPS.map((g) => `${g}/*`),
|
|
'pnpm-workspace.yaml must declare exactly apps/*, packages/* and extensions/*',
|
|
);
|
|
});
|
|
|
|
test('dependency-boundaries.json maps the three groups to the same top-level directories', () => {
|
|
const rule = JSON.parse(read('dependency-boundaries.json'));
|
|
for (const group of TOP_LEVEL_GROUPS) {
|
|
assert.equal(
|
|
rule.groups?.[group]?.path,
|
|
`${group}/`,
|
|
`dependency-boundaries.json must map group "${group}" to path "${group}/"`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('every discovered workspace package has a lockfile importer (layout ↔ lockfile parity)', () => {
|
|
const lockfile = read('pnpm-lock.yaml');
|
|
const manifests = collectPackageJsonPaths(REPO_ROOT).filter((rel) => rel !== 'package.json');
|
|
for (const rel of manifests) {
|
|
const importer = rel.replace(/\/package\.json$/, '');
|
|
assert.ok(
|
|
lockfile.includes(` ${importer}:`),
|
|
`pnpm-lock.yaml must contain an importer for "${importer}" (every package in the layout must be a workspace member)`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('the placement validation flags mis-placed packages (non-vacuous probe)', () => {
|
|
const valid = [
|
|
'apps/server/package.json',
|
|
'packages/core/package.json',
|
|
'extensions/example/package.json',
|
|
'package.json', // workspace root manifest
|
|
];
|
|
for (const rel of valid) {
|
|
assert.equal(placementError(rel), null, `"${rel}" must be a valid placement`);
|
|
}
|
|
const invalid = [
|
|
'lib/shared/package.json', // no top-level group at all
|
|
'apps/package.json', // manifest directly in a group dir (no package name level)
|
|
'packages/a/b/package.json', // nested deeper than one level
|
|
'docs/package.json', // outside the three groups
|
|
];
|
|
for (const rel of invalid) {
|
|
assert.notEqual(placementError(rel), null, `"${rel}" must be flagged as mis-placed`);
|
|
}
|
|
});
|