Files
clean-architecture-frontend…/scripts/test-optional-recipe-removal.mjs
T

114 lines
3.2 KiB
JavaScript

import { spawnSync } from "node:child_process";
import {
cp,
mkdir,
readFile,
readdir,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import path from "node:path";
const fixtureRoot = path.resolve(".tmp/optional-recipe-removal");
const pnpmCli = /** @type {string} */ (process.env.npm_execpath);
const copyTargets = [
"src",
"tests",
"recipes",
"scripts",
"config",
"public",
"index.html",
"package.json",
"tsconfig.base.json",
"tsconfig.json",
"tsconfig.app.json",
"tsconfig.node.json",
"tsconfig.test.json",
"tsconfig.recipes.json",
"vite.config.js",
"vitest.config.js",
"playwright.config.js",
"eslint.config.js",
".dependency-cruiser.cjs",
];
/** @param {string} script */
function runPnpm(script) {
return (
spawnSync(process.execPath, [pnpmCli, script], {
cwd: fixtureRoot,
stdio: "inherit",
}).status === 0
);
}
/** @param {string} directory @returns {Promise<string[]>} */
async function filesBelow(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const groups = await Promise.all(
entries.map((entry) => {
const target = path.join(directory, entry.name);
return entry.isDirectory() ? filesBelow(target) : [target];
}),
);
return groups.flat();
}
await rm(fixtureRoot, { recursive: true, force: true });
await mkdir(fixtureRoot, { recursive: true });
for (const target of copyTargets) {
await cp(target, path.join(fixtureRoot, target), { recursive: true });
}
await symlink(path.resolve("node_modules"), path.join(fixtureRoot, "node_modules"), "dir");
await rm(path.join(fixtureRoot, "recipes"), { recursive: true, force: true });
await rm(path.join(fixtureRoot, "tests/recipes"), {
recursive: true,
force: true,
});
const checks = [
["typecheck", runPnpm("check:types")],
["architecture", runPnpm("check:architecture")],
["test", runPnpm("test:all")],
["build", runPnpm("build")],
];
/** @type {string[]} */
const residue = [];
for (const file of await filesBelow(path.join(fixtureRoot, "dist"))) {
if (!/\.(?:js|css|html|json)$/.test(file)) continue;
const content = await readFile(file, "utf8");
if (content.includes("frontend-optional-recipe-must-not-reach-production")) {
residue.push(path.relative(fixtureRoot, file));
}
}
checks.push(["bundle-residue", residue.length === 0]);
const passed = checks.every(([, result]) => result);
await mkdir("artifacts/tests", { recursive: true });
await writeFile(
"artifacts/tests/optional-recipe-removal.xml",
`<?xml version="1.0" encoding="UTF-8"?>\n` +
`<testsuite name="optional-recipe-removal" tests="${checks.length}" failures="${passed ? 0 : 1}">` +
checks
.map(
([name, result]) =>
`<testcase name="${name}">${result ? "" : `<failure>${residue.join(", ")}</failure>`}</testcase>`,
)
.join("") +
`</testsuite>\n`,
);
await rm(fixtureRoot, { recursive: true, force: true });
if (!passed) {
process.stderr.write(
`Optional recipe removal failed: ${checks
.filter(([, result]) => !result)
.map(([name]) => name)
.join(", ")}\n`,
);
process.exit(1);
}
process.stdout.write(
`Optional recipe removal: PASS (${checks.length} base checks)\n`,
);