import { existsSync } from 'node:fs' import { mkdir, writeFile } from 'node:fs/promises' import { isBuiltin } from 'node:module' import { dirname, join } from 'node:path' export type PackageManifest = { dependencies?: Record devDependencies?: Record optionalDependencies?: Record } /** Bare specifier (package or package subpath) to the names release source imports from it. */ export type PackageImports = Map> // Anchored to a line start, with only import-clause characters, so prose in comments never matches. // Type-only imports are erased and never load, so they need no stand-in. const STATIC_IMPORT = /^\s*(?:import|export)\s+(?!type\s)([\w$*{},\s]*?)\s*\bfrom\s*(['"])([^'"\s]+)\2/gm const BARE_IMPORT = /(?:^\s*import\s*|(?` walking up from `root`. */ function resolvesFrom(root: string, name: string): boolean { for (let directory = root; ; directory = dirname(directory)) { if (existsSync(join(directory, 'node_modules', name, 'package.json'))) { return true } if (dirname(directory) === directory) { return false } } } function declaredPackages(manifest: PackageManifest): Set { return new Set([ ...Object.keys(manifest.dependencies ?? {}), ...Object.keys(manifest.devDependencies ?? {}), ...Object.keys(manifest.optionalDependencies ?? {}) ]) } function standInModule(release: string, specifier: string, names: Set): string { const reason = `Cross-version harness: release ${release} imports '${specifier}', which the current ` + 'tree does not install, so this release code path cannot run here.' const lines = [ `const reason = ${JSON.stringify(reason)}`, 'function unavailable(name) {', " const refuse = () => { throw new Error(`${reason} (used '${name}')`) }", ' return new Proxy(function unavailableExport() {}, {', // Why: module interop probes these at import time; any real use still refuses. " get: (_target, key) => typeof key === 'symbol' || key === '__esModule' || key === 'then' ? undefined : refuse(),", ' apply: refuse,', ' construct: refuse', ' })', '}' ] for (const name of [...names].sort()) { lines.push( name === 'default' ? "export default unavailable('default')" : `export const ${name} = unavailable(${JSON.stringify(name)})` ) } return `${lines.join('\n')}\n` } // Released dispatchers import their whole method table; stub only packages removed from today’s manifest to permit import. // Any actual use throws; declared but unresolved packages remain install failures. export async function installMissingPackageStandIns( root: string, release: string, imports: PackageImports, currentManifest: PackageManifest ): Promise { const declared = declaredPackages(currentManifest) const byPackage = new Map() for (const specifier of imports.keys()) { const name = packageNameOf(specifier) if (declared.has(name)) { continue } const specifiers = byPackage.get(name) if (specifiers) { specifiers.push(specifier) } else if (!resolvesFrom(root, name)) { byPackage.set(name, [specifier]) } } for (const [name, specifiers] of byPackage) { const directory = join(root, 'node_modules', ...name.split('/')) await mkdir(directory, { recursive: true }) const exportsMap: Record = {} for (const [index, specifier] of specifiers.sort().entries()) { const file = `stand-in-${index}.js` exportsMap[`.${specifier.slice(name.length)}`] = `./${file}` await writeFile( join(directory, file), standInModule(release, specifier, imports.get(specifier) ?? new Set()) ) } await writeFile( join(directory, 'package.json'), `${JSON.stringify({ name, version: '0.0.0-cross-version-stand-in', type: 'module', exports: exportsMap }, null, 2)}\n` ) } return [...byPackage.keys()].sort() }