import { parse } from "@babel/parser"; import { parseDependencyTree } from "dpdm/lib/parser.js"; import type { DependencyTree } from "dpdm/lib/types.js"; import { parseCircular, prettyCircular } from "dpdm/lib/utils.js"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { describe, it } from "node:test"; import { fileURLToPath } from "node:url"; import pkgRaw from "../../package.json" with { type: "json" }; interface IExportTarget { browser?: string; node?: string; types?: string; default?: string; } interface IPackageJsonContract { exports?: Record; imports?: Record; typesVersions?: Record>; } interface IModuleSpecifier { specifier: string; } const pkg = pkgRaw as IPackageJsonContract; const getTypesVersionsMap = (): Record => { return pkg.typesVersions?.["*"] ?? {}; }; const toTypesVersionKey = (exportKey: string): string => { return exportKey === "." ? "." : exportKey.replace(/^\.\//, ""); }; const isDistPath = (value: string): boolean => value.startsWith("./dist/"); const hasAllowedRuntimeExt = (value: string): boolean => /\.(mjs|css)$/.test(value); const hasAllowedTypeExt = (value: string): boolean => /\.d\.mts$/.test(value); const hasWildcard = (value: string): boolean => value.includes("*"); const getStringLiteralValue = (value: unknown): string | undefined => { if (!value || typeof value !== "object") { return undefined; } const node = value as Record; return node.type === "StringLiteral" && typeof node.value === "string" ? node.value : undefined; }; const getModuleSpecifiers = (source: string): IModuleSpecifier[] => { const result: IModuleSpecifier[] = []; const ast = parse(source, { createImportExpressions: true, plugins: [ "typescript" ], sourceType: "module", }); const visit = (value: unknown): void => { if (Array.isArray(value)) { value.forEach(visit); return; } if (!value || typeof value !== "object") { return; } const node = value as Record; if (typeof node.type !== "string") { return; } let sourceNode: unknown; if (node.type === "TSExternalModuleReference") { sourceNode = node.expression; } else if ([ "ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration", "ImportExpression", "TSImportType", ].includes(node.type)) { sourceNode = node.source; } const specifier = getStringLiteralValue(sourceNode); if (specifier) { result.push({ specifier }); } Object.values(node).forEach(visit); }; visit(ast); return result; }; const matchesPackageImport = (specifier: string, packageImport: string): boolean => { const wildcardIndex = packageImport.indexOf("*"); if (wildcardIndex === -1) { return specifier === packageImport; } const prefix = packageImport.slice(0, wildcardIndex); const suffix = packageImport.slice(wildcardIndex + 1); return specifier.length >= prefix.length + suffix.length && specifier.startsWith(prefix) && specifier.endsWith(suffix); }; const getAuditInvocation = (): { args: string[]; command: string; } => { const auditArgs = [ "audit", "--audit-level=high" ]; const npmExecPath = process.env.npm_execpath; if (npmExecPath) { return { args: [ npmExecPath, ...auditArgs ], command: process.execPath, }; } if (process.platform === "win32") { return { args: [ path.resolve(path.dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"), ...auditArgs ], command: process.execPath, }; } return { args: auditArgs, command: "npm", }; }; describe("Cyclic dependencies", () => { it("Check dependencies", async () => { const sourceFolder = path.join(process.cwd(), "./src/package"); const circulars = await parseDependencyTree([ sourceFolder ], {}) .then((tree: DependencyTree) => { const circulars = parseCircular(tree); if (circulars.length) { console.error(prettyCircular(circulars)); } return circulars.length; }) .catch((err: Error) => { console.error(`Не удалось выполнить тест на циклические зависимости: ${err.message}`); return 0; }); assert.ok(circulars === 0); }); }); describe("Dependency security audit", () => { it("Has no high or critical vulnerabilities", () => { const { args, command } = getAuditInvocation(); const result = spawnSync(command, args, { encoding: "utf8" }); assert.ifError(result.error); const output = [ result.stdout, result.stderr ].filter(Boolean).join("\n"); assert.equal(result.status, 0, output || `npm audit failed with exit code ${result.status ?? "unknown"}`); }); }); describe("Package public API contract", () => { it("contains no internal package imports in dist module artifacts", () => { const __dirname = fileURLToPath(new URL(".", import.meta.url)); const distDir = path.resolve(__dirname, "../../dist"); const packageImports = Object.keys(pkg.imports ?? {}); // eslint-disable-next-line security/detect-non-literal-fs-filename assert.ok(fs.existsSync(distDir), "dist directory must exist"); const distFiles = fs.globSync([ "**/*.mjs", "**/*.d.mts" ], { cwd: distDir }); assert.ok(distFiles.length > 0, "dist must contain module artifacts"); const violations = distFiles.flatMap((relativePath) => { const artifactPath = path.resolve(distDir, relativePath); // eslint-disable-next-line security/detect-non-literal-fs-filename const source = fs.readFileSync(artifactPath, "utf8"); return getModuleSpecifiers(source) .filter(({ specifier }) => packageImports.some((item) => matchesPackageImport(specifier, item))) .map(({ specifier }) => `${relativePath}: "${specifier}"`); }); assert.equal( violations.length, 0, `Dist artifacts contain internal package imports:\n${violations.join("\n")}` ); }); it("keeps exports and typesVersions in sync", () => { const exportsMap = pkg.exports ?? {}; const typesVersionsMap = getTypesVersionsMap(); const exportKeysWithTypes = Object .entries(exportsMap) .filter(([ key, value ]) => { return !key.includes("*") && typeof value === "object" && !!value && typeof value.types === "string"; }) .map(([ key ]) => toTypesVersionKey(key)); exportKeysWithTypes.forEach((key) => { assert.ok( key in typesVersionsMap, `Missing typesVersions entry for export key "${key}"` ); }); Object.keys(typesVersionsMap).forEach((typesKey) => { const exportKey = typesKey === "." ? "." : `./${typesKey}`; assert.ok( exportKey in exportsMap, `typesVersions key "${typesKey}" has no matching export "${exportKey}"` ); }); }); it("keeps export targets in dist and with valid extensions", () => { const exportsMap = pkg.exports ?? {}; Object.entries(exportsMap).forEach(([ key, value ]) => { if (typeof value === "string") { assert.ok(isDistPath(value), `Export "${key}" points outside dist: "${value}"`); return; } if (value.types) { assert.ok(isDistPath(value.types), `Export "${key}" types path outside dist: "${value.types}"`); assert.ok(hasAllowedTypeExt(value.types), `Export "${key}" types extension is invalid: "${value.types}"`); } if (value.default) { assert.ok(isDistPath(value.default), `Export "${key}" default path outside dist: "${value.default}"`); assert.ok( hasAllowedRuntimeExt(value.default), `Export "${key}" default extension is invalid: "${value.default}"` ); } [ value.browser, value.node ].forEach((target) => { if (target) { assert.ok(isDistPath(target), `Export "${key}" conditional path outside dist: "${target}"`); assert.ok(hasAllowedRuntimeExt(target), `Export "${key}" conditional extension is invalid: "${target}"`); } }); }); const typesVersionsMap = getTypesVersionsMap(); Object.entries(typesVersionsMap).forEach(([ key, list ]) => { assert.ok(Array.isArray(list) && list.length > 0, `typesVersions "${key}" must contain non-empty path array`); list.forEach((item) => { assert.ok(isDistPath(item), `typesVersions "${key}" path outside dist: "${item}"`); assert.ok(hasAllowedTypeExt(item), `typesVersions "${key}" type extension is invalid: "${item}"`); }); }); }); it("verifies dist paths existence for non-wildcard entries when dist is present", () => { if (process.env.CHECK_DIST_PUBLIC_API !== "1") { console.debug("[public-api-contract] CHECK_DIST_PUBLIC_API is not enabled, skipping dist existence checks."); return; } const __dirname = fileURLToPath(new URL(".", import.meta.url)); const distDir = path.resolve(__dirname, "../../dist"); // eslint-disable-next-line security/detect-non-literal-fs-filename if (!fs.existsSync(distDir)) { console.debug("[public-api-contract] dist/ not found, skipping file existence checks."); return; } const exportsMap = pkg.exports ?? {}; const typesVersionsMap = getTypesVersionsMap(); const nonWildcardExportPaths = Object.values(exportsMap) .flatMap((value) => { if (typeof value === "string") { return [ value ]; } return [ value.types, value.browser, value.node, value.default ].filter((item): item is string => !!item); }) .filter((item) => !hasWildcard(item)); const nonWildcardTypesPaths = Object.values(typesVersionsMap) .flat() .filter((item) => !hasWildcard(item)); [ ...nonWildcardExportPaths, ...nonWildcardTypesPaths ] .forEach((relativePath) => { const absPath = path.resolve(__dirname, "../../", relativePath.replace(/^\.\//, "")); // eslint-disable-next-line security/detect-non-literal-fs-filename assert.ok(fs.existsSync(absPath), `Missing dist artifact for public API path: "${relativePath}"`); }); }); });