#!/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; }; const readPackageJson = async (pkgDir: string): Promise => { 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; const smokeTestPackage = async ( pkgDir: string, tarballs: Tarballs, failures: SmokeFailure[], ): Promise => { 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 = {}; 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: "", 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(); 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();