Files
genshin-assistant/scripts/run-offline-acceptance.mjs
T

75 lines
2.9 KiB
JavaScript

import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
const steps = [
{ id: "dev-cleanup-safety", command: npmCommand, args: ["run", "dev-cleanup:test"] },
{ id: "native-helper-safety", command: npmCommand, args: ["run", "helper:test"] },
{ id: "lint", command: npmCommand, args: ["run", "lint"] },
{ id: "tests", command: npmCommand, args: ["test"] },
{ id: "ocr-eval", command: npmCommand, args: ["run", "eval"] },
{ id: "assessment-self-test", command: npmCommand, args: ["run", "scan:assessment:test"] },
{ id: "saved-native-validation", command: npmCommand, args: ["run", "scan:native:validate:saved"] },
{ id: "package-offline-check", command: npmCommand, args: ["run", "package:offline-check"] },
{ id: "dependency-audit", command: npmCommand, args: ["audit"] },
{ id: "production-dependency-audit", command: npmCommand, args: ["audit", "--omit=dev"] },
{ id: "git-diff-check", command: "git", args: ["diff", "--check"] },
];
const startedAt = new Date();
const results = [];
for (const step of steps) {
const stepStartedAt = Date.now();
console.log(`\n=== Offline acceptance: ${step.id} ===`);
const invocation = commandInvocation(step);
const result = spawnSync(invocation.command, invocation.args, {
cwd: projectRoot,
encoding: "utf8",
stdio: "inherit",
windowsHide: true,
});
results.push({
id: step.id,
ok: result.status === 0 && !result.error,
exitCode: result.status,
signal: result.signal,
error: result.error?.message ?? null,
elapsedMs: Date.now() - stepStartedAt,
});
}
const outputDir = path.join(projectRoot, "outputs", "offline-acceptance");
fs.mkdirSync(outputDir, { recursive: true });
const reportPath = path.join(outputDir, "offline-acceptance-report.json");
const report = {
version: "offline-acceptance-v2",
startedAt: startedAt.toISOString(),
completedAt: new Date().toISOString(),
ok: results.every((result) => result.ok),
projectRoot,
results,
};
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf8");
console.log(`\nOffline acceptance: ${report.ok ? "PASS" : "FAIL"}`);
for (const result of results) {
console.log(`${result.ok ? "PASS" : "FAIL"} ${result.id} (${result.elapsedMs} ms)`);
}
console.log(`Report: ${reportPath}`);
if (!report.ok) process.exitCode = 1;
function commandInvocation(step) {
if (process.platform !== "win32" || !step.command.endsWith(".cmd")) return step;
const tokens = [step.command, ...step.args];
if (tokens.some((token) => !/^[A-Za-z0-9:._=@/-]+$/.test(token))) {
throw new Error(`Unsafe Windows command token in offline acceptance step ${step.id}.`);
}
return {
command: process.env.ComSpec || "cmd.exe",
args: ["/d", "/s", "/c", tokens.join(" ")],
};
}