@mutmutco/cli 4.3.24 → 4.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +299 -385
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -14074,7 +14074,7 @@ var GATE_RUNTIME_DEFAULTS = {
|
|
|
14074
14074
|
node: { cmd: DEFAULT_GATE_CMD, install: "npm ci" },
|
|
14075
14075
|
python: { cmd: "pytest", install: 'pip install -e ".[dev]"' }
|
|
14076
14076
|
};
|
|
14077
|
-
function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches) {
|
|
14077
|
+
function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches, requiredChecks) {
|
|
14078
14078
|
const rt = GATE_RUNTIME_DEFAULTS[runtime] ?? GATE_RUNTIME_DEFAULTS.node;
|
|
14079
14079
|
const runtimeVars = {
|
|
14080
14080
|
GATE_RUNTIME: runtime,
|
|
@@ -14094,12 +14094,14 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
14094
14094
|
const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
|
|
14095
14095
|
const rulesetBranches = requiredCheckBranches?.length ? [...requiredCheckBranches] : trackBranches;
|
|
14096
14096
|
const rulesetRefs = JSON.stringify(rulesetBranches.map((branch) => `refs/heads/${branch}`));
|
|
14097
|
+
const rulesetContexts = JSON.stringify((requiredChecks?.length ? requiredChecks : ["gate"]).map((context) => ({ context })));
|
|
14097
14098
|
if (track === "trunk") {
|
|
14098
14099
|
return {
|
|
14099
14100
|
...runtimeVars,
|
|
14100
14101
|
GATE_PUSH_BRANCHES_YAML: "[main]",
|
|
14101
14102
|
GATE_FULL_RUN_BRANCH: "main",
|
|
14102
|
-
GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
|
|
14103
|
+
GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs,
|
|
14104
|
+
GATE_RULESET_CONTEXTS_JSON: rulesetContexts
|
|
14103
14105
|
};
|
|
14104
14106
|
}
|
|
14105
14107
|
if (track === "direct") {
|
|
@@ -14107,17 +14109,19 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
14107
14109
|
...runtimeVars,
|
|
14108
14110
|
GATE_PUSH_BRANCHES_YAML: "[development, main]",
|
|
14109
14111
|
GATE_FULL_RUN_BRANCH: "development",
|
|
14110
|
-
GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
|
|
14112
|
+
GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs,
|
|
14113
|
+
GATE_RULESET_CONTEXTS_JSON: rulesetContexts
|
|
14111
14114
|
};
|
|
14112
14115
|
}
|
|
14113
14116
|
return {
|
|
14114
14117
|
...runtimeVars,
|
|
14115
14118
|
GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
|
|
14116
14119
|
GATE_FULL_RUN_BRANCH: "development",
|
|
14117
|
-
GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
|
|
14120
|
+
GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs,
|
|
14121
|
+
GATE_RULESET_CONTEXTS_JSON: rulesetContexts
|
|
14118
14122
|
};
|
|
14119
14123
|
}
|
|
14120
|
-
function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranches) {
|
|
14124
|
+
function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranches, requiredChecks) {
|
|
14121
14125
|
const out = { ...vars };
|
|
14122
14126
|
out.REPO_NAME ??= parsed.name;
|
|
14123
14127
|
out.REPO_SLUG ??= parsed.slug;
|
|
@@ -14125,7 +14129,7 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
|
|
|
14125
14129
|
out.PROJECT_OWNER ??= parsed.owner;
|
|
14126
14130
|
const track = releaseTrack ?? resolveBootstrapReleaseTrack(cls);
|
|
14127
14131
|
const runtime = out.GATE_RUNTIME === "python" ? "python" : "node";
|
|
14128
|
-
for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches))) {
|
|
14132
|
+
for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches, requiredChecks))) {
|
|
14129
14133
|
out[key] ??= value;
|
|
14130
14134
|
}
|
|
14131
14135
|
if (!out.GATE_AFFECTED_CMD.trim()) out.GATE_AFFECTED_CMD = out.GATE_CMD;
|
|
@@ -15522,10 +15526,10 @@ var rollout_plan_default = {
|
|
|
15522
15526
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
15523
15527
|
},
|
|
15524
15528
|
baseline: {
|
|
15525
|
-
version: "4.3.
|
|
15526
|
-
tag: "v4.3.
|
|
15527
|
-
commit: "
|
|
15528
|
-
npm: "@mutmutco/cli@4.3.
|
|
15529
|
+
version: "4.3.26",
|
|
15530
|
+
tag: "v4.3.26",
|
|
15531
|
+
commit: "32a20f524697",
|
|
15532
|
+
npm: "@mutmutco/cli@4.3.26"
|
|
15529
15533
|
},
|
|
15530
15534
|
exitCriterion: "fleet-n-of-n",
|
|
15531
15535
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15542,14 +15546,14 @@ var rollout_plan_default = {
|
|
|
15542
15546
|
repo: "mutmutco/mmi-hub",
|
|
15543
15547
|
role: "canary",
|
|
15544
15548
|
schedule: "train",
|
|
15545
|
-
v3Target: "v4.3.
|
|
15549
|
+
v3Target: "v4.3.26"
|
|
15546
15550
|
}
|
|
15547
15551
|
],
|
|
15548
15552
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
15549
15553
|
rollback: {
|
|
15550
15554
|
independent: true,
|
|
15551
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15552
|
-
v3Target: "v4.3.
|
|
15555
|
+
mechanism: "npm dist-tag latest -> 4.3.26 and redeploy the Hub Lambda from tag v4.3.26 (32a20f524697); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15556
|
+
v3Target: "v4.3.26 (@mutmutco/cli@4.3.26, tag commit 32a20f524697 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15553
15557
|
}
|
|
15554
15558
|
},
|
|
15555
15559
|
{
|
|
@@ -19141,6 +19145,7 @@ Resume it: ${resumeCommand}
|
|
|
19141
19145
|
}
|
|
19142
19146
|
|
|
19143
19147
|
// src/train-doctor.ts
|
|
19148
|
+
var import_node_child_process9 = require("node:child_process");
|
|
19144
19149
|
var import_node_fs23 = require("node:fs");
|
|
19145
19150
|
var import_node_os13 = require("node:os");
|
|
19146
19151
|
var import_node_path22 = require("node:path");
|
|
@@ -21232,19 +21237,18 @@ var TRAIN_LANES = ["release", "rcand", "hotfix"];
|
|
|
21232
21237
|
var TROUBLESHOOTING_GUIDE = "docs/Guides/train-troubleshooting.md";
|
|
21233
21238
|
var SCRATCH_BRANCH_GLOBS = ["train/check/*", "hotfix-fold/*--port-*"];
|
|
21234
21239
|
var COMPOSE_GUARD_HARD_FAIL = /^secrets preflight: .*noEnvFile is (not )?true.*$/m;
|
|
21235
|
-
function
|
|
21236
|
-
const
|
|
21237
|
-
let
|
|
21240
|
+
function readOriginWorkflowsDefault(root, ref) {
|
|
21241
|
+
const git3 = (args) => (0, import_node_child_process9.execFileSync)("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
21242
|
+
let paths;
|
|
21238
21243
|
try {
|
|
21239
|
-
|
|
21240
|
-
} catch
|
|
21241
|
-
if (e.code === "ENOENT") return [];
|
|
21244
|
+
paths = git3(["ls-tree", "--name-only", ref, ".github/workflows/"]).split("\n").map((l) => l.trim()).filter((l) => /\.ya?ml$/i.test(l));
|
|
21245
|
+
} catch {
|
|
21242
21246
|
return null;
|
|
21243
21247
|
}
|
|
21244
21248
|
const files = [];
|
|
21245
|
-
for (const
|
|
21249
|
+
for (const path2 of paths) {
|
|
21246
21250
|
try {
|
|
21247
|
-
files.push({ path:
|
|
21251
|
+
files.push({ path: path2, body: git3(["show", `${ref}:${path2}`]) });
|
|
21248
21252
|
} catch {
|
|
21249
21253
|
}
|
|
21250
21254
|
}
|
|
@@ -21280,7 +21284,7 @@ var defaultDeps2 = {
|
|
|
21280
21284
|
selfConverge: selfConvergeTrainCli,
|
|
21281
21285
|
readGitignore,
|
|
21282
21286
|
writeGitignore,
|
|
21283
|
-
readWorkflows:
|
|
21287
|
+
readWorkflows: readOriginWorkflowsDefault,
|
|
21284
21288
|
ledgerPath: releaseLedgerPath,
|
|
21285
21289
|
readLedgerRaw: (path2) => {
|
|
21286
21290
|
try {
|
|
@@ -21491,9 +21495,10 @@ async function runTrainDoctor(input) {
|
|
|
21491
21495
|
add(linked ? { code: "worktree-isolated", severity: "blocker", source: "local", title: `this is a linked worktree on ${currentBranch2 || "(detached)"}, not the lane's start branch ${startBranch} (#2770)`, remedy: `run the ${lane} lane from the primary checkout on ${startBranch}; a linked worktree never carries the train` } : { code: "branch-mismatch", severity: "blocker", source: "local", title: `on ${currentBranch2 || "(detached)"}; the ${lane} lane starts from ${startBranch}`, remedy: `git checkout ${startBranch}, then rerun` });
|
|
21492
21496
|
}
|
|
21493
21497
|
const stage = laneStage(lane);
|
|
21494
|
-
const
|
|
21498
|
+
const workflowsRef = `origin/${startBranch ?? "main"}`;
|
|
21499
|
+
const workflows = deps.readWorkflows(cwd, workflowsRef);
|
|
21495
21500
|
if (workflows === null) {
|
|
21496
|
-
add({ code: "workflows-unreadable", severity: "blocker", source: "local", title: `.github/workflows
|
|
21501
|
+
add({ code: "workflows-unreadable", severity: "blocker", source: "local", title: `.github/workflows at ${workflowsRef} could not be read from ${cwd} \u2014 the tag-addressability (#5428) and npm-major (#5666) preflights did not run`, remedy: `run from a checkout whose git can read ${workflowsRef} (fetch origin first), then rerun; an unread workflow set is unverified, not green` });
|
|
21497
21502
|
}
|
|
21498
21503
|
if (hints) {
|
|
21499
21504
|
const missing = [
|
|
@@ -23490,6 +23495,7 @@ var GATE_GREEN_ON_BASE_LABEL = "gate is green on the default branch (#3819)";
|
|
|
23490
23495
|
var REQUIRED_CONTEXTS_EMITTED_LABEL = "required check contexts match PR workflows";
|
|
23491
23496
|
var TAG_ADDRESSABLE_CONTEXTS_LABEL = "rc/main required contexts are tag-addressable (#3880)";
|
|
23492
23497
|
var RELEASE_BRANCH_REACHABLE_LABEL = "rc/main required contexts are reachable from a release-base head (#5193)";
|
|
23498
|
+
var RULESET_TRACK_SCOPE_LABEL = "product ruleset branch scope covers the release track (#6327)";
|
|
23493
23499
|
var RECONCILE_SEED_BRANCH_PREFIX = "bootstrap-seed-ruleset-ref";
|
|
23494
23500
|
function slugFromRepo(repo) {
|
|
23495
23501
|
return (repo.includes("/") ? repo.split("/")[1] : repo).toLowerCase();
|
|
@@ -23556,6 +23562,10 @@ function gateWorkflowFiles(prWorkflowPaths) {
|
|
|
23556
23562
|
return files.length ? files : DEFAULT_GATE_FILES;
|
|
23557
23563
|
}
|
|
23558
23564
|
async function resolveEmittedPrContexts(deps, repo, branch) {
|
|
23565
|
+
const workflows = await resolveBranchWorkflows(deps, repo, branch);
|
|
23566
|
+
return workflows === void 0 ? void 0 : collectPullRequestWorkflowContexts(workflows);
|
|
23567
|
+
}
|
|
23568
|
+
async function resolveBranchWorkflows(deps, repo, branch) {
|
|
23559
23569
|
const paths = await listWorkflowPaths(deps, repo, branch);
|
|
23560
23570
|
if (paths === void 0) return void 0;
|
|
23561
23571
|
const workflows = [];
|
|
@@ -23563,7 +23573,7 @@ async function resolveEmittedPrContexts(deps, repo, branch) {
|
|
|
23563
23573
|
const body = await fetchFileContent(deps, repo, branch, path2);
|
|
23564
23574
|
if (body) workflows.push({ path: path2, body });
|
|
23565
23575
|
}
|
|
23566
|
-
return
|
|
23576
|
+
return workflows;
|
|
23567
23577
|
}
|
|
23568
23578
|
function registryRequiredContexts(meta) {
|
|
23569
23579
|
const raw = meta?.requiredChecks;
|
|
@@ -23576,6 +23586,9 @@ function refPatternCovers(pattern, ref) {
|
|
|
23576
23586
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
23577
23587
|
return new RegExp(`^${escaped}$`).test(ref);
|
|
23578
23588
|
}
|
|
23589
|
+
function uncoveredTrackBranches(liveIncludes, trackBranches) {
|
|
23590
|
+
return trackBranches.filter((branch) => !liveIncludes.some((pattern) => refPatternCovers(pattern, `refs/heads/${branch}`)));
|
|
23591
|
+
}
|
|
23579
23592
|
function rulesetCoversReleaseBranches(payload, track) {
|
|
23580
23593
|
const conditions = payload.conditions;
|
|
23581
23594
|
const include = conditions?.ref_name?.include;
|
|
@@ -23785,10 +23798,14 @@ async function auditRepoCi(repo, deps) {
|
|
|
23785
23798
|
prWorkflowPaths = listedWorkflowPaths === void 0 ? [] : await filterPullRequestTriggered(deps, repo, baseBranch, listedWorkflowPaths);
|
|
23786
23799
|
}
|
|
23787
23800
|
const hasGateWorkflow = hasCanonicalGateWorkflow || prWorkflowPaths.length > 0;
|
|
23788
|
-
let
|
|
23801
|
+
let branchWorkflows;
|
|
23802
|
+
const getBranchWorkflows = async () => {
|
|
23803
|
+
branchWorkflows ??= await resolveBranchWorkflows(deps, repo, baseBranch);
|
|
23804
|
+
return branchWorkflows;
|
|
23805
|
+
};
|
|
23789
23806
|
const getEmittedPrContexts = async () => {
|
|
23790
|
-
|
|
23791
|
-
return
|
|
23807
|
+
const workflows = await getBranchWorkflows();
|
|
23808
|
+
return workflows === void 0 ? void 0 : collectPullRequestWorkflowContexts(workflows);
|
|
23792
23809
|
};
|
|
23793
23810
|
const committedReferenceRaw = deployableGated ? await fetchFileContent(deps, repo, baseBranch, PRODUCT_RULESET_REF) : null;
|
|
23794
23811
|
let authoritativeRuleset = null;
|
|
@@ -23872,6 +23889,18 @@ async function auditRepoCi(repo, deps) {
|
|
|
23872
23889
|
});
|
|
23873
23890
|
}
|
|
23874
23891
|
}
|
|
23892
|
+
if (productRuleset != null) {
|
|
23893
|
+
const missingTrackBranches = [];
|
|
23894
|
+
for (const branch of uncoveredTrackBranches(liveBranchIncludes, branchesForTrack(resolveReleaseTrack(meta, void 0, repo)))) {
|
|
23895
|
+
if (await branchPresence(deps, repo, branch) === true) missingTrackBranches.push(branch);
|
|
23896
|
+
}
|
|
23897
|
+
checks.push({
|
|
23898
|
+
ok: missingTrackBranches.length === 0,
|
|
23899
|
+
label: RULESET_TRACK_SCOPE_LABEL,
|
|
23900
|
+
detail: missingTrackBranches.length ? `live ${PRODUCT_RULESET_NAME} includes [${liveBranchIncludes.join(", ")}] but the release track also requires [${missingTrackBranches.join(", ")}] \u2014 those branches tag and merge with no required check` : void 0,
|
|
23901
|
+
remediation: missingTrackBranches.length ? `mmi-cli oracle org project set ${repo} --var requiredCheckBranches=${JSON.stringify(branchesForTrack(resolveReleaseTrack(meta, void 0, repo)))} then mmi-cli devops bootstrap apply ${repo} --execute` : void 0
|
|
23902
|
+
});
|
|
23903
|
+
}
|
|
23875
23904
|
const contextAuthority = authoritativeRuleset ?? registryAuthorityWithoutReference(meta, repo);
|
|
23876
23905
|
if (contextAuthority != null) {
|
|
23877
23906
|
const emitted = await getEmittedPrContexts();
|
|
@@ -23886,11 +23915,12 @@ async function auditRepoCi(repo, deps) {
|
|
|
23886
23915
|
});
|
|
23887
23916
|
}
|
|
23888
23917
|
const prOnly = contextAuthority.coversReleaseBranches ? contextAuthority.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context)) : [];
|
|
23918
|
+
const tagBlind = contextAuthority.coversReleaseBranches ? contextsUnreachableOnTagPush(contextAuthority.contexts, await getBranchWorkflows() ?? [], TRAIN_PR_ONLY_CONTEXTS) : [];
|
|
23889
23919
|
checks.push({
|
|
23890
|
-
ok: prOnly.length === 0,
|
|
23920
|
+
ok: prOnly.length === 0 && tagBlind.length === 0,
|
|
23891
23921
|
label: TAG_ADDRESSABLE_CONTEXTS_LABEL,
|
|
23892
|
-
detail: prOnly.length ? `${contextAuthority.source} requires [${prOnly.join(", ")}] on rc/main, but those contexts can only materialize for pull_request` : void 0,
|
|
23893
|
-
remediation: prOnly.length ? `Remove [${prOnly.join(", ")}] from rc/main requirements in registry META or ${PRODUCT_RULESET_REF}` : void 0
|
|
23922
|
+
detail: prOnly.length ? `${contextAuthority.source} requires [${prOnly.join(", ")}] on rc/main, but those contexts can only materialize for pull_request` : tagBlind.length ? `${contextAuthority.source} requires [${tagBlind.join(", ")}] on rc/main, but their workflows never trigger on a v* tag push (#5428) \u2014 the train would tag and wait forever` : void 0,
|
|
23923
|
+
remediation: prOnly.length ? `Remove [${prOnly.join(", ")}] from rc/main requirements in registry META or ${PRODUCT_RULESET_REF}` : tagBlind.length ? `Add push: tags: ['v*'] to the workflow(s) emitting [${tagBlind.join(", ")}] on ${baseBranch} (MMI-Hub .github/workflows/gate.yml is the shape)` : void 0
|
|
23894
23924
|
});
|
|
23895
23925
|
if (productRuleset != null && liveContexts.length > 0) {
|
|
23896
23926
|
const releaseBranches = releaseBranchRefsCovered(productRuleset.conditions, resolveReleaseTrack(meta, void 0, repo));
|
|
@@ -24187,7 +24217,8 @@ async function seedGateYml(repo, deps, meta, result) {
|
|
|
24187
24217
|
parsed,
|
|
24188
24218
|
"deployable",
|
|
24189
24219
|
releaseTrack,
|
|
24190
|
-
meta?.requiredCheckBranches
|
|
24220
|
+
meta?.requiredCheckBranches,
|
|
24221
|
+
registryRequiredContexts(meta) ?? void 0
|
|
24191
24222
|
);
|
|
24192
24223
|
if (await contentExists(deps, repo, baseBranch, PRODUCT_GATE_PATH)) {
|
|
24193
24224
|
return await seedRulesetRefIfMissing(repo, deps, refOnlyVars(), baseBranch, result);
|
|
@@ -24214,7 +24245,8 @@ async function seedGateYml(repo, deps, meta, result) {
|
|
|
24214
24245
|
parsed,
|
|
24215
24246
|
"deployable",
|
|
24216
24247
|
releaseTrack,
|
|
24217
|
-
meta?.requiredCheckBranches
|
|
24248
|
+
meta?.requiredCheckBranches,
|
|
24249
|
+
registryRequiredContexts(meta) ?? void 0
|
|
24218
24250
|
);
|
|
24219
24251
|
const rendered = renderSeedBody(deps, GATE_TEMPLATE_SEED, PRODUCT_GATE_PATH, derivedVars);
|
|
24220
24252
|
if (rendered == null) {
|
|
@@ -25480,10 +25512,10 @@ async function loadConfigForBoardSelector2(selector, repoOption) {
|
|
|
25480
25512
|
}
|
|
25481
25513
|
|
|
25482
25514
|
// src/statusline-invalidate.ts
|
|
25483
|
-
var
|
|
25515
|
+
var import_node_child_process10 = require("node:child_process");
|
|
25484
25516
|
function invalidateStatuslineBoardCache() {
|
|
25485
25517
|
try {
|
|
25486
|
-
const child2 = (0,
|
|
25518
|
+
const child2 = (0, import_node_child_process10.spawn)("jerv-cli", ["lane", "ops", "invalidate-board"], {
|
|
25487
25519
|
detached: true,
|
|
25488
25520
|
stdio: "ignore",
|
|
25489
25521
|
windowsHide: true
|
|
@@ -26564,8 +26596,9 @@ function deployStagesForBootstrap(deployModel, track) {
|
|
|
26564
26596
|
return branchesForTrack(track).map((branch) => branch === "development" ? "dev" : branch);
|
|
26565
26597
|
}
|
|
26566
26598
|
var missingBaseDetail = (base) => `${base} is missing; no source ref exists from which bootstrap can create the train lanes`;
|
|
26567
|
-
function productRulesetMatches(fact, branches) {
|
|
26568
|
-
|
|
26599
|
+
function productRulesetMatches(fact, branches, contexts) {
|
|
26600
|
+
const wantedContexts = contexts?.length ? contexts : ["gate"];
|
|
26601
|
+
if (fact?.enforcement !== "active" || !sameNames(fact.contexts, wantedContexts)) return false;
|
|
26569
26602
|
const wanted = branches.map((branch) => branch.startsWith("refs/") ? branch : `refs/heads/${branch}`);
|
|
26570
26603
|
return sameNames(fact.branches, wanted);
|
|
26571
26604
|
}
|
|
@@ -26601,7 +26634,7 @@ function planBootstrapControlPlane(input) {
|
|
|
26601
26634
|
code: "bootstrap-gap",
|
|
26602
26635
|
detail: [input.github.gateError, input.github.rulesetError].filter(Boolean).join("; ")
|
|
26603
26636
|
});
|
|
26604
|
-
} else if (productRulesetMatches(input.github.productRuleset, input.requiredCheckBranches)) {
|
|
26637
|
+
} else if (productRulesetMatches(input.github.productRuleset, input.requiredCheckBranches, input.requiredChecks)) {
|
|
26605
26638
|
rows.push({ kind: "product-ruleset", target: "mmi-product-required-checks", action: "skip", detail: "active gate context and branch scope already match the seeded authority" });
|
|
26606
26639
|
} else if (!input.github.gateExists) {
|
|
26607
26640
|
rows.push({ kind: "product-ruleset", target: "mmi-product-required-checks", action: "park", detail: `${PRODUCT_GATE_PATH2} is not present on ${base}; install disabled and activate on the next apply after the seed lands` });
|
|
@@ -27613,6 +27646,7 @@ function registerBootstrapCommands(program3) {
|
|
|
27613
27646
|
repoClass: cls,
|
|
27614
27647
|
releaseTrack: track,
|
|
27615
27648
|
requiredCheckBranches: resolveRequiredCheckBranches(metaWithDeclaredClass(meta, cls), repo),
|
|
27649
|
+
requiredChecks: meta?.requiredChecks ?? void 0,
|
|
27616
27650
|
deployModel: meta?.deployModel,
|
|
27617
27651
|
owners: controlOwners,
|
|
27618
27652
|
github,
|
|
@@ -27752,7 +27786,7 @@ function registerBootstrapCommands(program3) {
|
|
|
27752
27786
|
for (const [k, v] of Object.entries(gateConfigToVars(meta?.gate))) if (rawVars[k] == null) rawVars[k] = v;
|
|
27753
27787
|
} catch {
|
|
27754
27788
|
}
|
|
27755
|
-
const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack, registryRequiredCheckBranches);
|
|
27789
|
+
const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack, registryRequiredCheckBranches, registryMeta?.requiredChecks);
|
|
27756
27790
|
if (!vars.PROJECT_ID) {
|
|
27757
27791
|
try {
|
|
27758
27792
|
const r = await gh(linkedProjectsQueryArgs(parsedRepo.owner, parsedRepo.name));
|
|
@@ -27811,6 +27845,7 @@ function registerBootstrapCommands(program3) {
|
|
|
27811
27845
|
repoClass: o.class,
|
|
27812
27846
|
releaseTrack: effectiveTrack,
|
|
27813
27847
|
requiredCheckBranches: resolveRequiredCheckBranches(metaWithDeclaredClass(registryMeta, o.class), repo),
|
|
27848
|
+
requiredChecks: registryMeta?.requiredChecks ?? void 0,
|
|
27814
27849
|
deployModel: applyDeployModel,
|
|
27815
27850
|
owners: controlOwners,
|
|
27816
27851
|
github,
|
|
@@ -28160,6 +28195,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
28160
28195
|
repoClass: o.class,
|
|
28161
28196
|
releaseTrack: effectiveTrack,
|
|
28162
28197
|
requiredCheckBranches: resolveRequiredCheckBranches(metaWithDeclaredClass(registryMeta, o.class), repo),
|
|
28198
|
+
requiredChecks: registryMeta?.requiredChecks ?? void 0,
|
|
28163
28199
|
deployModel: applyDeployModel,
|
|
28164
28200
|
owners: controlOwners,
|
|
28165
28201
|
github,
|
|
@@ -28270,7 +28306,7 @@ LIVE apply to ${repo}:
|
|
|
28270
28306
|
if (seed.source.startsWith("seed:")) {
|
|
28271
28307
|
const project2 = projects.find((p) => (p.repos ?? []).some((repo) => repo.toLowerCase() === r.repo.toLowerCase()));
|
|
28272
28308
|
const track = resolveReleaseTrack(project2, void 0, r.repo);
|
|
28273
|
-
const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass, track, project2?.requiredCheckBranches);
|
|
28309
|
+
const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass, track, project2?.requiredCheckBranches, project2?.requiredChecks);
|
|
28274
28310
|
const resolved = resolveSeedWriteContent(seed, vars, readSeedFile2, content);
|
|
28275
28311
|
if (!resolved.ok || resolved.content == null) {
|
|
28276
28312
|
return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.ok ? "rendered no content" : resolved.reason} \u2014 refusing an incomplete per-repo render`);
|
|
@@ -28358,6 +28394,7 @@ LIVE apply to ${repo}:
|
|
|
28358
28394
|
branchExists = false;
|
|
28359
28395
|
}
|
|
28360
28396
|
if (!branchExists) await gh(["api", `repos/${rec.repo}/git/refs`, "-f", `ref=refs/heads/${branch}`, "-f", `sha=${baseSha}`]);
|
|
28397
|
+
else await gh(["api", "-X", "PATCH", `repos/${rec.repo}/git/refs/heads/${branch}`, "-f", `sha=${baseSha}`, "-F", "force=true"]);
|
|
28361
28398
|
let existingSha;
|
|
28362
28399
|
try {
|
|
28363
28400
|
const cur = await gh(["api", `repos/${rec.repo}/contents/${enc(seed.target)}?ref=${branch}`]);
|
|
@@ -29557,7 +29594,7 @@ function deriveComposeProjectName(worktreePath) {
|
|
|
29557
29594
|
}
|
|
29558
29595
|
|
|
29559
29596
|
// src/stage-runner.ts
|
|
29560
|
-
var
|
|
29597
|
+
var import_node_child_process11 = require("node:child_process");
|
|
29561
29598
|
var import_node_fs28 = require("node:fs");
|
|
29562
29599
|
var import_node_path27 = require("node:path");
|
|
29563
29600
|
var import_node_net = require("node:net");
|
|
@@ -29606,7 +29643,7 @@ function findEnvFiles(root) {
|
|
|
29606
29643
|
}
|
|
29607
29644
|
|
|
29608
29645
|
// src/stage-runner.ts
|
|
29609
|
-
var execFileP3 = (0, import_node_util5.promisify)(
|
|
29646
|
+
var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process11.execFile);
|
|
29610
29647
|
var DOCKER_TIMEOUT_MS = 15e3;
|
|
29611
29648
|
var EARLY_EXIT_GRACE_MS = 2e3;
|
|
29612
29649
|
function earlyExitGraceMs() {
|
|
@@ -30124,7 +30161,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
30124
30161
|
let up = sub(config.up.trim());
|
|
30125
30162
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
30126
30163
|
const identity = await resolveStageIdentity(cwd);
|
|
30127
|
-
const child2 = (0,
|
|
30164
|
+
const child2 = (0, import_node_child_process11.spawn)(up, {
|
|
30128
30165
|
cwd,
|
|
30129
30166
|
shell: true,
|
|
30130
30167
|
// POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
|
|
@@ -34644,9 +34681,9 @@ function writeError(res) {
|
|
|
34644
34681
|
|
|
34645
34682
|
// src/schedules-commands.ts
|
|
34646
34683
|
var import_promises5 = require("node:fs/promises");
|
|
34647
|
-
var
|
|
34684
|
+
var import_node_child_process12 = require("node:child_process");
|
|
34648
34685
|
var import_node_util6 = require("node:util");
|
|
34649
|
-
var execFileP4 = (0, import_node_util6.promisify)(
|
|
34686
|
+
var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process12.execFile);
|
|
34650
34687
|
var AWS_REGION = "eu-central-1";
|
|
34651
34688
|
var AWS_TIMEOUT_MS = 3e4;
|
|
34652
34689
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -36166,7 +36203,7 @@ function renderVerifySecrets(body) {
|
|
|
36166
36203
|
}
|
|
36167
36204
|
|
|
36168
36205
|
// src/command-register-collaboration.ts
|
|
36169
|
-
var
|
|
36206
|
+
var import_node_child_process17 = require("node:child_process");
|
|
36170
36207
|
var import_node_fs43 = require("node:fs");
|
|
36171
36208
|
var import_promises7 = require("node:fs/promises");
|
|
36172
36209
|
|
|
@@ -36381,14 +36418,6 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
36381
36418
|
};
|
|
36382
36419
|
}
|
|
36383
36420
|
}
|
|
36384
|
-
const queue = await deps.queueMerge?.(prNumber, repo);
|
|
36385
|
-
if (queue) return {
|
|
36386
|
-
...base,
|
|
36387
|
-
queue,
|
|
36388
|
-
status: queue.state === "merged" ? "merged" : "failed",
|
|
36389
|
-
mergeStatus: queue.state === "merged" ? "merged" : "failed",
|
|
36390
|
-
...queue.state === "merged" ? {} : { error: queue.detail }
|
|
36391
|
-
};
|
|
36392
36421
|
const ciPolicy = await deps.resolveCiPolicy(repo);
|
|
36393
36422
|
base.ciPolicy = ciPolicy;
|
|
36394
36423
|
const checksWaitError = (checksWait) => {
|
|
@@ -36548,12 +36577,12 @@ function boardAdvanceFailureMessage(result) {
|
|
|
36548
36577
|
}
|
|
36549
36578
|
|
|
36550
36579
|
// src/test-policy-core.ts
|
|
36551
|
-
var
|
|
36580
|
+
var import_node_child_process14 = require("node:child_process");
|
|
36552
36581
|
var import_node_fs36 = require("node:fs");
|
|
36553
36582
|
var import_node_path33 = require("node:path");
|
|
36554
36583
|
|
|
36555
36584
|
// src/test-command-policy-shared.mjs
|
|
36556
|
-
var
|
|
36585
|
+
var import_node_child_process13 = require("node:child_process");
|
|
36557
36586
|
var TEST_COMMAND_CLASS = "test";
|
|
36558
36587
|
var TRAILER_KEY = "Test-Policy-Override";
|
|
36559
36588
|
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
@@ -36653,7 +36682,7 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
|
|
|
36653
36682
|
};
|
|
36654
36683
|
}
|
|
36655
36684
|
function git(args, cwd) {
|
|
36656
|
-
return (0,
|
|
36685
|
+
return (0, import_node_child_process13.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
36657
36686
|
}
|
|
36658
36687
|
function parseScope(value) {
|
|
36659
36688
|
const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
|
|
@@ -37152,14 +37181,14 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
37152
37181
|
return findings;
|
|
37153
37182
|
}
|
|
37154
37183
|
function git2(args, cwd) {
|
|
37155
|
-
return (0,
|
|
37184
|
+
return (0, import_node_child_process14.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
37156
37185
|
}
|
|
37157
37186
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
37158
37187
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
37159
37188
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
37160
37189
|
function parseTrailers(message2, cwd) {
|
|
37161
37190
|
try {
|
|
37162
|
-
return (0,
|
|
37191
|
+
return (0, import_node_child_process14.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
37163
37192
|
windowsHide: true,
|
|
37164
37193
|
cwd,
|
|
37165
37194
|
input: message2,
|
|
@@ -37700,287 +37729,9 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
37700
37729
|
};
|
|
37701
37730
|
}
|
|
37702
37731
|
|
|
37703
|
-
// src/
|
|
37704
|
-
var import_node_child_process14 = require("node:child_process");
|
|
37732
|
+
// src/post-merge-recon.ts
|
|
37705
37733
|
var import_node_fs38 = require("node:fs");
|
|
37706
|
-
var import_node_os19 = require("node:os");
|
|
37707
37734
|
var import_node_path35 = require("node:path");
|
|
37708
|
-
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
37709
|
-
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
37710
|
-
function isReviewVerdict(value) {
|
|
37711
|
-
return typeof value === "string" && REVIEW_VERDICTS.includes(value);
|
|
37712
|
-
}
|
|
37713
|
-
function renderReviewVerdictComment(input) {
|
|
37714
|
-
const payload = {
|
|
37715
|
-
v: 1,
|
|
37716
|
-
patch: input.patch,
|
|
37717
|
-
head: input.head,
|
|
37718
|
-
verdict: input.verdict,
|
|
37719
|
-
scope: input.scope,
|
|
37720
|
-
risk: input.risk,
|
|
37721
|
-
unverified: input.unverified,
|
|
37722
|
-
reviewer: input.reviewer
|
|
37723
|
-
};
|
|
37724
|
-
const findings = input.findings?.trim();
|
|
37725
|
-
return `${REVIEW_VERDICT_MARKER}
|
|
37726
|
-
\`\`\`json
|
|
37727
|
-
${JSON.stringify(payload, null, 2)}
|
|
37728
|
-
\`\`\`
|
|
37729
|
-
${findings ? `
|
|
37730
|
-
${findings}
|
|
37731
|
-
` : ""}`;
|
|
37732
|
-
}
|
|
37733
|
-
function isReviewVerdictComment(body) {
|
|
37734
|
-
return body.trimStart().startsWith(REVIEW_VERDICT_MARKER);
|
|
37735
|
-
}
|
|
37736
|
-
var FENCE_RE = /^(?:`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n(?:`{3,}|~{3,})\s*$/m;
|
|
37737
|
-
function parseReviewVerdictComment(body) {
|
|
37738
|
-
if (!isReviewVerdictComment(body)) return void 0;
|
|
37739
|
-
const rest = body.trimStart().slice(REVIEW_VERDICT_MARKER.length);
|
|
37740
|
-
const fence = FENCE_RE.exec(rest);
|
|
37741
|
-
if (!fence) return void 0;
|
|
37742
|
-
let parsed;
|
|
37743
|
-
try {
|
|
37744
|
-
parsed = JSON.parse(fence[1]);
|
|
37745
|
-
} catch {
|
|
37746
|
-
return void 0;
|
|
37747
|
-
}
|
|
37748
|
-
if (!parsed || typeof parsed !== "object") return void 0;
|
|
37749
|
-
const p = parsed;
|
|
37750
|
-
if (p.v !== 1 || !isReviewVerdict(p.verdict)) return void 0;
|
|
37751
|
-
if (typeof p.patch !== "string" || !/^[0-9a-f]{40}$/.test(p.patch)) return void 0;
|
|
37752
|
-
if (typeof p.head !== "string" || typeof p.scope !== "string" || typeof p.risk !== "string" || typeof p.reviewer !== "string") return void 0;
|
|
37753
|
-
const unverified = Array.isArray(p.unverified) ? p.unverified.filter((u) => typeof u === "string") : [];
|
|
37754
|
-
return { v: 1, patch: p.patch, head: p.head, verdict: p.verdict, scope: p.scope, risk: p.risk, unverified, reviewer: p.reviewer };
|
|
37755
|
-
}
|
|
37756
|
-
function latestReviewComment(comments) {
|
|
37757
|
-
let latest;
|
|
37758
|
-
for (const c of comments) {
|
|
37759
|
-
if (!isReviewVerdictComment(c.body)) continue;
|
|
37760
|
-
if (!latest || c.createdAt > latest.createdAt || c.createdAt === latest.createdAt && (c.id ?? 0) >= (latest.id ?? 0)) latest = c;
|
|
37761
|
-
}
|
|
37762
|
-
return latest;
|
|
37763
|
-
}
|
|
37764
|
-
function evaluateReviewVerdict(comments, currentPatchId) {
|
|
37765
|
-
const latest = latestReviewComment(comments);
|
|
37766
|
-
if (!latest) return { ok: false, reason: "none" };
|
|
37767
|
-
const payload = parseReviewVerdictComment(latest.body);
|
|
37768
|
-
if (!payload) return { ok: false, reason: "malformed" };
|
|
37769
|
-
if (payload.verdict !== "PROCEED") return { ok: false, reason: "not-proceed", verdict: payload.verdict, patch: payload.patch };
|
|
37770
|
-
if (payload.patch !== currentPatchId) return { ok: false, reason: "stale", verdict: payload.verdict, patch: payload.patch };
|
|
37771
|
-
return { ok: true, reason: "proceed", verdict: payload.verdict, patch: payload.patch };
|
|
37772
|
-
}
|
|
37773
|
-
function evaluateTrustedReviewVerdict(comments, patch, head) {
|
|
37774
|
-
const latest = latestReviewComment(comments);
|
|
37775
|
-
if (!latest) return { ok: false, reason: "none" };
|
|
37776
|
-
const identity = { commentId: latest.id, commentCreatedAt: latest.createdAt };
|
|
37777
|
-
if (latest.author?.toLowerCase() !== "jervaise") return { ...identity, ok: false, reason: "untrusted-author" };
|
|
37778
|
-
const evaluation = evaluateReviewVerdict([latest], patch);
|
|
37779
|
-
if (!evaluation.ok) return { ...identity, ok: false, reason: evaluation.reason };
|
|
37780
|
-
if (parseReviewVerdictComment(latest.body)?.head !== head) return { ...identity, ok: false, reason: "stale-head" };
|
|
37781
|
-
return { ...identity, ok: true, reason: "proceed" };
|
|
37782
|
-
}
|
|
37783
|
-
async function checkPrReview(number, repo, head, deps = {
|
|
37784
|
-
readHead: readPrHeadSha,
|
|
37785
|
-
readComments: readPrIssueComments,
|
|
37786
|
-
computePatch: computePrPatchId
|
|
37787
|
-
}) {
|
|
37788
|
-
const base = { repo, number, head };
|
|
37789
|
-
if (!/^[1-9][0-9]*$/.test(number) || !/^[\w.-]+\/[\w.-]+$/.test(repo) || !/^[0-9a-f]{40}$/.test(head)) {
|
|
37790
|
-
return { ...base, ok: false, reason: "invalid-input", detail: "expected a PR number, owner/repo and exact lowercase 40-character head SHA" };
|
|
37791
|
-
}
|
|
37792
|
-
try {
|
|
37793
|
-
if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
|
|
37794
|
-
const [comments, patch] = await Promise.all([deps.readComments(number, repo), deps.computePatch(number, repo)]);
|
|
37795
|
-
if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
|
|
37796
|
-
return { ...base, patch, ...evaluateTrustedReviewVerdict(comments, patch, head) };
|
|
37797
|
-
} catch (e) {
|
|
37798
|
-
return { ...base, ok: false, reason: "unreadable", detail: e.message };
|
|
37799
|
-
}
|
|
37800
|
-
}
|
|
37801
|
-
function computePrPatchId(number, repo) {
|
|
37802
|
-
return new Promise((resolve7, reject) => {
|
|
37803
|
-
const gh = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
37804
|
-
const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
37805
|
-
let out = "";
|
|
37806
|
-
let ghErr = "";
|
|
37807
|
-
let gitErr = "";
|
|
37808
|
-
const timer = setTimeout(() => {
|
|
37809
|
-
gh.kill();
|
|
37810
|
-
git3.kill();
|
|
37811
|
-
reject(new Error(`patch-id: timed out after ${GC_GH_TIMEOUT_MS4}ms`));
|
|
37812
|
-
}, GC_GH_TIMEOUT_MS4);
|
|
37813
|
-
gh.stdout.pipe(git3.stdin);
|
|
37814
|
-
gh.stderr.on("data", (d) => {
|
|
37815
|
-
ghErr += d.toString();
|
|
37816
|
-
});
|
|
37817
|
-
git3.stderr.on("data", (d) => {
|
|
37818
|
-
gitErr += d.toString();
|
|
37819
|
-
});
|
|
37820
|
-
git3.stdout.on("data", (d) => {
|
|
37821
|
-
out += d.toString();
|
|
37822
|
-
});
|
|
37823
|
-
gh.on("error", (e) => {
|
|
37824
|
-
clearTimeout(timer);
|
|
37825
|
-
reject(e);
|
|
37826
|
-
});
|
|
37827
|
-
git3.on("error", (e) => {
|
|
37828
|
-
clearTimeout(timer);
|
|
37829
|
-
reject(e);
|
|
37830
|
-
});
|
|
37831
|
-
let ghCode;
|
|
37832
|
-
let gitCode;
|
|
37833
|
-
const finish = () => {
|
|
37834
|
-
if (ghCode === void 0 || gitCode === void 0) return;
|
|
37835
|
-
clearTimeout(timer);
|
|
37836
|
-
if (ghCode !== 0) return reject(Object.assign(new Error(`gh pr diff ${number} --repo ${repo} exited ${ghCode}: ${ghErr.trim()}`), { stderr: ghErr }));
|
|
37837
|
-
if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
|
|
37838
|
-
const id = out.trim().split(/\s+/)[0];
|
|
37839
|
-
if (!/^[0-9a-f]{40}$/.test(id ?? "")) return reject(new Error(`patch-id: PR #${number} diff is empty or unreadable (gh: ${ghErr.trim() || "no stderr"})`));
|
|
37840
|
-
resolve7(id);
|
|
37841
|
-
};
|
|
37842
|
-
gh.on("close", (code) => {
|
|
37843
|
-
ghCode = code;
|
|
37844
|
-
finish();
|
|
37845
|
-
});
|
|
37846
|
-
git3.on("close", (code) => {
|
|
37847
|
-
gitCode = code;
|
|
37848
|
-
finish();
|
|
37849
|
-
});
|
|
37850
|
-
git3.stdin.on("error", (e) => {
|
|
37851
|
-
clearTimeout(timer);
|
|
37852
|
-
gh.kill();
|
|
37853
|
-
git3.kill();
|
|
37854
|
-
reject(e);
|
|
37855
|
-
});
|
|
37856
|
-
});
|
|
37857
|
-
}
|
|
37858
|
-
async function readPrHeadSha(number, repo) {
|
|
37859
|
-
const { stdout } = await execFileP("gh", ["api", `repos/${repo}/pulls/${number}`, "--jq", ".head.sha"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
37860
|
-
const sha = stdout.trim();
|
|
37861
|
-
if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error(`could not read PR #${number} head sha`);
|
|
37862
|
-
return sha;
|
|
37863
|
-
}
|
|
37864
|
-
async function readPrIssueComments(number, repo) {
|
|
37865
|
-
const { stdout } = await execFileP("gh", ["api", "--paginate", `repos/${repo}/issues/${number}/comments?per_page=100`, "--jq", ".[] | {id, body, createdAt: .created_at, author: .user.login}"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
37866
|
-
const comments = parseNdjsonLines(stdout);
|
|
37867
|
-
if (comments.some((c) => !c || !Number.isSafeInteger(c.id) || c.id <= 0 || typeof c.body !== "string" || typeof c.createdAt !== "string" || !Number.isFinite(Date.parse(c.createdAt)) || c.author !== null && typeof c.author !== "string")) throw new Error("unreadable PR comment identity");
|
|
37868
|
-
return comments;
|
|
37869
|
-
}
|
|
37870
|
-
async function postPrCommentFromFile(number, repo, body) {
|
|
37871
|
-
const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
|
|
37872
|
-
const path2 = (0, import_node_path35.join)(dir, "body.md");
|
|
37873
|
-
try {
|
|
37874
|
-
(0, import_node_fs38.writeFileSync)(path2, body, "utf8");
|
|
37875
|
-
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
37876
|
-
return stdout.trim();
|
|
37877
|
-
} finally {
|
|
37878
|
-
try {
|
|
37879
|
-
(0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
|
|
37880
|
-
} catch {
|
|
37881
|
-
}
|
|
37882
|
-
}
|
|
37883
|
-
}
|
|
37884
|
-
|
|
37885
|
-
// src/pr-mergify-queue.ts
|
|
37886
|
-
var MERGIFY_PILOT_REPO = "mutmutco/Jerv-JervCode";
|
|
37887
|
-
var MERGIFY_PILOT_VARIABLE = "JERV_BATCH_QUEUE_PILOT";
|
|
37888
|
-
var MERGIFY_READY_LABEL = "ready-to-merge";
|
|
37889
|
-
async function ghJson(args) {
|
|
37890
|
-
return JSON.parse((await execFileP("gh", ["api", ...args], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
|
|
37891
|
-
}
|
|
37892
|
-
async function usesMergifyPilot(repo, base, readVariable = async () => ghJson([`repos/${repo}/actions/variables/${MERGIFY_PILOT_VARIABLE}`])) {
|
|
37893
|
-
if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase() || base !== "development") return false;
|
|
37894
|
-
const variable = await readVariable();
|
|
37895
|
-
if (variable?.value === "true") return true;
|
|
37896
|
-
if (variable?.value === "false") return false;
|
|
37897
|
-
throw new Error(`Mergify pilot activation is missing or malformed on ${repo}; refusing native merge`);
|
|
37898
|
-
}
|
|
37899
|
-
function queueCheckState(checks, head) {
|
|
37900
|
-
const check = checks.filter((c) => c.name === "Mergify Merge Queue" && c.head_sha === head && c.app?.id === 10562 && c.app.slug === "mergify").sort((a, b) => b.id - a.id)[0];
|
|
37901
|
-
if (!check) return "refused";
|
|
37902
|
-
if (check.conclusion && !["success", "neutral"].includes(check.conclusion)) return "refused";
|
|
37903
|
-
if (["In merge queue", "Running merge queue checks"].includes(check.output?.title ?? "")) return "pending";
|
|
37904
|
-
return "requested";
|
|
37905
|
-
}
|
|
37906
|
-
async function requestMergifyMerge(number, repo, guardedText, expectedHead, deps = {
|
|
37907
|
-
readPull: () => ghJson([`repos/${repo}/pulls/${number}`]),
|
|
37908
|
-
readChecks: async (head) => parseNdjsonLines((await execFileP("gh", [
|
|
37909
|
-
"api",
|
|
37910
|
-
"--paginate",
|
|
37911
|
-
`repos/${repo}/commits/${head}/check-runs?per_page=100`,
|
|
37912
|
-
"--jq",
|
|
37913
|
-
".check_runs[]"
|
|
37914
|
-
], { timeout: GC_GH_TIMEOUT_MS4 })).stdout),
|
|
37915
|
-
addLabel: async () => {
|
|
37916
|
-
await ghJson([`repos/${repo}/issues/${number}/labels`, "--method", "POST", "-f", `labels[]=${MERGIFY_READY_LABEL}`]);
|
|
37917
|
-
},
|
|
37918
|
-
review: (head) => checkPrReview(number, repo, head),
|
|
37919
|
-
now: () => Date.now(),
|
|
37920
|
-
sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
|
|
37921
|
-
}, timeoutMs = 6e5) {
|
|
37922
|
-
let head = expectedHead ?? "";
|
|
37923
|
-
let state = "refused";
|
|
37924
|
-
const receipt = (detail) => ({ provider: "mergify", state, head, detail });
|
|
37925
|
-
try {
|
|
37926
|
-
if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase()) return receipt("repository is outside the Mergify pilot");
|
|
37927
|
-
const initial = await deps.readPull();
|
|
37928
|
-
head = expectedHead ?? initial.head.sha;
|
|
37929
|
-
const sameTarget = (pr) => /^[0-9a-f]{40}$/.test(head) && pr.head.sha === head && pr.base.ref === "development";
|
|
37930
|
-
if (!sameTarget(initial)) return receipt("PR head or target base changed");
|
|
37931
|
-
if (initial.merged === true) {
|
|
37932
|
-
state = "merged";
|
|
37933
|
-
return receipt("GitHub confirms the PR merged");
|
|
37934
|
-
}
|
|
37935
|
-
const validate = (pr) => pr.state === "open" && sameTarget(pr) && `${pr.title}
|
|
37936
|
-
${pr.body ?? ""}` === guardedText;
|
|
37937
|
-
if (!validate(initial)) return receipt("PR state, head, base or guarded title/body changed");
|
|
37938
|
-
const initialState = queueCheckState(await deps.readChecks(head), head);
|
|
37939
|
-
if (initialState === "refused") return receipt("trusted current-head Mergify check is absent or failed");
|
|
37940
|
-
if (!(await deps.review(head)).ok) return receipt("current trusted review verdict does not permit queue admission");
|
|
37941
|
-
if (!validate(await deps.readPull())) return receipt("PR changed before queue request");
|
|
37942
|
-
let mutationDetail = "";
|
|
37943
|
-
if (!initial.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
|
|
37944
|
-
try {
|
|
37945
|
-
await deps.addLabel();
|
|
37946
|
-
} catch {
|
|
37947
|
-
mutationDetail = "Label response was ambiguous; no replay was attempted. ";
|
|
37948
|
-
}
|
|
37949
|
-
}
|
|
37950
|
-
state = "requested";
|
|
37951
|
-
const deadline = deps.now() + timeoutMs;
|
|
37952
|
-
while (true) {
|
|
37953
|
-
const pr = await deps.readPull();
|
|
37954
|
-
if (!sameTarget(pr)) {
|
|
37955
|
-
state = "refused";
|
|
37956
|
-
return receipt("PR head or target base changed while queued");
|
|
37957
|
-
}
|
|
37958
|
-
if (pr.merged === true) {
|
|
37959
|
-
state = "merged";
|
|
37960
|
-
return receipt("GitHub confirms the PR merged");
|
|
37961
|
-
}
|
|
37962
|
-
if (!validate(pr)) {
|
|
37963
|
-
state = "refused";
|
|
37964
|
-
return receipt("PR state, head, base or guarded title/body changed while queued");
|
|
37965
|
-
}
|
|
37966
|
-
state = queueCheckState(await deps.readChecks(head), head);
|
|
37967
|
-
if (state === "refused") return receipt("trusted current-head Mergify check is absent or failed");
|
|
37968
|
-
if (!pr.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
|
|
37969
|
-
state = "refused";
|
|
37970
|
-
return receipt(`${mutationDetail}Queue request label is absent`);
|
|
37971
|
-
}
|
|
37972
|
-
if (deps.now() >= deadline) return receipt(`${mutationDetail}${state === "pending" ? "Mergify confirms queue admission" : "Queue requested; Mergify has not confirmed admission"}; PR has not merged within the wait window`);
|
|
37973
|
-
await deps.sleep(3e4);
|
|
37974
|
-
}
|
|
37975
|
-
} catch {
|
|
37976
|
-
state = "refused";
|
|
37977
|
-
return receipt("Mergify or GitHub state is unreadable; no native merge was attempted");
|
|
37978
|
-
}
|
|
37979
|
-
}
|
|
37980
|
-
|
|
37981
|
-
// src/post-merge-recon.ts
|
|
37982
|
-
var import_node_fs39 = require("node:fs");
|
|
37983
|
-
var import_node_path36 = require("node:path");
|
|
37984
37735
|
|
|
37985
37736
|
// src/cross-repo-filing-issue.ts
|
|
37986
37737
|
function crossRepoFilingRetryCommand(prRepo, prNumber) {
|
|
@@ -38146,16 +37897,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
38146
37897
|
}
|
|
38147
37898
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
38148
37899
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
38149
|
-
(0,
|
|
38150
|
-
(0,
|
|
37900
|
+
(0, import_node_fs38.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
|
|
37901
|
+
(0, import_node_fs38.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
38151
37902
|
`, "utf8");
|
|
38152
37903
|
return path2;
|
|
38153
37904
|
}
|
|
38154
37905
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
38155
37906
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
38156
|
-
if (!(0,
|
|
37907
|
+
if (!(0, import_node_fs38.existsSync)(path2)) return;
|
|
38157
37908
|
try {
|
|
38158
|
-
(0,
|
|
37909
|
+
(0, import_node_fs38.unlinkSync)(path2);
|
|
38159
37910
|
} catch {
|
|
38160
37911
|
}
|
|
38161
37912
|
}
|
|
@@ -38185,15 +37936,197 @@ function postMergeReconWarnings(input) {
|
|
|
38185
37936
|
return lines2;
|
|
38186
37937
|
}
|
|
38187
37938
|
|
|
38188
|
-
// src/
|
|
37939
|
+
// src/review-verdict.ts
|
|
38189
37940
|
var import_node_child_process15 = require("node:child_process");
|
|
37941
|
+
var import_node_fs39 = require("node:fs");
|
|
37942
|
+
var import_node_os19 = require("node:os");
|
|
37943
|
+
var import_node_path36 = require("node:path");
|
|
37944
|
+
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
37945
|
+
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
37946
|
+
function isReviewVerdict(value) {
|
|
37947
|
+
return typeof value === "string" && REVIEW_VERDICTS.includes(value);
|
|
37948
|
+
}
|
|
37949
|
+
function renderReviewVerdictComment(input) {
|
|
37950
|
+
const payload = {
|
|
37951
|
+
v: 1,
|
|
37952
|
+
patch: input.patch,
|
|
37953
|
+
head: input.head,
|
|
37954
|
+
verdict: input.verdict,
|
|
37955
|
+
scope: input.scope,
|
|
37956
|
+
risk: input.risk,
|
|
37957
|
+
unverified: input.unverified,
|
|
37958
|
+
reviewer: input.reviewer
|
|
37959
|
+
};
|
|
37960
|
+
const findings = input.findings?.trim();
|
|
37961
|
+
return `${REVIEW_VERDICT_MARKER}
|
|
37962
|
+
\`\`\`json
|
|
37963
|
+
${JSON.stringify(payload, null, 2)}
|
|
37964
|
+
\`\`\`
|
|
37965
|
+
${findings ? `
|
|
37966
|
+
${findings}
|
|
37967
|
+
` : ""}`;
|
|
37968
|
+
}
|
|
37969
|
+
function isReviewVerdictComment(body) {
|
|
37970
|
+
return body.trimStart().startsWith(REVIEW_VERDICT_MARKER);
|
|
37971
|
+
}
|
|
37972
|
+
var FENCE_RE = /^(?:`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n(?:`{3,}|~{3,})\s*$/m;
|
|
37973
|
+
function parseReviewVerdictComment(body) {
|
|
37974
|
+
if (!isReviewVerdictComment(body)) return void 0;
|
|
37975
|
+
const rest = body.trimStart().slice(REVIEW_VERDICT_MARKER.length);
|
|
37976
|
+
const fence = FENCE_RE.exec(rest);
|
|
37977
|
+
if (!fence) return void 0;
|
|
37978
|
+
let parsed;
|
|
37979
|
+
try {
|
|
37980
|
+
parsed = JSON.parse(fence[1]);
|
|
37981
|
+
} catch {
|
|
37982
|
+
return void 0;
|
|
37983
|
+
}
|
|
37984
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
37985
|
+
const p = parsed;
|
|
37986
|
+
if (p.v !== 1 || !isReviewVerdict(p.verdict)) return void 0;
|
|
37987
|
+
if (typeof p.patch !== "string" || !/^[0-9a-f]{40}$/.test(p.patch)) return void 0;
|
|
37988
|
+
if (typeof p.head !== "string" || typeof p.scope !== "string" || typeof p.risk !== "string" || typeof p.reviewer !== "string") return void 0;
|
|
37989
|
+
const unverified = Array.isArray(p.unverified) ? p.unverified.filter((u) => typeof u === "string") : [];
|
|
37990
|
+
return { v: 1, patch: p.patch, head: p.head, verdict: p.verdict, scope: p.scope, risk: p.risk, unverified, reviewer: p.reviewer };
|
|
37991
|
+
}
|
|
37992
|
+
function latestReviewComment(comments) {
|
|
37993
|
+
let latest;
|
|
37994
|
+
for (const c of comments) {
|
|
37995
|
+
if (!isReviewVerdictComment(c.body)) continue;
|
|
37996
|
+
if (!latest || c.createdAt > latest.createdAt || c.createdAt === latest.createdAt && (c.id ?? 0) >= (latest.id ?? 0)) latest = c;
|
|
37997
|
+
}
|
|
37998
|
+
return latest;
|
|
37999
|
+
}
|
|
38000
|
+
function evaluateReviewVerdict(comments, currentPatchId) {
|
|
38001
|
+
const latest = latestReviewComment(comments);
|
|
38002
|
+
if (!latest) return { ok: false, reason: "none" };
|
|
38003
|
+
const payload = parseReviewVerdictComment(latest.body);
|
|
38004
|
+
if (!payload) return { ok: false, reason: "malformed" };
|
|
38005
|
+
if (payload.verdict !== "PROCEED") return { ok: false, reason: "not-proceed", verdict: payload.verdict, patch: payload.patch };
|
|
38006
|
+
if (payload.patch !== currentPatchId) return { ok: false, reason: "stale", verdict: payload.verdict, patch: payload.patch };
|
|
38007
|
+
return { ok: true, reason: "proceed", verdict: payload.verdict, patch: payload.patch };
|
|
38008
|
+
}
|
|
38009
|
+
function evaluateTrustedReviewVerdict(comments, patch, head) {
|
|
38010
|
+
const latest = latestReviewComment(comments);
|
|
38011
|
+
if (!latest) return { ok: false, reason: "none" };
|
|
38012
|
+
const identity = { commentId: latest.id, commentCreatedAt: latest.createdAt };
|
|
38013
|
+
if (latest.author?.toLowerCase() !== "jervaise") return { ...identity, ok: false, reason: "untrusted-author" };
|
|
38014
|
+
const evaluation = evaluateReviewVerdict([latest], patch);
|
|
38015
|
+
if (!evaluation.ok) return { ...identity, ok: false, reason: evaluation.reason };
|
|
38016
|
+
if (parseReviewVerdictComment(latest.body)?.head !== head) return { ...identity, ok: false, reason: "stale-head" };
|
|
38017
|
+
return { ...identity, ok: true, reason: "proceed" };
|
|
38018
|
+
}
|
|
38019
|
+
async function checkPrReview(number, repo, head, deps = {
|
|
38020
|
+
readHead: readPrHeadSha,
|
|
38021
|
+
readComments: readPrIssueComments,
|
|
38022
|
+
computePatch: computePrPatchId
|
|
38023
|
+
}) {
|
|
38024
|
+
const base = { repo, number, head };
|
|
38025
|
+
if (!/^[1-9][0-9]*$/.test(number) || !/^[\w.-]+\/[\w.-]+$/.test(repo) || !/^[0-9a-f]{40}$/.test(head)) {
|
|
38026
|
+
return { ...base, ok: false, reason: "invalid-input", detail: "expected a PR number, owner/repo and exact lowercase 40-character head SHA" };
|
|
38027
|
+
}
|
|
38028
|
+
try {
|
|
38029
|
+
if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
|
|
38030
|
+
const [comments, patch] = await Promise.all([deps.readComments(number, repo), deps.computePatch(number, repo)]);
|
|
38031
|
+
if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
|
|
38032
|
+
return { ...base, patch, ...evaluateTrustedReviewVerdict(comments, patch, head) };
|
|
38033
|
+
} catch (e) {
|
|
38034
|
+
return { ...base, ok: false, reason: "unreadable", detail: e.message };
|
|
38035
|
+
}
|
|
38036
|
+
}
|
|
38037
|
+
function computePrPatchId(number, repo) {
|
|
38038
|
+
return new Promise((resolve7, reject) => {
|
|
38039
|
+
const gh = (0, import_node_child_process15.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
38040
|
+
const git3 = (0, import_node_child_process15.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
38041
|
+
let out = "";
|
|
38042
|
+
let ghErr = "";
|
|
38043
|
+
let gitErr = "";
|
|
38044
|
+
const timer = setTimeout(() => {
|
|
38045
|
+
gh.kill();
|
|
38046
|
+
git3.kill();
|
|
38047
|
+
reject(new Error(`patch-id: timed out after ${GC_GH_TIMEOUT_MS4}ms`));
|
|
38048
|
+
}, GC_GH_TIMEOUT_MS4);
|
|
38049
|
+
gh.stdout.pipe(git3.stdin);
|
|
38050
|
+
gh.stderr.on("data", (d) => {
|
|
38051
|
+
ghErr += d.toString();
|
|
38052
|
+
});
|
|
38053
|
+
git3.stderr.on("data", (d) => {
|
|
38054
|
+
gitErr += d.toString();
|
|
38055
|
+
});
|
|
38056
|
+
git3.stdout.on("data", (d) => {
|
|
38057
|
+
out += d.toString();
|
|
38058
|
+
});
|
|
38059
|
+
gh.on("error", (e) => {
|
|
38060
|
+
clearTimeout(timer);
|
|
38061
|
+
reject(e);
|
|
38062
|
+
});
|
|
38063
|
+
git3.on("error", (e) => {
|
|
38064
|
+
clearTimeout(timer);
|
|
38065
|
+
reject(e);
|
|
38066
|
+
});
|
|
38067
|
+
let ghCode;
|
|
38068
|
+
let gitCode;
|
|
38069
|
+
const finish = () => {
|
|
38070
|
+
if (ghCode === void 0 || gitCode === void 0) return;
|
|
38071
|
+
clearTimeout(timer);
|
|
38072
|
+
if (ghCode !== 0) return reject(Object.assign(new Error(`gh pr diff ${number} --repo ${repo} exited ${ghCode}: ${ghErr.trim()}`), { stderr: ghErr }));
|
|
38073
|
+
if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
|
|
38074
|
+
const id = out.trim().split(/\s+/)[0];
|
|
38075
|
+
if (!/^[0-9a-f]{40}$/.test(id ?? "")) return reject(new Error(`patch-id: PR #${number} diff is empty or unreadable (gh: ${ghErr.trim() || "no stderr"})`));
|
|
38076
|
+
resolve7(id);
|
|
38077
|
+
};
|
|
38078
|
+
gh.on("close", (code) => {
|
|
38079
|
+
ghCode = code;
|
|
38080
|
+
finish();
|
|
38081
|
+
});
|
|
38082
|
+
git3.on("close", (code) => {
|
|
38083
|
+
gitCode = code;
|
|
38084
|
+
finish();
|
|
38085
|
+
});
|
|
38086
|
+
git3.stdin.on("error", (e) => {
|
|
38087
|
+
clearTimeout(timer);
|
|
38088
|
+
gh.kill();
|
|
38089
|
+
git3.kill();
|
|
38090
|
+
reject(e);
|
|
38091
|
+
});
|
|
38092
|
+
});
|
|
38093
|
+
}
|
|
38094
|
+
async function readPrHeadSha(number, repo) {
|
|
38095
|
+
const { stdout } = await execFileP("gh", ["api", `repos/${repo}/pulls/${number}`, "--jq", ".head.sha"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
38096
|
+
const sha = stdout.trim();
|
|
38097
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error(`could not read PR #${number} head sha`);
|
|
38098
|
+
return sha;
|
|
38099
|
+
}
|
|
38100
|
+
async function readPrIssueComments(number, repo) {
|
|
38101
|
+
const { stdout } = await execFileP("gh", ["api", "--paginate", `repos/${repo}/issues/${number}/comments?per_page=100`, "--jq", ".[] | {id, body, createdAt: .created_at, author: .user.login}"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
38102
|
+
const comments = parseNdjsonLines(stdout);
|
|
38103
|
+
if (comments.some((c) => !c || !Number.isSafeInteger(c.id) || c.id <= 0 || typeof c.body !== "string" || typeof c.createdAt !== "string" || !Number.isFinite(Date.parse(c.createdAt)) || c.author !== null && typeof c.author !== "string")) throw new Error("unreadable PR comment identity");
|
|
38104
|
+
return comments;
|
|
38105
|
+
}
|
|
38106
|
+
async function postPrCommentFromFile(number, repo, body) {
|
|
38107
|
+
const dir = (0, import_node_fs39.mkdtempSync)((0, import_node_path36.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
|
|
38108
|
+
const path2 = (0, import_node_path36.join)(dir, "body.md");
|
|
38109
|
+
try {
|
|
38110
|
+
(0, import_node_fs39.writeFileSync)(path2, body, "utf8");
|
|
38111
|
+
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
38112
|
+
return stdout.trim();
|
|
38113
|
+
} finally {
|
|
38114
|
+
try {
|
|
38115
|
+
(0, import_node_fs39.rmSync)(dir, { recursive: true, force: true });
|
|
38116
|
+
} catch {
|
|
38117
|
+
}
|
|
38118
|
+
}
|
|
38119
|
+
}
|
|
38120
|
+
|
|
38121
|
+
// src/pr-create-docs-check.ts
|
|
38122
|
+
var import_node_child_process16 = require("node:child_process");
|
|
38190
38123
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
38191
38124
|
function catFileBatch(root, ref, paths) {
|
|
38192
38125
|
if (paths.length === 0) return Promise.resolve([]);
|
|
38193
38126
|
return new Promise((resolve7) => {
|
|
38194
38127
|
const chunks = [];
|
|
38195
38128
|
let settled = false;
|
|
38196
|
-
const child2 = (0,
|
|
38129
|
+
const child2 = (0, import_node_child_process16.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
|
|
38197
38130
|
const finish = () => {
|
|
38198
38131
|
if (settled) return;
|
|
38199
38132
|
settled = true;
|
|
@@ -39309,7 +39242,7 @@ function ciAuditDeps() {
|
|
|
39309
39242
|
}
|
|
39310
39243
|
};
|
|
39311
39244
|
}
|
|
39312
|
-
async function
|
|
39245
|
+
async function ghJson(args, timeout = 1e4) {
|
|
39313
39246
|
const { stdout } = await execFileP("gh", args, { timeout });
|
|
39314
39247
|
return JSON.parse(stdout);
|
|
39315
39248
|
}
|
|
@@ -39387,7 +39320,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
39387
39320
|
try {
|
|
39388
39321
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
39389
39322
|
if (o.repo) args.push("--repo", o.repo);
|
|
39390
|
-
spawnDetachedSelf(args, { spawn:
|
|
39323
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process17.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
39391
39324
|
} catch {
|
|
39392
39325
|
}
|
|
39393
39326
|
}
|
|
@@ -39596,7 +39529,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39596
39529
|
async function readParentField(number, repo) {
|
|
39597
39530
|
let payload;
|
|
39598
39531
|
try {
|
|
39599
|
-
payload = await
|
|
39532
|
+
payload = await ghJson(["api", `repos/${repo}/issues/${number}`]);
|
|
39600
39533
|
} catch (e) {
|
|
39601
39534
|
const err = e;
|
|
39602
39535
|
return { parentReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -39628,7 +39561,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39628
39561
|
emit(data2, await readParentField(n, repo));
|
|
39629
39562
|
return;
|
|
39630
39563
|
}
|
|
39631
|
-
const data = await
|
|
39564
|
+
const data = await ghJson(["issue", "view", String(n), "--repo", repo, "--json", gh.ghFields]);
|
|
39632
39565
|
emit(data, await readParentField(n, repo));
|
|
39633
39566
|
} catch (e) {
|
|
39634
39567
|
const err = e;
|
|
@@ -39643,7 +39576,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39643
39576
|
const repo = await resolveRepo(o.repo);
|
|
39644
39577
|
if (!repo) return fail("issue discover-related: could not resolve repo");
|
|
39645
39578
|
try {
|
|
39646
|
-
const issues = await
|
|
39579
|
+
const issues = await ghJson([
|
|
39647
39580
|
"issue",
|
|
39648
39581
|
"list",
|
|
39649
39582
|
"--repo",
|
|
@@ -39658,7 +39591,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39658
39591
|
const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
|
|
39659
39592
|
if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
|
|
39660
39593
|
if (!candidates.length) return;
|
|
39661
|
-
const viewed = await
|
|
39594
|
+
const viewed = await ghJson([
|
|
39662
39595
|
"issue",
|
|
39663
39596
|
"view",
|
|
39664
39597
|
String(number),
|
|
@@ -39732,7 +39665,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39732
39665
|
const checked = o.off !== true;
|
|
39733
39666
|
let body;
|
|
39734
39667
|
try {
|
|
39735
|
-
const viewed = await
|
|
39668
|
+
const viewed = await ghJson(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
|
|
39736
39669
|
body = viewed.body ?? "";
|
|
39737
39670
|
} catch (e) {
|
|
39738
39671
|
return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
|
|
@@ -39834,7 +39767,7 @@ ${list}`);
|
|
|
39834
39767
|
if (!o.force) {
|
|
39835
39768
|
let openLessons = [];
|
|
39836
39769
|
try {
|
|
39837
|
-
openLessons = await
|
|
39770
|
+
openLessons = await ghJson([
|
|
39838
39771
|
"issue",
|
|
39839
39772
|
"list",
|
|
39840
39773
|
"--repo",
|
|
@@ -39940,7 +39873,7 @@ ${list}`);
|
|
|
39940
39873
|
console.log(JSON.stringify(data2));
|
|
39941
39874
|
return;
|
|
39942
39875
|
}
|
|
39943
|
-
const data = await
|
|
39876
|
+
const data = await ghJson(["pr", "view", String(n), "--repo", repo, "--json", effective]);
|
|
39944
39877
|
console.log(JSON.stringify(data));
|
|
39945
39878
|
} catch (e) {
|
|
39946
39879
|
const err = e;
|
|
@@ -40205,7 +40138,7 @@ ${list}`);
|
|
|
40205
40138
|
await readClosingGuardInput(number, repoArgs, landRepoForGuard, "pr land"),
|
|
40206
40139
|
async (n) => {
|
|
40207
40140
|
if (!landRepoForGuard) return void 0;
|
|
40208
|
-
const viewed = await
|
|
40141
|
+
const viewed = await ghJson(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
|
|
40209
40142
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
40210
40143
|
}
|
|
40211
40144
|
);
|
|
@@ -40223,14 +40156,6 @@ ${list}`);
|
|
|
40223
40156
|
}
|
|
40224
40157
|
if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
|
|
40225
40158
|
const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
|
|
40226
|
-
queueMerge: async (prNumber, repo) => {
|
|
40227
|
-
const meta = await ghJson2(["pr", "view", prNumber, "--repo", repo, "--json", "baseRefName,headRefOid"]);
|
|
40228
|
-
if (!await usesMergifyPilot(repo, meta.baseRefName)) return void 0;
|
|
40229
|
-
if (!landClosingGuardInput) throw new Error("pr land: Mergify requires a readable closing-keyword guard");
|
|
40230
|
-
const guard = evaluateClosingGuard(landClosingGuardInput, { force: o.force, context: "pr land", squashBodyText: landClosingGuardInput.text });
|
|
40231
|
-
if (guard.blocked) throw new Error(guard.message);
|
|
40232
|
-
return requestMergifyMerge(prNumber, repo, landClosingGuardInput.text, meta.headRefOid);
|
|
40233
|
-
},
|
|
40234
40159
|
resolveRepo: async (prNumber, repoOpt) => {
|
|
40235
40160
|
const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
|
|
40236
40161
|
const viewed = (await execFileP("gh", ["pr", "view", prNumber, ...args, "--json", "headRepository,baseRefName", "--jq", '.headRepository.nameWithOwner + " " + .baseRefName'], { timeout: GC_GH_TIMEOUT_MS4 })).stdout.trim();
|
|
@@ -40388,23 +40313,20 @@ ${list}`);
|
|
|
40388
40313
|
const baseRef = prMeta.base;
|
|
40389
40314
|
const headRefOid = (prMeta.oid ?? "").trim() || void 0;
|
|
40390
40315
|
if (!repoForPostCleanup) throw new Error("pr merge: cannot resolve target repository");
|
|
40391
|
-
const mergifyPilot = await usesMergifyPilot(repoForPostCleanup, baseRef);
|
|
40392
|
-
if (mergifyPilot && (method !== "--squash" || o.squashBodyFile)) throw new Error("pr merge: Mergify pilot uses the protected squash title/body policy; custom merge methods and body files are unsupported");
|
|
40393
40316
|
const devDeployDeps = repoForPostCleanup ? registryClientDeps(await loadConfig()) : void 0;
|
|
40394
40317
|
const devDeployPlan = repoForPostCleanup && devDeployDeps ? await planDevDeployOnDevelopmentMerge(repoForPostCleanup, baseRef, devDeployDeps).catch(() => ({ applicable: false, reason: "unreadable" })) : { applicable: false, reason: "unreadable" };
|
|
40395
40318
|
const closingGuardInput = await withAlreadyClosedCommitTargets(
|
|
40396
40319
|
await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
|
|
40397
40320
|
async (n) => {
|
|
40398
40321
|
if (!repoForPostCleanup) return void 0;
|
|
40399
|
-
const viewed = await
|
|
40322
|
+
const viewed = await ghJson(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
|
|
40400
40323
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
40401
40324
|
}
|
|
40402
40325
|
);
|
|
40403
40326
|
if (o.squashBodyFile && method !== "--squash") {
|
|
40404
40327
|
throw new Error("pr merge: --squash-body-file applies only to squash merges");
|
|
40405
40328
|
}
|
|
40406
|
-
|
|
40407
|
-
const mergeSquashBody = mergifyPilot ? closingGuardInput.text : squashBodyTextForMerge(
|
|
40329
|
+
const mergeSquashBody = squashBodyTextForMerge(
|
|
40408
40330
|
closingGuardInput,
|
|
40409
40331
|
method === "--squash",
|
|
40410
40332
|
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
@@ -40502,7 +40424,7 @@ ${list}`);
|
|
|
40502
40424
|
}
|
|
40503
40425
|
return true;
|
|
40504
40426
|
};
|
|
40505
|
-
if (
|
|
40427
|
+
if (o.wait && !await runWaitGate()) return;
|
|
40506
40428
|
if (ciPolicy.policy === "no-ci") {
|
|
40507
40429
|
const guard = decidePrMergeNoCiGuard(await pollGhPrChecks(number, repoArgs), ciPolicy.reason);
|
|
40508
40430
|
if (guard.action === "refuse") throw new Error(`gh pr merge ${number}: ${guard.message}`);
|
|
@@ -40511,7 +40433,7 @@ ${list}`);
|
|
|
40511
40433
|
const remoteBefore = await remoteBranchExists2(headRef, { remote });
|
|
40512
40434
|
let upgradedToAuto = false;
|
|
40513
40435
|
let remoteNotAttemptedReason = "preserved-delayed-cleanup";
|
|
40514
|
-
const overrideBody =
|
|
40436
|
+
const overrideBody = mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
|
|
40515
40437
|
number,
|
|
40516
40438
|
repoArgs,
|
|
40517
40439
|
async (a, t) => (await execFileP("gh", a, { timeout: t })).stdout,
|
|
@@ -40591,15 +40513,7 @@ ${list}`);
|
|
|
40591
40513
|
});
|
|
40592
40514
|
try {
|
|
40593
40515
|
try {
|
|
40594
|
-
|
|
40595
|
-
const queue = await requestMergifyMerge(number, repoForPostCleanup, closingGuardInput.text, headRefOid);
|
|
40596
|
-
if (queue.state !== "merged") {
|
|
40597
|
-
console.log(JSON.stringify({ mergeStatus: "failed", pr: number, repo: repoForPostCleanup, queue }));
|
|
40598
|
-
process.exitCode = queue.state === "refused" ? 1 : PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
40599
|
-
return;
|
|
40600
|
-
}
|
|
40601
|
-
remoteNotAttemptedReason = "pr-already-merged";
|
|
40602
|
-
} else await mergeOnce();
|
|
40516
|
+
await mergeOnce();
|
|
40603
40517
|
} catch (e) {
|
|
40604
40518
|
if (!(e instanceof PrHeadBehindBaseError)) throw e;
|
|
40605
40519
|
const localCheckedOut = !foreignCwd && await prHeadCheckedOutHere(headRef, targetRepo2, Boolean(o.repo));
|
|
@@ -41070,7 +40984,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
41070
40984
|
}
|
|
41071
40985
|
|
|
41072
40986
|
// src/dist-drift.ts
|
|
41073
|
-
var
|
|
40987
|
+
var import_node_child_process18 = require("node:child_process");
|
|
41074
40988
|
var import_node_crypto13 = require("node:crypto");
|
|
41075
40989
|
var import_node_fs46 = require("node:fs");
|
|
41076
40990
|
var import_node_os21 = require("node:os");
|
|
@@ -41215,7 +41129,7 @@ function bomPathFor(root) {
|
|
|
41215
41129
|
}
|
|
41216
41130
|
}
|
|
41217
41131
|
function rebuildTo(packageRoot, outDir) {
|
|
41218
|
-
(0,
|
|
41132
|
+
(0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
|
|
41219
41133
|
cwd: packageRoot,
|
|
41220
41134
|
env: { ...process.env, MMI_DIST_OUTDIR: outDir },
|
|
41221
41135
|
windowsHide: true,
|
|
@@ -41433,7 +41347,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
41433
41347
|
}
|
|
41434
41348
|
|
|
41435
41349
|
// src/spawn-policy-core.ts
|
|
41436
|
-
var
|
|
41350
|
+
var import_node_child_process19 = require("node:child_process");
|
|
41437
41351
|
var import_node_fs47 = require("node:fs");
|
|
41438
41352
|
var import_node_path44 = require("node:path");
|
|
41439
41353
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
@@ -41504,7 +41418,7 @@ function findViolationsInSource(raw) {
|
|
|
41504
41418
|
return found;
|
|
41505
41419
|
}
|
|
41506
41420
|
function policedFiles(root) {
|
|
41507
|
-
const r = (0,
|
|
41421
|
+
const r = (0, import_node_child_process19.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
41508
41422
|
cwd: root,
|
|
41509
41423
|
encoding: "utf8",
|
|
41510
41424
|
windowsHide: true,
|
|
@@ -43041,12 +42955,12 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = H
|
|
|
43041
42955
|
}
|
|
43042
42956
|
|
|
43043
42957
|
// src/hotfix-coverage.ts
|
|
43044
|
-
var
|
|
42958
|
+
var import_node_child_process20 = require("node:child_process");
|
|
43045
42959
|
var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
43046
42960
|
function checkHotfixCoverage(options = {}) {
|
|
43047
42961
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
43048
42962
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
43049
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
42963
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process20.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
43050
42964
|
const revList = (range) => {
|
|
43051
42965
|
const out = git3(["rev-list", "--no-merges", range]).trim();
|
|
43052
42966
|
return out ? out.split("\n") : [];
|
|
@@ -43114,7 +43028,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
43114
43028
|
}
|
|
43115
43029
|
function checkHotfixCarries(options) {
|
|
43116
43030
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
43117
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
43031
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process20.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
43118
43032
|
const isAncestor = (sha, ref) => {
|
|
43119
43033
|
try {
|
|
43120
43034
|
git3(["merge-base", "--is-ancestor", sha, ref]);
|
package/package.json
CHANGED