test(FN-2343): harden build-output contract coverage

- Add shared test setup helpers that always build dashboard client assets before assertions run
- Remove skip-gated build-output tests and make CLI/dashboard suites deterministic by owning artifact setup
- Enforce hashed vendor chunk naming checks for vendor-react and vendor-xterm in generated assets
- Verify copied CLI client index references real built chunks and does not contain the dashboard stub marker
This commit is contained in:
Fusion
2026-04-23 13:29:23 -07:00
committed by gsxdsm
parent cab684877e
commit abe35bea80
4 changed files with 95 additions and 29 deletions

View File

@@ -0,0 +1,31 @@
import { execSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { join } from "node:path";
export const cliRoot = join(__dirname, "..", "..");
export const workspaceRoot = join(cliRoot, "..", "..");
export const bundlePath = join(cliRoot, "dist", "bin.js");
export const clientIndexPath = join(cliRoot, "dist", "client", "index.html");
export const dashboardClientStubMarker = "Dashboard assets not built";
function runBuildCommand(command: string, cwd: string) {
execSync(command, {
cwd,
stdio: "pipe",
timeout: 240_000,
});
}
/**
* This suite verifies real copied dashboard client assets in CLI dist output.
* It must build those assets explicitly instead of skip-gating on ambient dist/.
*/
export function buildCliWithRealDashboardAssets() {
runBuildCommand("pnpm --filter @fusion/dashboard build:client", workspaceRoot);
runBuildCommand("pnpm build", cliRoot);
}
export function readClientIndexHtml() {
return readFileSync(clientIndexPath, "utf-8");
}

View File

@@ -1,26 +1,21 @@
import { describe, it, expect, beforeAll } from "vitest";
import { execSync } from "node:child_process";
import { readFileSync, existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import {
buildCliWithRealDashboardAssets,
bundlePath,
cliRoot,
clientIndexPath,
dashboardClientStubMarker,
readClientIndexHtml,
} from "./bundle-output-helpers";
const cliRoot = join(__dirname, "..", "..");
const bundlePath = join(cliRoot, "dist", "bin.js");
const clientIndexPath = join(cliRoot, "dist", "client", "index.html");
const tsupConfigPath = join(cliRoot, "tsup.config.ts");
const clientDirExists = existsSync(clientIndexPath);
describe("CLI bundle output", () => {
beforeAll(() => {
if (existsSync(bundlePath)) {
return;
}
execSync("pnpm build", {
cwd: cliRoot,
stdio: "pipe",
timeout: 120_000,
});
}, 180_000);
buildCliWithRealDashboardAssets();
}, 300_000);
it("dist/bin.js exists", () => {
expect(existsSync(bundlePath)).toBe(true);
@@ -46,8 +41,19 @@ describe("CLI bundle output", () => {
expect(content).toContain("createServer");
});
it.skipIf(!clientDirExists)("dashboard client assets are included", () => {
it("dashboard client assets are included", () => {
expect(existsSync(clientIndexPath)).toBe(true);
const indexHtml = readClientIndexHtml();
expect(indexHtml).toContain("<script");
expect(indexHtml).toMatch(/assets\/.+-[A-Za-z0-9_-]+\.js/);
expect(indexHtml).toMatch(/assets\/vendor-react-[A-Za-z0-9_-]+\.js/);
expect(indexHtml).not.toContain(dashboardClientStubMarker);
const copiedAssetsDir = join(cliRoot, "dist", "client", "assets");
const copiedAssets = readdirSync(copiedAssetsDir);
expect(copiedAssets.some((file) => /^vendor-react-[A-Za-z0-9_-]+\.js$/.test(file))).toBe(true);
expect(copiedAssets.some((file) => /^vendor-xterm-[A-Za-z0-9_-]+\.js$/.test(file))).toBe(true);
});
it("tsup config copies dashboard assets from dashboard/dist/client to dist/client", () => {
@@ -69,7 +75,7 @@ describe("CLI bundle output", () => {
it("provides require via createRequire banner", () => {
const content = readFileSync(bundlePath, "utf-8");
// Banner should inject createRequire for ESM CJS interop
expect(content).toContain('createRequire');
expect(content).toContain("createRequire");
expect(content).toContain("import.meta.url");
// Banner should be near the top of the file (after shebang)
const shebangEnd = content.indexOf("\n");