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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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)", () => {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user