Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ steps.source.outputs.source_sha }}
path: .release-source
persist-credentials: false

- name: Install Node.js
Expand All @@ -80,7 +81,7 @@ jobs:
node-version: 24.13.1

- name: Verify tag, packages, clients, docs, and downloads agree
run: node scripts/check-release-consistency.mjs --tag "$RELEASE_TAG"
run: node scripts/check-release-consistency.mjs --tag "$RELEASE_TAG" --repo-root .release-source

ci-authority:
needs: verify
Expand Down
35 changes: 27 additions & 8 deletions scripts/check-release-consistency.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1419,27 +1419,46 @@ export function checkReleaseConsistency(repoRoot, releaseTag) {
return collectReleaseConsistencyErrors(loadReleaseContractFiles(repoRoot), releaseTag);
}

function parseTagArgument(args) {
if (args.length === 0) return undefined;
if (args.length === 2 && args[0] === "--tag" && args[1]) return args[1];
throw new Error("usage: node scripts/check-release-consistency.mjs [--tag vX.Y.Z]");
export function parseCliArguments(args, cwd = process.cwd()) {
let releaseTag;
let repoRoot = resolve(cwd);
let repoRootProvided = false;
for (let index = 0; index < args.length; index += 2) {
const option = args[index];
const value = args[index + 1];
if (!value) {
throw new Error(
"usage: node scripts/check-release-consistency.mjs [--tag vX.Y.Z] [--repo-root path]",
);
}
if (option === "--tag" && releaseTag === undefined) {
releaseTag = value;
} else if (option === "--repo-root" && !repoRootProvided) {
repoRoot = resolve(cwd, value);
repoRootProvided = true;
} else {
throw new Error(
"usage: node scripts/check-release-consistency.mjs [--tag vX.Y.Z] [--repo-root path]",
);
}
}
return { releaseTag, repoRoot };
}

const isMain =
process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
if (isMain) {
try {
const errors = checkReleaseConsistency(process.cwd(), parseTagArgument(process.argv.slice(2)));
const { releaseTag, repoRoot } = parseCliArguments(process.argv.slice(2));
const errors = checkReleaseConsistency(repoRoot, releaseTag);
if (errors.length > 0) {
console.error(
`Release consistency check failed with ${errors.length} error${errors.length === 1 ? "" : "s"}:`,
);
for (const error of errors) console.error(`- ${error}`);
process.exitCode = 1;
} else {
const version = JSON.parse(
readFileSync(resolve(process.cwd(), "package.json"), "utf8"),
).version;
const version = JSON.parse(readFileSync(resolve(repoRoot, "package.json"), "utf8")).version;
console.log(`Release consistency check passed for v${version}.`);
}
} catch (error) {
Expand Down
23 changes: 23 additions & 0 deletions scripts/check-release-consistency.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
collectReleaseConsistencyErrors,
discoverReleasePackagePaths,
loadReleaseContractFiles,
parseCliArguments,
} from "./check-release-consistency.mjs";

const repoRoot = resolve(import.meta.dirname, "..");
Expand Down Expand Up @@ -435,6 +436,28 @@ test("historical repair runs CI authority from trusted control while querying ol
assert.notEqual(checkoutSha, queriedSha);
});

test("historical repair keeps trusted verification code separate from immutable source", () => {
const verifyJob = requiredWorkflowJob(files.get(".github/workflows/release.yml"), "verify");
const controlCheckout = requiredNamedStep(verifyJob, "Check out trusted release-control source");
const sourceCheckout = requiredNamedStep(verifyJob, "Check out immutable release source");
const consistencyStep = requiredNamedStep(
verifyJob,
"Verify tag, packages, clients, docs, and downloads agree",
);

assert.equal(controlCheckout.with.ref, "${{ github.sha }}");
assert.equal(sourceCheckout.with.ref, "${{ steps.source.outputs.source_sha }}");
assert.equal(sourceCheckout.with.path, ".release-source");
assert.equal(
consistencyStep.run,
'node scripts/check-release-consistency.mjs --tag "$RELEASE_TAG" --repo-root .release-source',
);
assert.deepEqual(parseCliArguments(["--tag", "v0.1.31", "--repo-root", ".release-source"]), {
releaseTag: "v0.1.31",
repoRoot: resolve(".release-source"),
});
});

test("rejects published app-wire version drift until release surfaces agree", () => {
const drifted = changedRuntime("publishedAppWire", (record) => {
record.version = "0.5.1";
Expand Down
Loading