Files
PersonalBlog/tests/typescript-pin.test.mjs
implementer bcea489391 feat: add TypeBox/Ajv config schema package to the workspace (E00-S04-T01)
Adds packages/config (@personal-blog/config) — the EPPP configuration
service foundation. The package defines the configuration schema with
TypeBox (configSchema: host, port, databaseUrl, sessionSecret — the
validated config fields, golden-tuple pins @sinclair/typebox@0.34.52 and
ajv@8.20.0) and compiles it with Ajv (validateConfig). The environment
adapter (T04), field-specific startup errors (T02) and secret redaction
(T03) build on this boundary in later tasks; nothing reads process.env yet.

Wiring for the new workspace package: lockfile importer + resolved
typebox/ajv tree, apps/server/Dockerfile manifest copy (frozen in-image
install must match the lockfile importers), config-schema CI job, package
set fixtures (workspace-layout, workspace-config, strict-tsconfig,
typescript-pin), probe-file gitignore entry.
2026-08-30 02:29:52 +00:00

109 lines
4.4 KiB
JavaScript

/**
* TypeScript exact-pin test — locks in the [E00-S01-T09] TypeScript 6.0.3
* exact dependency for the workspace.
*
* Acceptance criteria covered (each test fails without the committed config):
* - "TypeScript 6.0.3 is pinned as an exact dependency" → the root
* `package.json` declares `devDependencies.typescript` as exactly
* `6.0.3` (a bare MAJOR.MINOR.PATCH — no `^`/`~`/`>=` range), and
* `pnpm-lock.yaml` resolves the root importer with specifier `6.0.3` /
* version `6.0.3` and contains exactly one resolved TypeScript entry
* (`typescript@6.0.3`) in its packages section.
* - "workspace packages resolve the exact TypeScript version" → running
* `pnpm exec tsc --version` inside every workspace package — the same
* binary the package `build`/`typecheck` scripts invoke — reports
* `Version 6.0.3`.
*
* Run: `node --test tests/typescript-pin.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 } from 'node:fs';
import { spawnSync } from 'node:child_process';
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 exact TypeScript version this workspace is pinned to ([E00-S01-T09]). */
const PINNED_TYPESCRIPT = '6.0.3';
/** Every workspace package that must resolve the pinned TypeScript version. */
const WORKSPACE_PACKAGES = ['apps/server', 'packages/core', 'packages/config', 'packages/database-postgres', 'extensions/example'];
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
test('root package.json pins typescript as an exact dependency (6.0.3)', () => {
const manifest = JSON.parse(read('package.json'));
const spec = manifest.devDependencies?.typescript;
assert.equal(
spec,
PINNED_TYPESCRIPT,
`root package.json must pin devDependencies.typescript exactly as "${PINNED_TYPESCRIPT}" (got "${spec}")`,
);
// The pin must be exact — a bare MAJOR.MINOR.PATCH, never a semver range
// (^, ~, >=, <, *, x-ranges) that could resolve to a different version.
assert.match(
spec,
/^\d+\.\d+\.\d+$/,
`"${spec}" is not an exact MAJOR.MINOR.PATCH version — pin typescript exactly, without a semver range`,
);
});
test('pnpm-lock.yaml resolves the root typescript pin to exactly 6.0.3', () => {
const lockfile = read('pnpm-lock.yaml');
// Root importer: devDependencies.typescript must be specifier 6.0.3 with
// resolved version 6.0.3 (a range specifier like "^6.0.3" fails this).
const importersSection = lockfile.slice(
lockfile.indexOf('importers:'),
lockfile.indexOf('packages:'),
);
assert.match(
importersSection,
/devDependencies:\s*\n\s+typescript:\s*\n\s+specifier: 6\.0\.3\s*\n\s+version: 6\.0\.3/,
'pnpm-lock.yaml must resolve the root typescript devDependency to specifier "6.0.3" / version "6.0.3"',
);
// The packages section must contain exactly one resolved TypeScript entry,
// so no package can drift onto a different TypeScript version.
const packagesSection = lockfile.slice(
lockfile.indexOf('packages:'),
lockfile.indexOf('snapshots:'),
);
const resolvedTypescriptEntries = [...packagesSection.matchAll(/^\s+(typescript@\S+):/gm)].map(
(m) => m[1],
);
assert.deepEqual(
resolvedTypescriptEntries,
[`typescript@${PINNED_TYPESCRIPT}`],
`the lockfile must contain exactly one resolved TypeScript version (typescript@${PINNED_TYPESCRIPT}); got: ${resolvedTypescriptEntries.join(', ')}`,
);
});
test('every workspace package resolves the exact TypeScript version (6.0.3)', () => {
for (const pkg of WORKSPACE_PACKAGES) {
const result = spawnSync('corepack', ['pnpm', 'exec', 'tsc', '--version'], {
cwd: path.join(REPO_ROOT, pkg),
encoding: 'utf8',
timeout: 120_000,
});
assert.equal(
result.status,
0,
`"corepack pnpm exec tsc --version" failed in ${pkg}: ${(result.stderr || result.stdout || '').trim()}`,
);
assert.equal(
result.stdout.trim(),
`Version ${PINNED_TYPESCRIPT}`,
`${pkg} must resolve TypeScript ${PINNED_TYPESCRIPT} (got "${result.stdout.trim()}")`,
);
}
});