import assert from "node:assert/strict"; import * as fs from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import test from "node:test"; const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); const sourceImportPattern = /from\s+["'](@earendil-works\/[^"']+)["']|import\s+["'](@earendil-works\/[^"']+)["']/g; const oldPiScopePattern = /@mariozechner\/pi-/; const piPackageJsonSubpathPattern = /@earendil-works\/pi-[^"']+\/package\.json/; const cjsPiPackageResolutionPattern = /require(?:\.resolve)?\(\s*["']@earendil-works\/pi-/; const exactVersionPattern = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/; function collectTsFiles(dir: string): string[] { const files: string[] = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const entryPath = path.join(dir, entry.name); if (entry.isDirectory()) { collectTsFiles(entryPath).forEach((file) => files.push(file)); } else if (entry.name.endsWith(".ts")) { files.push(entryPath); } } return files; } test("direct @earendil-works runtime imports are declared for CI installs", () => { const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8")); const declared = new Set([ ...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {}), ]); const imported = new Set(); for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) { const source = fs.readFileSync(file, "utf-8"); for (const match of source.matchAll(sourceImportPattern)) { imported.add(match[1] ?? match[2]!); } } const missing = [...imported].filter((specifier) => !declared.has(specifier)).sort(); assert.deepEqual(missing, []); }); test("direct dependency declarations are exact version pins", () => { const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8")); for (const section of ["dependencies", "devDependencies"] as const) { for (const [name, version] of Object.entries(packageJson[section] ?? {})) { assert.match(version, exactVersionPattern, `${section}.${name} should use an exact version`); } } }); test("old pi package scope is not used by source or tests", () => { for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) { const source = fs.readFileSync(file, "utf-8"); assert.equal(oldPiScopePattern.test(source), false, file); } }); test("Pi package resolution stays export-map safe", () => { for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) { const source = fs.readFileSync(file, "utf-8"); assert.equal(piPackageJsonSubpathPattern.test(source), false, `${file} should not resolve unexported package.json subpaths`); assert.equal(cjsPiPackageResolutionPattern.test(source), false, `${file} should not use CommonJS resolution for ESM-only Pi packages`); } });