233 lines · 7.8 KB · TypeScript
RawDownload
#!/usr/bin/env bun/** * Pack-and-import smoke test for the public `@executor-js/*` packages. * * Reproduces what an external npm consumer experiences: * *   1. Pack each publishable workspace via `publish-packages.ts *      --dry-run`, so `publishConfig.exports` and `workspace:*` rewrites *      are applied to the tarball. *   2. For each package, install the tarball into a fresh temp dir *      with npm `overrides` pointing every other `@executor-js/*` *      transitive dep at its local tarball — otherwise npm pulls the *      currently-published version from the registry, which masks any *      internal-API mismatch this branch introduced. *   3. Read the installed package.json (the post-`publishConfig` view) *      and dynamically `import()` every subpath in its `exports` map. * * Failures for `@executor-js/*` package not-found are hard failures — * that's the regression class where private workspace packages leak * into a public bundle. Failures for other peers (`react`, `effect`, * `@tanstack/*`, etc.) are downgraded to warnings, since they reflect * a missing peer in the smoke environment, not a bug in the bundle. * * Invoke via `bun run release:smoke:packages`. */import { $ } from "bun";import { existsSync, readdirSync } from "node:fs";import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";import { tmpdir } from "node:os";import { dirname, join, resolve } from "node:path";import { fileURLToPath } from "node:url"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const PUBLIC_PACKAGE_DIRS = [  "packages/core/fumadb",  "packages/kernel/core",  "packages/kernel/runtime-quickjs",  "packages/core/sdk",  "packages/core/config",  "packages/core/execution",  "packages/core/cli",  "packages/plugins/example",  "packages/plugins/file-secrets",  "packages/plugins/graphql",  "packages/plugins/keychain",  "packages/plugins/mcp",  "packages/plugins/onepassword",  "packages/plugins/openapi",] as const; type PackageJson = {  name: string;  version: string;  exports?: Record<string, unknown>;}; const readPackageJson = async (pkgDir: string): Promise<PackageJson> => {  const raw = await readFile(join(pkgDir, "package.json"), "utf8");  return JSON.parse(raw) as PackageJson;}; const findTarball = (pkgDir: string): string | null => {  const tgz = readdirSync(pkgDir).find((entry) => entry.endsWith(".tgz"));  return tgz ? join(pkgDir, tgz) : null;}; const subpathsToTest = (pkg: PackageJson): readonly string[] => Object.keys(pkg.exports ?? {}); const importSpecifier = (pkgName: string, subpath: string): string =>  subpath === "." ? pkgName : `${pkgName}${subpath.slice(1)}`; type SmokeFailure = {  readonly pkg: string;  readonly subpath: string;  readonly reason: string;}; const PRIVATE_PACKAGE_RE = /Cannot find package '(@executor-js\/[^']+)'/; const firstMeaningfulLine = (stderr: string): string => {  const lines = stderr    .split("\n")    .map((line) => line.trim())    .filter(Boolean);  const errorLine = lines.find((line) => /^(\w*Error|Cannot find)/i.test(line));  if (errorLine) return errorLine;  for (const line of lines) {    if (line.startsWith("node:")) continue;    if (line.startsWith("file://")) continue;    if (line.startsWith("at ")) continue;    if (line.startsWith("import ")) continue;    if (line.startsWith("throw ")) continue;    if (line === "^" || /^\^+$/.test(line)) continue;    return line;  }  return lines[0] ?? "(no stderr)";}; type Tarballs = ReadonlyMap<string, string>; const smokeTestPackage = async (  pkgDir: string,  tarballs: Tarballs,  failures: SmokeFailure[],): Promise<void> => {  const pkg = await readPackageJson(pkgDir);  const tarballPath = tarballs.get(pkg.name);  if (!tarballPath) {    failures.push({ pkg: pkg.name, subpath: "", reason: "no tarball produced" });    return;  }   const tmp = await mkdtemp(join(tmpdir(), "executor-smoke-"));  try {    // npm `overrides` forces transitive `@executor-js/*` deps to resolve    // to their local tarball instead of whatever's published on npm.    // Without this, a plugin built against an unreleased symbol in    // `@executor-js/sdk` would silently install the published `sdk`    // and fail at import — a real bug, but not the bundle bug we're    // here to catch.    const overrides: Record<string, string> = {};    for (const [name, path] of tarballs) {      overrides[name] = `file:${path}`;    }    const fixture = {      name: "executor-smoke-fixture",      version: "0.0.0",      private: true,      type: "module",      dependencies: { [pkg.name]: `file:${tarballPath}` },      overrides,    };    await writeFile(join(tmp, "package.json"), `${JSON.stringify(fixture, null, 2)}\n`);     const install = await $`npm install --no-audit --no-fund --legacy-peer-deps`      .cwd(tmp)      .quiet()      .nothrow();    if (install.exitCode !== 0) {      failures.push({        pkg: pkg.name,        subpath: "<install>",        reason: install.stderr.toString().trim().split("\n").slice(-3).join("\n"),      });      return;    }     // Read the installed manifest — that's the real published view    // (publishConfig.exports applied, workspace specifiers resolved).    const installedPkg = await readPackageJson(join(tmp, "node_modules", ...pkg.name.split("/")));    const subpaths = subpathsToTest(installedPkg);    if (subpaths.length === 0) {      failures.push({        pkg: pkg.name,        subpath: "",        reason: "no exports declared in published manifest",      });      return;    }     for (const subpath of subpaths) {      const spec = importSpecifier(pkg.name, subpath);      const probe =        await $`node --input-type=module --eval ${`await import(${JSON.stringify(spec)});`}`          .cwd(tmp)          .quiet()          .nothrow();      if (probe.exitCode === 0) {        console.log(`  ok  ${spec}`);        continue;      }      const stderr = probe.stderr.toString();      const privateMatch = stderr.match(PRIVATE_PACKAGE_RE);      if (privateMatch) {        const offending = privateMatch[1];        failures.push({          pkg: pkg.name,          subpath,          reason: `published bundle imports private workspace package '${offending}'`,        });        console.log(`  FAIL ${spec} — references private '${offending}'`);        continue;      }      const peerMatch =        stderr.match(/Cannot find package '([^']+)'/) ??        stderr.match(/Cannot find module '([^']+)'/);      const detail = peerMatch ? `missing peer '${peerMatch[1]}'` : firstMeaningfulLine(stderr);      console.log(`  skip ${spec} — ${detail}`);    }  } finally {    await rm(tmp, { recursive: true, force: true });  }}; const main = async () => {  console.log("[smoke] packing public packages via publish-packages.ts --dry-run");  await $`bun run scripts/publish-packages.ts --dry-run`.cwd(repoRoot);   const tarballs = new Map<string, string>();  for (const relDir of PUBLIC_PACKAGE_DIRS) {    const pkgDir = join(repoRoot, relDir);    if (!existsSync(pkgDir)) continue;    const pkg = await readPackageJson(pkgDir);    const tarball = findTarball(pkgDir);    if (tarball) tarballs.set(pkg.name, tarball);  }   const failures: SmokeFailure[] = [];  for (const relDir of PUBLIC_PACKAGE_DIRS) {    const pkgDir = join(repoRoot, relDir);    if (!existsSync(pkgDir)) {      failures.push({ pkg: relDir, subpath: "", reason: "missing dir" });      continue;    }    const pkg = await readPackageJson(pkgDir);    console.log(`[smoke] ${pkg.name}`);    await smokeTestPackage(pkgDir, tarballs, failures);  }   if (failures.length === 0) {    console.log("[smoke] all packages OK");    return;  }   console.error(`\n[smoke] ${failures.length} failure(s):`);  for (const f of failures) {    console.error(`  - ${f.pkg}${f.subpath ? ` (${f.subpath})` : ""}: ${f.reason}`);  }  process.exit(1);}; await main();