90 lines
4.1 KiB
JavaScript
90 lines
4.1 KiB
JavaScript
// Invoked as `node scripts/vitest-cli-compat.mjs ...` (see package.json `test`); no shebang so the
|
|
// module also imports cleanly under Vite/Rollup in vitest-cli-compat.test.mjs. The CLI body below is
|
|
// gated behind `isCliEntrypoint`, so importing this file never spawns vitest.
|
|
import { spawn } from "node:child_process";
|
|
import { chmod, copyFile, mkdtemp, rm } from "node:fs/promises";
|
|
import { delimiter, join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const defaultAppTestRoot = "apps/web/app/";
|
|
const isCliEntrypoint = process.argv[1] ? import.meta.url === pathToFileURL(process.argv[1]).href : false;
|
|
|
|
export function normalizeVitestArgs(vitestArgs) {
|
|
const brokenTenantArg = vitestArgs.find((arg) => arg.includes("app./"));
|
|
if (brokenTenantArg) {
|
|
throw new Error(`Refusing to repair shell-expanded tenant route path "${brokenTenantArg}". Quote paths containing $tenant or use apps/web/app/... explicitly.`);
|
|
}
|
|
const repairedArgs = vitestArgs;
|
|
const hasDefaultAppTestRoot = repairedArgs.includes(defaultAppTestRoot);
|
|
const hasExplicitTestPath = repairedArgs.some((arg, index) => index > 0 && arg !== defaultAppTestRoot && /^(?:app|apps\/web\/app)\/.*\.test\.[cm]?[jt]sx?$/.test(arg));
|
|
|
|
if (!hasDefaultAppTestRoot || !hasExplicitTestPath) return repairedArgs;
|
|
|
|
return repairedArgs.filter((arg, index) => {
|
|
if (arg === defaultAppTestRoot) return false;
|
|
return !(arg === "--" && repairedArgs[index - 1] === defaultAppTestRoot);
|
|
});
|
|
}
|
|
|
|
export function appendNodeOption(nodeOptions, option) {
|
|
return [nodeOptions, option].filter(Boolean).join(" ");
|
|
}
|
|
|
|
async function createPnpmExecCompatShimDir() {
|
|
const shimDir = await mkdtemp(join(tmpdir(), "hivegrid-vitest-compat-"));
|
|
await Promise.all(["eslint", "vitest"].map((name) => copyPnpmExecCompatShim(shimDir, name)));
|
|
return shimDir;
|
|
}
|
|
|
|
async function copyPnpmExecCompatShim(shimDir, name) {
|
|
const shimSource = fileURLToPath(new URL(`./${name}-pnpm-exec-shim.cjs`, import.meta.url));
|
|
const shimDestination = join(shimDir, name);
|
|
await copyFile(shimSource, shimDestination);
|
|
await chmod(shimDestination, 0o755);
|
|
}
|
|
|
|
if (isCliEntrypoint) {
|
|
// Computed here (not at module top level) so importing this file for unit tests never touches
|
|
// `import.meta.url` under Vite, where it is not a file: URL and `fileURLToPath` would throw.
|
|
const vitestEntry = fileURLToPath(new URL("../node_modules/vitest/vitest.mjs", import.meta.url));
|
|
const repoRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
const rawArgs = process.argv.slice(2);
|
|
const args = rawArgs.flatMap((arg) => (arg === "--runInBand" ? ["--single-thread"] : [arg]));
|
|
const normalizedArgs = normalizeVitestArgs(args);
|
|
let shimDir;
|
|
const storageDir = await mkdtemp(join(tmpdir(), "hivegrid-vitest-storage-"));
|
|
let childEnv = { ...process.env, VITEST_PNPM_EXEC_COMPAT_READY: "1", HIVEGRID_REPO_ROOT: repoRoot };
|
|
|
|
if (!process.env.VITEST_PNPM_EXEC_COMPAT_READY) {
|
|
shimDir = await createPnpmExecCompatShimDir();
|
|
childEnv = { ...childEnv, PATH: `${shimDir}${delimiter}${process.env.PATH ?? ""}` };
|
|
}
|
|
|
|
// `--localstorage-file` enables Node's experimental file-backed localStorage,
|
|
// which only exists on Node >= 22. Older Node (e.g. the CI runner's system
|
|
// node 20) rejects unknown flags in NODE_OPTIONS and refuses to start
|
|
// ("--localstorage-file= is not allowed in NODE_OPTIONS"), so only append it
|
|
// when the running Node actually supports it. The jsdom test environment
|
|
// provides its own localStorage regardless.
|
|
if (Number(process.versions.node.split(".")[0]) >= 22) {
|
|
childEnv.NODE_OPTIONS = appendNodeOption(childEnv.NODE_OPTIONS, `--localstorage-file=${join(storageDir, "localstorage.json")}`);
|
|
}
|
|
|
|
const child = spawn(process.execPath, [vitestEntry, ...normalizedArgs], {
|
|
cwd: repoRoot,
|
|
stdio: "inherit",
|
|
env: childEnv,
|
|
});
|
|
|
|
child.on("exit", async (code, signal) => {
|
|
await Promise.all([shimDir ? rm(shimDir, { recursive: true, force: true }) : Promise.resolve(), rm(storageDir, { recursive: true, force: true })]);
|
|
if (signal) {
|
|
process.kill(process.pid, signal);
|
|
return;
|
|
}
|
|
|
|
process.exit(code ?? 1);
|
|
});
|
|
}
|