feat: add gh pr preflight helper
This commit is contained in:
@@ -239,6 +239,8 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
|
||||
assertCondition(usage.some((line) => line.includes("gh pr read")), "gh help should list pr read", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh pr view")), "gh help should list pr view", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh pr read") && line.includes("owner/repo#number") && line.includes("--raw|--full")), "gh help should document pr shorthand and raw/full disclosure", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh preflight")), "gh help should list top-level preflight alias", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh pr preflight")), "gh help should list pr preflight", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh pr create")), "gh help should list pr create", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh pr edit")), "gh help should list pr edit", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("gh pr comment")), "gh help should list pr comment", { usage });
|
||||
@@ -250,6 +252,7 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
|
||||
assertCondition(notes.some((line) => line.includes("--raw and --full are explicit full-disclosure aliases")), "gh help should explain raw/full read disclosure", { notes });
|
||||
assertCondition(notes.some((line) => line.includes("PR list defaults to --state all")), "gh help should document pr list default state", { notes });
|
||||
assertCondition(notes.some((line) => line.includes("stateDetail") && line.includes("mergedAt")), "gh help should describe closeout field normalization", { notes });
|
||||
assertCondition(notes.some((line) => line.includes("low-noise read-only closeout helper")), "gh help should document PR preflight closeout helper", { notes });
|
||||
|
||||
const mock = await startMockGitHub();
|
||||
const env = {
|
||||
@@ -367,6 +370,41 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
|
||||
assertCondition(unsupportedReadMessage.includes("projectCards"), "unsupported pr read field message should name the bad field", unsupportedReadFieldData);
|
||||
assertCondition(unsupportedReadMessage.includes("mergedAt") && unsupportedReadMessage.includes("mergeCommit"), "unsupported pr read field message should include supported closeout fields", unsupportedReadFieldData);
|
||||
|
||||
const closeoutPreflight = await runCli(["gh", "pr", "preflight", "42", "--repo", "pikasTech/unidesk"], env);
|
||||
assertCondition(closeoutPreflight.status === 0, "pr preflight should succeed through REST and GraphQL", closeoutPreflight.json ?? { stdout: closeoutPreflight.stdout });
|
||||
assertCondition(!closeoutPreflight.stdout.includes("contract-token"), "pr preflight must not print token values", { stdout: closeoutPreflight.stdout });
|
||||
const closeoutPreflightData = dataOf(closeoutPreflight.json ?? {});
|
||||
assertCondition(closeoutPreflightData.command === "pr preflight", "pr preflight should report command", closeoutPreflightData);
|
||||
assertCondition(closeoutPreflightData.readOnly === true && closeoutPreflightData.writesRemote === false, "pr preflight must stay read-only", closeoutPreflightData);
|
||||
assertCondition(!("raw" in closeoutPreflightData), "pr preflight default output should omit raw payloads", closeoutPreflightData);
|
||||
const authCapability = closeoutPreflightData.authCapability as JsonRecord;
|
||||
assertCondition(authCapability.ok === true && authCapability.tokenPresent === true && authCapability.tokenSource === "GH_TOKEN", "pr preflight should expose redacted auth capability", authCapability);
|
||||
assertCondition(authCapability.valuesPrinted === false, "pr preflight should explicitly avoid secret values", authCapability);
|
||||
const preflightPr = closeoutPreflightData.pullRequest as JsonRecord;
|
||||
assertCondition(preflightPr.number === 42 && preflightPr.bodyOmitted === true, "pr preflight should return bounded PR metadata", preflightPr);
|
||||
const mergeability = closeoutPreflightData.mergeability as JsonRecord;
|
||||
assertCondition(mergeability.mergeable === "MERGEABLE" && mergeability.mergeStateStatus === "CLEAN", "pr preflight should expose mergeability", mergeability);
|
||||
assertCondition(mergeability.readyForCommanderMerge === true && mergeability.conclusion === "ready", "pr preflight should summarize closeout readiness", mergeability);
|
||||
const preflightStatus = closeoutPreflightData.statusChecks as JsonRecord;
|
||||
const preflightCounts = preflightStatus.counts as JsonRecord;
|
||||
assertCondition(preflightStatus.state === "SUCCESS" && preflightStatus.rawOmitted === true, "pr preflight default status rollup should be compact", preflightStatus);
|
||||
assertCondition(preflightCounts.success === 2, "pr preflight should count successful contexts", preflightStatus);
|
||||
const policy = closeoutPreflightData.policy as JsonRecord;
|
||||
assertCondition(policy.mergesPr === false && policy.mergeCommandSupported === false && policy.unideskCliMergeSupported === false, "pr preflight policy should block UniDesk CLI merge execution", policy);
|
||||
|
||||
const aliasPreflight = await runCli(["gh", "preflight", "42", "--repo", "pikasTech/unidesk"], env);
|
||||
assertCondition(aliasPreflight.status === 0, "top-level gh preflight alias should succeed", aliasPreflight.json ?? { stdout: aliasPreflight.stdout });
|
||||
const aliasPreflightData = dataOf(aliasPreflight.json ?? {});
|
||||
assertCondition(aliasPreflightData.command === "preflight", "top-level gh preflight should report alias command", aliasPreflightData);
|
||||
assertCondition((aliasPreflightData.policy as JsonRecord).mergesPr === false, "top-level gh preflight alias must not merge", aliasPreflightData);
|
||||
|
||||
const fullPreflight = await runCli(["gh", "pr", "preflight", "42", "--repo", "pikasTech/unidesk", "--full"], env);
|
||||
assertCondition(fullPreflight.status === 0, "pr preflight --full should succeed", fullPreflight.json ?? { stdout: fullPreflight.stdout });
|
||||
const fullPreflightData = dataOf(fullPreflight.json ?? {});
|
||||
const fullStatus = fullPreflightData.statusChecks as JsonRecord;
|
||||
assertCondition(fullStatus.rawOmitted === false && Array.isArray(fullStatus.contexts), "pr preflight --full should include status contexts", fullStatus);
|
||||
assertCondition(typeof fullPreflightData.raw === "object" && fullPreflightData.raw !== null, "pr preflight --full should include raw read payload summary", fullPreflightData);
|
||||
|
||||
const preflight = await runBun([
|
||||
"scripts/code-queue-pr-preflight-example.ts",
|
||||
"--repo",
|
||||
@@ -558,6 +596,9 @@ export async function runGhCliPrContract(): Promise<JsonRecord> {
|
||||
"GitHub DNS/API transients are retryable and distinct from auth or PR semantic failures",
|
||||
"pr view closeout metadata fields are accepted and hydrated through GraphQL",
|
||||
"pr read unsupported fields fail structurally with supported closeout fields listed",
|
||||
"pr preflight exposes redacted auth plus compact merge/status closeout metadata",
|
||||
"top-level gh preflight alias works for commander closeout",
|
||||
"pr preflight --full is the explicit status-context disclosure path",
|
||||
"pr create dry-run exposes planned operation",
|
||||
"pr comment dry-run preserves markdown text",
|
||||
"pr update/edit use low-noise REST PATCH without GraphQL projectCards",
|
||||
|
||||
Reference in New Issue
Block a user