Files
elizaos--eliza/packages/scripts/ci-bun-version-contract.mjs
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

173 lines
6.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Contract for the pinned CI Bun version (#13402 item 2 + item 5). Keeps the
* frozen-lockfile install path on ONE concrete Bun version so a stale bump or a
* regression back to floating `canary`/`latest` cannot slip through unnoticed.
*
* Background: `bun install --frozen-lockfile` fails when Bun reserializes
* bun.lock to lockfileVersion 2, which floating `canary`/`latest` do on their
* own cadence (#11184/#9454); the repo's packageManager `bun@1.4.0-canary.1` is
* also unresolvable in CI. So the required test/build gates pin a concrete Bun
* version rather than tracking the moving channel. The canonical value lives in
* `.github/ci-bun-version.json` — GitHub Actions cannot interpolate a file into
* `${{ }}` at parse time, so the literal is repeated in each workflow and this
* contract is what guarantees those copies never drift from the source of truth.
*
* Two invariants, checked statically against the checked-in YAML (no workflow is
* executed):
*
* 1. No divergent concrete pin. Every `bun-version:`/`BUN_VERSION:` value in
* `.github/workflows` that is a concrete version (a semver, not a `${{ }}`
* expression and not floating `canary`/`latest`) must equal the canonical
* version. This catches a second concrete pin drifting away from the rest.
*
* 2. The frozen-lockfile gate workflows stay pinned. Each workflow in
* GATE_WORKFLOWS — the required/scheduled lanes that run a frozen install —
* must wire the canonical pin (as a `BUN_VERSION`/`bun-version` literal) and
* must NOT wire floating `canary`/`latest`. This catches a regression that
* unpins a gate back onto the moving channel.
*
* Intentionally silent about non-gate workflows that track `canary`/`latest` on
* purpose (benchmarks, some release/build lanes). Narrowing those is a separate
* judgement call under #13402 item 2; this contract only locks the concrete-pin
* source of truth and the frozen-install gates.
*/
import { readdirSync, readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const DEFAULT_REPO_ROOT = resolve(
dirname(fileURLToPath(import.meta.url)),
"..",
"..",
);
const VERSION_FILE = ".github/ci-bun-version.json";
const WORKFLOW_DIR = ".github/workflows";
// The frozen-lockfile install lanes that must stay on the concrete pin. Each
// documents the `--frozen-lockfile` rationale inline; the required `ci-ok`
// aggregate (test.yml) and the main gate (ci.yaml) are the load-bearing two.
const GATE_WORKFLOWS = [
"ci.yaml",
"test.yml",
"develop-exhaustive.yml",
"ci-full-matrix-proof.yml",
"benchmark-tests.yml",
"windows-desktop-preload-smoke.yml",
"feed-env-audit.yml",
];
// A concrete pin: a plain semver, optionally with a prerelease/build suffix.
// `canary`, `latest`, and `${{ ... }}` expressions deliberately do not match.
const CONCRETE_PIN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)*$/;
const FLOATING = new Set(["canary", "latest"]);
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
// Extract every `bun-version:`/`BUN_VERSION:` value wired in a workflow, as
// `{ key, raw }` with the inline comment and surrounding quotes stripped. Only
// real YAML key wiring counts — a version named in a `#` comment (e.g. the
// unresolvable `bun@1.4.0-canary.1` rationale) is never treated as a pin.
function bunVersionValues(text) {
const out = [];
for (const line of text.split("\n")) {
const match = line.match(
/^\s*(?:-\s+)?(bun-version|BUN_VERSION):\s*(.+?)\s*$/,
);
if (!match) continue;
let raw = match[2].replace(/\s+#.*$/, "").trim();
if (raw.startsWith('"') || raw.startsWith("'")) {
raw = raw.slice(1, -1);
}
out.push({ key: match[1], raw });
}
return out;
}
function isExpression(raw) {
return raw.includes("${{");
}
// Validate both invariants against a repo layout rooted at `repoRoot`. Pure
// (no process exit / no console) so tests can drive it against fixture trees;
// throws on the first violation, returns the canonical version and the scanned
// concrete-pin sites on success.
export function runContract(repoRoot = DEFAULT_REPO_ROOT) {
const read = (rel) => readFileSync(resolve(repoRoot, rel), "utf8");
const manifest = JSON.parse(read(VERSION_FILE));
const canonical = manifest.version;
assert(
typeof canonical === "string" && CONCRETE_PIN.test(canonical),
`${VERSION_FILE}: "version" must be a concrete Bun pin (semver), got ${JSON.stringify(canonical)}`,
);
assert(
!FLOATING.has(canonical),
`${VERSION_FILE}: "version" must not float (${canonical})`,
);
const workflowFiles = readdirSync(resolve(repoRoot, WORKFLOW_DIR)).filter(
(name) => name.endsWith(".yml") || name.endsWith(".yaml"),
);
// --- Invariant 1: no concrete pin diverges from the source of truth. ---
const concretePins = [];
for (const name of workflowFiles) {
const rel = join(WORKFLOW_DIR, name);
for (const { raw } of bunVersionValues(read(rel))) {
if (isExpression(raw) || FLOATING.has(raw) || !CONCRETE_PIN.test(raw)) {
continue;
}
assert(
raw === canonical,
`${rel}: pins Bun ${raw}, but the canonical CI Bun version is ${canonical} ` +
`(${VERSION_FILE}). Update this workflow or bump the source of truth — keep them in lockstep.`,
);
concretePins.push({ workflow: rel, version: raw });
}
}
// --- Invariant 2: the frozen-install gate lanes stay pinned. ---
for (const name of GATE_WORKFLOWS) {
const rel = join(WORKFLOW_DIR, name);
const text = read(rel);
const values = bunVersionValues(text);
const floats = values.find(
(v) => !isExpression(v.raw) && FLOATING.has(v.raw),
);
assert(
floats === undefined,
`${rel}: is a frozen-lockfile gate but wires floating Bun "${floats?.raw}". ` +
`It must stay pinned to ${canonical} (${VERSION_FILE}) so --frozen-lockfile does not break.`,
);
const pinsCanonical = values.some((v) => v.raw === canonical);
assert(
pinsCanonical,
`${rel}: is a frozen-lockfile gate but does not wire the canonical Bun pin ${canonical} ` +
`(${VERSION_FILE}). Expected a BUN_VERSION/bun-version: "${canonical}" literal.`,
);
}
return { canonical, concretePins, gateWorkflows: GATE_WORKFLOWS };
}
if (import.meta.url === `file://${process.argv[1]}`) {
try {
const { canonical, concretePins, gateWorkflows } = runContract();
console.log(
`ci bun version contract passed (canonical ${canonical}; ` +
`${concretePins.length} concrete pin(s) in lockstep; ${gateWorkflows.length} gate lane(s) pinned)`,
);
} catch (error) {
console.error(`[ci-bun-version-contract] FAIL ${error.message}`);
process.exit(1);
}
}