Skip to content
Open
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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,40 @@ jobs:
# archive rather than running package lifecycle code with publication rights.
# The stamp is never committed: the tag allocated after acceptance is the
# immutable version record.

# The Windows half of the promise: run the pure-Node surfaces — the CLI
# suite and the guards — on windows-latest, so the Windows-only defect
# class (#241 has the inventory) fails loudly in CI instead of on a
# contributor's machine.
verify-windows:
runs-on: windows-latest
timeout-minutes: 20
permissions:
contents: read
steps:
# The hosted Windows images default core.autocrlf=true; a CRLF checkout
# blinds the newline-anchored template assertions (found on this job's
# first real run). Check out with LF, as every other environment sees it.
- run: git config --global core.autocrlf input

- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: CLI test suite on Windows
working-directory: packages/cli
run: node --test test/*.test.mjs

- name: Guards on Windows
run: node guards/run.mjs

package-release:
needs: [decide-release, verify]
if: needs.decide-release.outputs.release == 'true' && github.event.repository.visibility == 'public'
Expand Down
233 changes: 124 additions & 109 deletions packages/cli/templates/delivery/verify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,131 +3,141 @@
import { execFileSync } from "node:child_process";
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { pathToFileURL } from "node:url";

const mode = process.argv[2];
const repo = required("GITHUB_REPOSITORY");
const defaultBranch = required("DEFAULT_BRANCH");
const receiptDir = join(required("RUNNER_TEMP"), "facility-delivery");
const receiptPath = join(receiptDir, "receipt.json");
const conventionalSubject =
/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\((?=\S)[^()\r\n]*[^\s()\r\n]\))?!?: \S.*$/;

if (mode === "discover") discover();
else if (mode === "finalize") finalize();
else throw new Error("Usage: verify.mjs discover|finalize");

function discover() {
const event = JSON.parse(readFileSync(required("GITHUB_EVENT_PATH"), "utf8"));
const startedAt = required("FACILITY_STARTED_AT");
const startSha = required("FACILITY_START_SHA");
const existingPr =
event.pull_request?.number ?? (event.issue?.pull_request ? event.issue.number : undefined);
const pr = existingPr ? pull(existingPr) : newlyOpenedPull(startedAt);

assert(pr.state === "open", `PR #${pr.number} is not open`);
assert(!pr.draft, `PR #${pr.number} is still a draft; automated review would not run`);
assert(
pr.base?.ref === defaultBranch,
`PR #${pr.number} targets ${pr.base?.ref}, not ${defaultBranch}`,
);
assert(pr.head?.repo?.full_name === repo, `PR #${pr.number} is not a same-repository PR`);
assert(
pr.user?.type === "Bot",
`PR #${pr.number} is not bot-authored; address-review would be disabled`,
);
assert(
/^(feature|fix|chore|ci|docs|refactor|perf|test|build|revert)\/[a-z0-9][a-z0-9._/-]*$/.test(
pr.head.ref,
),
`branch ${pr.head?.ref} is not semantic or still carries an agent/tool prefix`,
);
assert(pr.head?.sha !== startSha, "builder reported success without delivering a new commit");

const commits = ghJson(["api", `repos/${repo}/pulls/${pr.number}/commits?per_page=100`]);
const startIndex = commits.findIndex((commit) => commit.sha === startSha);
if (existingPr) {
// The GitHub runner is injectable ONLY through this exported entry point:
// the test seam lives at the module boundary, and the production script
// path always constructs the fixed executable below. Nothing about the gh
// invocation is readable from the ambient environment, so an earlier
// workflow step persisting variables through $GITHUB_ENV cannot redirect
// this verifier while it holds GH_TOKEN.
export function runDelivery(mode, { gh = defaultGh } = {}) {
const repo = required("GITHUB_REPOSITORY");
const defaultBranch = required("DEFAULT_BRANCH");
const receiptDir = join(required("RUNNER_TEMP"), "facility-delivery");
const receiptPath = join(receiptDir, "receipt.json");
const ghJson = (args) => JSON.parse(gh(args));

if (mode === "discover") discover();
else if (mode === "finalize") finalize();
else throw new Error("Usage: verify.mjs discover|finalize");

function discover() {
const event = JSON.parse(readFileSync(required("GITHUB_EVENT_PATH"), "utf8"));
const startedAt = required("FACILITY_STARTED_AT");
const startSha = required("FACILITY_START_SHA");
const existingPr =
event.pull_request?.number ?? (event.issue?.pull_request ? event.issue.number : undefined);
const pr = existingPr ? pull(existingPr) : newlyOpenedPull(startedAt);

assert(pr.state === "open", `PR #${pr.number} is not open`);
assert(!pr.draft, `PR #${pr.number} is still a draft; automated review would not run`);
assert(
startIndex >= 0,
`PR #${pr.number} no longer contains its pre-builder head; history was rewritten`,
);
}
const delivered = startIndex >= 0 ? commits.slice(startIndex + 1) : commits;
assert(delivered.length > 0, `PR #${pr.number} contains no builder-delivered commits`);

for (const commit of delivered) {
const message = commit.commit?.message ?? "";
const subject = message.split(/\r?\n/, 1)[0];
assert(
!hasForbiddenSubjectCharacters(subject) && conventionalSubject.test(subject),
`commit ${commit.sha} is not Conventional Commits: ${subject}`,
pr.base?.ref === defaultBranch,
`PR #${pr.number} targets ${pr.base?.ref}, not ${defaultBranch}`,
);
assert(pr.head?.repo?.full_name === repo, `PR #${pr.number} is not a same-repository PR`);
assert(
!/^Co-authored-by:/im.test(message),
`commit ${commit.sha} contains a Co-authored-by trailer`,
pr.user?.type === "Bot",
`PR #${pr.number} is not bot-authored; address-review would be disabled`,
);
assert(
commit.commit?.verification?.verified === true,
`commit ${commit.sha} is not verified by GitHub`,
/^(feature|fix|chore|ci|docs|refactor|perf|test|build|revert)\/[a-z0-9][a-z0-9._/-]*$/.test(
pr.head.ref,
),
`branch ${pr.head?.ref} is not semantic or still carries an agent/tool prefix`,
);
assert(pr.head?.sha !== startSha, "builder reported success without delivering a new commit");

const commits = ghJson(["api", `repos/${repo}/pulls/${pr.number}/commits?per_page=100`]);
const startIndex = commits.findIndex((commit) => commit.sha === startSha);
if (existingPr) {
assert(
startIndex >= 0,
`PR #${pr.number} no longer contains its pre-builder head; history was rewritten`,
);
}
const delivered = startIndex >= 0 ? commits.slice(startIndex + 1) : commits;
assert(delivered.length > 0, `PR #${pr.number} contains no builder-delivered commits`);

for (const commit of delivered) {
const message = commit.commit?.message ?? "";
const subject = message.split(/\r?\n/, 1)[0];
assert(
!hasForbiddenSubjectCharacters(subject) && conventionalSubject.test(subject),
`commit ${commit.sha} is not Conventional Commits: ${subject}`,
);
assert(
!/^Co-authored-by:/im.test(message),
`commit ${commit.sha} contains a Co-authored-by trailer`,
);
assert(
commit.commit?.verification?.verified === true,
`commit ${commit.sha} is not verified by GitHub`,
);
}

mkdirSync(receiptDir, { recursive: true });
const receipt = {
schema: "facility.delivery.v1",
repository: repo,
pullRequest: { number: pr.number, url: pr.html_url, base: pr.base.ref },
branch: pr.head.ref,
headSha: pr.head.sha,
author: pr.user.login,
deliveredCommits: delivered.map((commit) => commit.sha),
startedAt,
verification: "pending",
};
writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
output("pr_number", String(pr.number));
output("head_ref", pr.head.ref);
output("head_sha", pr.head.sha);
console.log(`Validated delivery metadata for ${pr.html_url} at ${pr.head.sha}.`);
}

mkdirSync(receiptDir, { recursive: true });
const receipt = {
schema: "facility.delivery.v1",
repository: repo,
pullRequest: { number: pr.number, url: pr.html_url, base: pr.base.ref },
branch: pr.head.ref,
headSha: pr.head.sha,
author: pr.user.login,
deliveredCommits: delivered.map((commit) => commit.sha),
startedAt,
verification: "pending",
};
writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
output("pr_number", String(pr.number));
output("head_ref", pr.head.ref);
output("head_sha", pr.head.sha);
console.log(`Validated delivery metadata for ${pr.html_url} at ${pr.head.sha}.`);
}

function finalize() {
const prNumber = required("FACILITY_PR_NUMBER");
const headRef = required("FACILITY_HEAD_REF");
const headSha = required("FACILITY_HEAD_SHA");
const pr = pull(prNumber);
assert(pr.head?.ref === headRef, `PR #${prNumber} head branch changed during verification`);
assert(pr.head?.sha === headSha, `PR #${prNumber} head commit changed during verification`);

const receipt = JSON.parse(readFileSync(receiptPath, "utf8"));
receipt.verification = "passed";
receipt.verifiedAt = new Date().toISOString();
writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
console.log(JSON.stringify(receipt));
}
function finalize() {
const prNumber = required("FACILITY_PR_NUMBER");
const headRef = required("FACILITY_HEAD_REF");
const headSha = required("FACILITY_HEAD_SHA");
const pr = pull(prNumber);
assert(pr.head?.ref === headRef, `PR #${prNumber} head branch changed during verification`);
assert(pr.head?.sha === headSha, `PR #${prNumber} head commit changed during verification`);

const receipt = JSON.parse(readFileSync(receiptPath, "utf8"));
receipt.verification = "passed";
receipt.verifiedAt = new Date().toISOString();
writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
console.log(JSON.stringify(receipt));
}

function newlyOpenedPull(startedAt) {
const pulls = ghJson([
"api",
`repos/${repo}/pulls?state=open&base=${encodeURIComponent(defaultBranch)}&sort=created&direction=desc&per_page=50`,
]).filter(
(pr) =>
pr.head?.repo?.full_name === repo &&
pr.created_at >= startedAt &&
pr.head?.sha !== process.env.FACILITY_START_SHA,
);
assert(
pulls.length === 1,
`expected exactly one new PR from this builder run; found ${pulls.length}`,
);
return pulls[0];
}
function newlyOpenedPull(startedAt) {
const pulls = ghJson([
"api",
`repos/${repo}/pulls?state=open&base=${encodeURIComponent(defaultBranch)}&sort=created&direction=desc&per_page=50`,
]).filter(
(pr) =>
pr.head?.repo?.full_name === repo &&
pr.created_at >= startedAt &&
pr.head?.sha !== process.env.FACILITY_START_SHA,
);
assert(
pulls.length === 1,
`expected exactly one new PR from this builder run; found ${pulls.length}`,
);
return pulls[0];
}

function pull(number) {
return ghJson(["api", `repos/${repo}/pulls/${number}`]);
function pull(number) {
return ghJson(["api", `repos/${repo}/pulls/${number}`]);
}
}

function ghJson(args) {
return JSON.parse(execFileSync("gh", args, { encoding: "utf8" }));
function defaultGh(args) {
return execFileSync("gh", args, { encoding: "utf8" });
}

function output(name, value) {
Expand Down Expand Up @@ -155,3 +165,8 @@ function required(name) {
function assert(condition, message) {
if (!condition) throw new Error(message);
}

const invoked = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
if (import.meta.url === invoked) {
runDelivery(process.argv[2]);
}
14 changes: 11 additions & 3 deletions packages/cli/templates/doctor/resolve.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// resolver has proved that the PR head is current, every check is terminal,
// the failure is low risk, and the bounded retry budget remains.
import { execFileSync } from "node:child_process";

import { createHash } from "node:crypto";
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { pathToFileURL } from "node:url";
Expand Down Expand Up @@ -341,7 +342,13 @@ export async function resolveDoctor({
});
}

async function main() {
// The GitHub runner is injectable ONLY through this exported entry point:
// the test seam lives at the module boundary, and the production script
// path always constructs the fixed executable below. Nothing about the gh
// invocation is readable from the ambient environment, so an earlier
// workflow step persisting variables through $GITHUB_ENV cannot redirect
// this verifier while it holds GH_TOKEN.
export async function main({ gh: injectedGh } = {}) {
const outputPath = process.env.GITHUB_OUTPUT;
const output = (key, value) => {
if (outputPath) appendFileSync(outputPath, `${key}=${String(value).replaceAll("\n", " ")}\n`);
Expand All @@ -351,8 +358,9 @@ async function main() {
try {
const repository = requiredEnv("GITHUB_REPOSITORY");
const event = JSON.parse(readFileSync(requiredEnv("GITHUB_EVENT_PATH"), "utf8"));
const gh = async (args) =>
execFileSync("gh", args, { encoding: "utf8", maxBuffer: 20 * 1024 * 1024 });
const gh =
injectedGh ??
(async (args) => execFileSync("gh", args, { encoding: "utf8", maxBuffer: 20 * 1024 * 1024 }));
decision = await resolveDoctor({
repository,
event,
Expand Down
Loading
Loading