feat(FN-2361): codify workspace verification bootstrap contract

- Add root verify:workspace script enforcing lint -> test -> build order
- Update CI workflow to run verify:workspace as the single workspace gate before binary packaging
- Add CLI guardrail tests for workflow sequencing and root script contract invariants
- Document deterministic workspace bootstrap expectations in contributing guide
- Clarify bundle-output test bootstrap intent for explicit artifact setup
This commit is contained in:
Fusion
2026-04-23 15:44:54 -07:00
committed by gsxdsm
parent 54843012bc
commit fbe5b82ce0
12 changed files with 448 additions and 26 deletions

View File

@@ -14,6 +14,9 @@ const tsupConfigPath = join(cliRoot, "tsup.config.ts");
describe("CLI bundle output", () => {
beforeAll(() => {
// Intentional: bundle-output tests validate compiled artifacts, so they
// perform their own explicit build bootstrap instead of relying on ambient
// workspace dist/ state.
buildCliWithRealDashboardAssets();
}, 300_000);

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, beforeAll } from "vitest";
import { readFileSync, accessSync, constants, existsSync } from "node:fs";
import { readFileSync, accessSync, constants } from "node:fs";
import { join } from "node:path";
import { parse } from "yaml";
@@ -22,13 +22,20 @@ function loadWorkflow(name: string): any {
describe("CI workflow (.github/workflows/ci.yml)", () => {
let workflow: any;
let content: string;
let ciSteps: any[];
beforeAll(() => {
const result = loadWorkflow("ci.yml");
workflow = result.parsed;
content = result.content;
ciSteps = workflow.jobs?.ci?.steps ?? [];
});
const findStepByRun = (runSnippet: string) => ciSteps.find((step) => typeof step.run === "string" && step.run.includes(runSnippet));
const findStepIndexByRun = (runSnippet: string) =>
ciSteps.findIndex((step) => typeof step.run === "string" && step.run.includes(runSnippet));
it("is valid YAML", () => {
expect(workflow).toBeDefined();
expect(typeof workflow).toBe("object");
@@ -47,8 +54,24 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
expect(content).toContain("pnpm install");
});
it("includes pnpm build step", () => {
expect(content).toContain("pnpm build");
it("uses verify:workspace as the single lint/test/build contract", () => {
const verifyStep = findStepByRun("pnpm verify:workspace");
expect(verifyStep).toBeDefined();
expect(verifyStep.name).toContain("bootstrap contract");
const directLintStep = findStepByRun("pnpm lint");
const directTestStep = findStepByRun("pnpm test");
const directBuildStep = findStepByRun("pnpm build");
expect(directLintStep).toBeUndefined();
expect(directTestStep).toBeUndefined();
expect(directBuildStep).toBeUndefined();
});
it("runs workspace verification before binary packaging", () => {
const verifyIdx = findStepIndexByRun("pnpm verify:workspace");
const buildExeIdx = findStepIndexByRun("build:exe");
expect(verifyIdx).toBeGreaterThanOrEqual(0);
expect(buildExeIdx).toBeGreaterThan(verifyIdx);
});
it("includes binary build step", () => {
@@ -62,10 +85,6 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
it("verifies binary exists after build", () => {
expect(content).toContain("test -f packages/cli/dist/fn");
});
it("includes pnpm test step", () => {
expect(content).toContain("pnpm test");
});
});
describe("Version & Release workflow (.github/workflows/version.yml)", () => {

View File

@@ -16,6 +16,11 @@ function loadWorkflowYaml(name: string): any {
return parse(content);
}
function loadRootPackageJson(): any {
const path = join(workspaceRoot, "package.json");
return JSON.parse(readFileSync(path, "utf-8"));
}
describe("CLI package.json publishing config", () => {
const pkg = loadPackageJson("cli");
@@ -106,6 +111,30 @@ describe("Scoped @fusion/* packages publishing config", () => {
}
});
describe("Workspace bootstrap script contract", () => {
const rootPkg = loadRootPackageJson();
it("keeps root test self-sufficient (no implicit pre-build dependency)", () => {
const testScript = rootPkg.scripts?.test;
expect(testScript).toBeDefined();
expect(testScript).toContain("pnpm -r");
expect(testScript).not.toContain("pnpm build");
});
it("defines verify:workspace in lint -> test -> build order", () => {
const verifyScript = rootPkg.scripts?.["verify:workspace"];
expect(verifyScript).toBe("pnpm lint && pnpm test && pnpm build");
const lintIdx = verifyScript.indexOf("pnpm lint");
const testIdx = verifyScript.indexOf("pnpm test");
const buildIdx = verifyScript.indexOf("pnpm build");
expect(lintIdx).toBeGreaterThanOrEqual(0);
expect(testIdx).toBeGreaterThan(lintIdx);
expect(buildIdx).toBeGreaterThan(testIdx);
});
});
describe("Workflow YAML validity", () => {
it("ci.yml is valid YAML", () => {
const parsed = loadWorkflowYaml("ci.yml");