/** * Standalone verification for publish-time package manifests. * * Usage: * node scripts/verify-no-workspace-protocol.mjs packages/cli packages/core * node ../../scripts/verify-no-workspace-protocol.mjs . * * Run this only after publish versions have been rewritten. * * Checks two invariants before publish: * 1. publishable dependency fields must not contain workspace: specifiers * 2. internal workspace dependencies must match the current workspace package version exactly */ import { access, readdir, readFile } from "node:fs/promises"; import { join, resolve } from "node:path"; const dirs = process.argv.slice(2); if (dirs.length === 0) { process.stderr.write("Usage: node verify-no-workspace-protocol.mjs [...]\n"); process.exit(1); } async function exists(path) { try { await access(path); return true; } catch { return false; } } async function findWorkspaceRoot(startDir) { let dir = resolve(startDir); for (let i = 0; i < 10; i++) { if (await exists(join(dir, "packages"))) { return dir; } const parent = resolve(dir, ".."); if (parent === dir) break; dir = parent; } throw new Error(`Could not find workspace root from ${startDir}`); } async function loadWorkspaceVersions(workspaceRoot) { const packagesDir = join(workspaceRoot, "packages"); const entries = await readdir(packagesDir); const versions = new Map(); for (const entry of entries) { try { const raw = await readFile(join(packagesDir, entry, "package.json"), "utf-8"); const pkg = JSON.parse(raw); versions.set(pkg.name, pkg.version); } catch { // ignore directories that are not publishable packages } } return versions; } let failed = false; const workspaceRoot = await findWorkspaceRoot(process.cwd()); const workspaceVersions = await loadWorkspaceVersions(workspaceRoot); for (const dirArg of dirs) { const dir = resolve(process.cwd(), dirArg); const packageJsonPath = join(dir, "package.json"); const raw = await readFile(packageJsonPath, "utf-8"); const pkg = JSON.parse(raw); let dirFailed = false; for (const field of ["dependencies", "optionalDependencies", "peerDependencies"]) { const deps = pkg[field]; if (!deps) continue; for (const [name, specifier] of Object.entries(deps)) { if (typeof specifier === "string" && specifier.startsWith("workspace:")) { process.stderr.write(`FAIL: ${dir} — ${field}.${name}: ${specifier}\n`); dirFailed = true; failed = true; continue; } const workspaceVersion = workspaceVersions.get(name); if (workspaceVersion && specifier !== workspaceVersion) { process.stderr.write( `FAIL: ${dir} — ${field}.${name}: expected ${workspaceVersion}, got ${specifier}\n`, ); dirFailed = true; failed = true; } } } if (!dirFailed) { process.stderr.write(`OK: ${dir}\n`); } } if (failed) { process.exit(1); }