mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-28 15:46:58 +09:00
Closes #6280 ## Summary - Expose the deployed Builder's CLI-facing bundle contract through a non-cached, read-only endpoint. - Resolve and execute the exact `webstudio@latest` registry artifact, observe the bundle version sent by its real `sync` request, and require an exact match before assigning the release staging alias. - Include expected and received bundle versions in runtime and release-gate errors. ## Technical choices - The existing generated `bundleVersion` remains the single compatibility boundary. Builder-only changes outside that project-bundle contract do not require a CLI release. - The CLI sync probe runs against a local fixture API and temporary directory. It reads the deployed runtime contract but never reads or modifies a Cloud project. - Verification runs after Vercel creates the exact production-built deployment and before the release staging alias is assigned. A mismatch therefore leaves production domains untouched. - Registry resolution requires version and integrity metadata, then executes the resolved immutable version rather than source from the checkout. - The gate requires the deployed Builder origin and cannot fall back to the checkout's contract. ## Todo 1. [x] Add the deployed Builder contract endpoint. 2. [x] Probe the exact published CLI through its real sync request. 3. [x] Gate the release staging alias on an exact contract match. 4. [x] Report expected and received versions for compatibility failures. 5. [x] Add regression tests and verify the production Builder build. 6. [x] Review documentation and fixture impact. ## Verification - `pnpm --filter @webstudio-is/builder exec vitest run app/services/build-router.server.test.ts app/services/cli-compatibility-route.node.test.ts` — 12 passed. - `pnpm --filter webstudio exec vitest run scripts/builder-contract-gate.test.ts scripts/release-smoke-registry.test.ts` — 7 passed. - Rebased onto `main` at `9ec4c820e1`. - Builder and CLI typechecks passed. - `pnpm lint` and changed-file formatting passed. - `pnpm --filter @webstudio-is/builder build` passed and emitted `/rest/cli-compatibility`. - The changed GitHub Action parses as valid YAML. - Real registry probe passed for `webstudio@0.293.0` against `bundle-kzb45v`. - Real registry probe correctly blocked current Builder contract `bundle-1mym82p` and reported expected `bundle-1mym82p`, received `bundle-kzb45v`. - Missing staged Builder origin fails closed before registry execution. - GitHub CI passed all four workflows, including all six Builder E2E shards. - The formerly failing empty-MDX insertion workflow passed 8 repeated runs with 4 workers locally, then passed in Builder E2E shard 4 in CI. - Full browser-based release smoke was not run, per the request not to use Chrome. Agent evaluations were not run because no evaluation approval was requested or received. - Documentation: no user-facing documentation changes are required. - Fixtures/generated outputs: no fixture inputs or generated outputs are affected. ## Operational boundary The check needs the exact deployed runtime, so Vercel creates an unaliased `--skip-domain` deployment before verification. A failed check cannot affect production domains or the normal release alias. Repository automation cannot prevent a Vercel administrator from manually promoting an arbitrary deployment outside this release path.
72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import { describe, expect, test, vi } from "vitest";
|
|
import {
|
|
assertPublishedCliContract,
|
|
loadBuilderBundleVersion,
|
|
} from "./builder-contract-gate";
|
|
|
|
describe("published CLI contract gate", () => {
|
|
test("accepts the exact Builder bundle contract", () => {
|
|
expect(() =>
|
|
assertPublishedCliContract({
|
|
cliVersion: "0.293.0",
|
|
expectedVersion: "bundle-shared",
|
|
receivedVersion: "bundle-shared",
|
|
})
|
|
).not.toThrow();
|
|
});
|
|
|
|
test("reports the expected and received bundle contracts", () => {
|
|
expect(() =>
|
|
assertPublishedCliContract({
|
|
cliVersion: "0.293.0",
|
|
expectedVersion: "bundle-builder",
|
|
receivedVersion: "bundle-cli",
|
|
})
|
|
).toThrowError(
|
|
"Published CLI contract mismatch for webstudio@0.293.0. Expected bundle version bundle-builder, received bundle-cli. Publish a matching CLI before deploying Builder."
|
|
);
|
|
});
|
|
|
|
test("rejects a CLI that does not send a bundle contract", () => {
|
|
expect(() =>
|
|
assertPublishedCliContract({
|
|
cliVersion: "0.293.0",
|
|
expectedVersion: "bundle-builder",
|
|
receivedVersion: undefined,
|
|
})
|
|
).toThrowError(
|
|
"Published CLI contract mismatch for webstudio@0.293.0. Expected bundle version bundle-builder, received missing. Publish a matching CLI before deploying Builder."
|
|
);
|
|
});
|
|
|
|
test("reads the contract from the staged Builder runtime", async () => {
|
|
const request = vi.fn(async () =>
|
|
Response.json({ bundleVersion: "bundle-runtime" })
|
|
);
|
|
|
|
await expect(
|
|
loadBuilderBundleVersion("https://candidate.example", {
|
|
attempts: 1,
|
|
request,
|
|
})
|
|
).resolves.toBe("bundle-runtime");
|
|
expect(request).toHaveBeenCalledWith(
|
|
new URL("https://candidate.example/rest/cli-compatibility"),
|
|
expect.objectContaining({ cache: "no-store" })
|
|
);
|
|
});
|
|
|
|
test("rejects an invalid staged Builder contract response", async () => {
|
|
const request = vi.fn(async () => Response.json({}));
|
|
|
|
await expect(
|
|
loadBuilderBundleVersion("https://candidate.example", {
|
|
attempts: 1,
|
|
request,
|
|
})
|
|
).rejects.toThrowError(
|
|
"Could not read the CLI contract from https://candidate.example/rest/cli-compatibility after 1 attempt: Builder CLI compatibility response has no bundle version."
|
|
);
|
|
});
|
|
});
|