@mutmutco/cli 4.6.33 → 4.6.35
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 +178 -48
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -15497,6 +15497,7 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
|
|
|
15497
15497
|
["run cancel", { metadata: "read", actions: "write" }],
|
|
15498
15498
|
// #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
|
|
15499
15499
|
["run rerun", { metadata: "read", actions: "write" }],
|
|
15500
|
+
["run dispatch", { metadata: "read", actions: "write" }],
|
|
15500
15501
|
// #9780: repository Actions variables — get/list mint only Variables:read; create/update/delete
|
|
15501
15502
|
// Variables:write. Repository scope only; no secret store, no rename, no workflow dispatch.
|
|
15502
15503
|
// #9790: the token key is `actions_variables` (as ci-reconcile.yml mints); `variables` was refused 422.
|
|
@@ -28366,10 +28367,10 @@ var rollout_plan_default = {
|
|
|
28366
28367
|
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)."
|
|
28367
28368
|
},
|
|
28368
28369
|
baseline: {
|
|
28369
|
-
version: "4.6.
|
|
28370
|
-
tag: "v4.6.
|
|
28371
|
-
commit: "
|
|
28372
|
-
npm: "@mutmutco/cli@4.6.
|
|
28370
|
+
version: "4.6.35",
|
|
28371
|
+
tag: "v4.6.35",
|
|
28372
|
+
commit: "2a54cf011975",
|
|
28373
|
+
npm: "@mutmutco/cli@4.6.35"
|
|
28373
28374
|
},
|
|
28374
28375
|
exitCriterion: "fleet-n-of-n",
|
|
28375
28376
|
hubOnlyShortcut: "forbidden",
|
|
@@ -28386,14 +28387,14 @@ var rollout_plan_default = {
|
|
|
28386
28387
|
repo: "mutmutco/mmi-hub",
|
|
28387
28388
|
role: "canary",
|
|
28388
28389
|
schedule: "train",
|
|
28389
|
-
v3Target: "v4.6.
|
|
28390
|
+
v3Target: "v4.6.35"
|
|
28390
28391
|
}
|
|
28391
28392
|
],
|
|
28392
28393
|
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.",
|
|
28393
28394
|
rollback: {
|
|
28394
28395
|
independent: true,
|
|
28395
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
28396
|
-
v3Target: "v4.6.
|
|
28396
|
+
mechanism: "npm dist-tag latest -> 4.6.35 and redeploy the Hub Lambda from tag v4.6.35 (2a54cf011975); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
28397
|
+
v3Target: "v4.6.35 (@mutmutco/cli@4.6.35, tag commit 2a54cf011975 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
28397
28398
|
}
|
|
28398
28399
|
},
|
|
28399
28400
|
{
|
|
@@ -30698,6 +30699,8 @@ var CORRELATE_ATTEMPTS = 5;
|
|
|
30698
30699
|
var CORRELATE_DELAY_MS = 1500;
|
|
30699
30700
|
var CORRELATE_SKEW_SLACK_MS = 1e4;
|
|
30700
30701
|
var CORRELATE_PAGE_LIMIT = 50;
|
|
30702
|
+
var GET_PUBLISH_CORRELATE_ATTEMPTS = 12;
|
|
30703
|
+
var GET_PUBLISH_CORRELATE_DELAY_MS = 2500;
|
|
30701
30704
|
var RUN_CONFIRM_ATTEMPTS = 3;
|
|
30702
30705
|
var RUN_CONFIRM_DELAY_MS = 1e3;
|
|
30703
30706
|
var defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
@@ -30744,11 +30747,13 @@ async function correlateRun(deps, args) {
|
|
|
30744
30747
|
const sleep2 = resolveSleep(deps);
|
|
30745
30748
|
const threshold = args.since - CORRELATE_SKEW_SLACK_MS;
|
|
30746
30749
|
const repo = args.mode === "workflow" ? args.repo ?? HUB_REPO2 : HUB_REPO2;
|
|
30750
|
+
const attempts = args.mode === "dispatch" ? args.attempts ?? CORRELATE_ATTEMPTS : CORRELATE_ATTEMPTS;
|
|
30751
|
+
const delayMs = args.mode === "dispatch" ? args.delayMs ?? CORRELATE_DELAY_MS : CORRELATE_DELAY_MS;
|
|
30747
30752
|
let lastError;
|
|
30748
30753
|
let parsedAnyResponse = false;
|
|
30749
30754
|
let truncatedWindow = false;
|
|
30750
|
-
for (let attempt = 0; attempt <
|
|
30751
|
-
if (attempt > 0) await sleep2(
|
|
30755
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
30756
|
+
if (attempt > 0) await sleep2(delayMs);
|
|
30752
30757
|
const listArgs = [
|
|
30753
30758
|
"run",
|
|
30754
30759
|
"list",
|
|
@@ -30791,7 +30796,7 @@ async function correlateRun(deps, args) {
|
|
|
30791
30796
|
if (!parsedAnyResponse) {
|
|
30792
30797
|
return {
|
|
30793
30798
|
read: "failed",
|
|
30794
|
-
error: `\`gh run list --workflow ${args.workflow} --repo ${repo}\` failed on all ${
|
|
30799
|
+
error: `\`gh run list --workflow ${args.workflow} --repo ${repo}\` failed on all ${attempts} attempts${lastError ? ` (${describeReadError(lastError)})` : ""}`
|
|
30795
30800
|
};
|
|
30796
30801
|
}
|
|
30797
30802
|
if (truncatedWindow) {
|
|
@@ -30806,7 +30811,7 @@ function correlateTenantRun(deps, since, titleIncludes) {
|
|
|
30806
30811
|
return correlateRun(deps, { workflow: "tenant-deploy.yml", since, mode: "dispatch", titleIncludes });
|
|
30807
30812
|
}
|
|
30808
30813
|
function correlateGetPublishRun(deps, since, product, mode) {
|
|
30809
|
-
return correlateRun(deps, { workflow: "get-publish.yml", since, mode: "dispatch", titleIncludes: [product, ...mode ? [mode] : []], ...mode ? { requireTitleMatch: true } : {} });
|
|
30814
|
+
return correlateRun(deps, { workflow: "get-publish.yml", since, mode: "dispatch", titleIncludes: [product, ...mode ? [mode] : []], ...mode ? { requireTitleMatch: true } : {}, attempts: GET_PUBLISH_CORRELATE_ATTEMPTS, delayMs: GET_PUBLISH_CORRELATE_DELAY_MS });
|
|
30810
30815
|
}
|
|
30811
30816
|
function correlateControlRun(deps, since, titleIncludes) {
|
|
30812
30817
|
return correlateRun(deps, { workflow: "tenant-control.yml", since, mode: "dispatch", titleIncludes, titleFieldsFrom: 2, ...titleIncludes?.length ? { requireTitleMatch: true } : {} });
|
|
@@ -31937,7 +31942,7 @@ async function fetchServedChannelVersion(url, fetchImpl = fetch) {
|
|
|
31937
31942
|
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
31938
31943
|
}
|
|
31939
31944
|
}
|
|
31940
|
-
async function findPriorGreenDispatch(deps, repo, workflow, sha, titleTokens) {
|
|
31945
|
+
async function findPriorGreenDispatch(deps, repo, workflow, sha, titleTokens, opts = {}) {
|
|
31941
31946
|
if (!/^[0-9a-f]{40}$/i.test(sha)) return void 0;
|
|
31942
31947
|
try {
|
|
31943
31948
|
const rows = JSON.parse(await deps.run("gh", [
|
|
@@ -31956,12 +31961,18 @@ async function findPriorGreenDispatch(deps, repo, workflow, sha, titleTokens) {
|
|
|
31956
31961
|
"--json",
|
|
31957
31962
|
"databaseId,url,status,conclusion,displayTitle,headSha"
|
|
31958
31963
|
]));
|
|
31959
|
-
const
|
|
31960
|
-
|
|
31964
|
+
const titleMatch = (r) => titleTokens.every((t) => (r.displayTitle ?? "").split(/[^\w.-]+/).includes(t));
|
|
31965
|
+
const atSha = (r) => r.headSha?.toLowerCase() === sha.toLowerCase();
|
|
31966
|
+
const hit = rows.find((r) => typeof r.databaseId === "number" && atSha(r) && r.status === "completed" && r.conclusion === "success" && titleMatch(r));
|
|
31967
|
+
if (hit) return { runId: hit.databaseId, ...hit.url ? { runUrl: hit.url } : {} };
|
|
31968
|
+
if (!opts.includeActive) return void 0;
|
|
31969
|
+
const active = rows.find((r) => typeof r.databaseId === "number" && atSha(r) && PRIOR_DISPATCH_ACTIVE_STATUSES.has(r.status ?? "") && titleMatch(r));
|
|
31970
|
+
return active ? { runId: active.databaseId, ...active.url ? { runUrl: active.url } : {}, active: true } : void 0;
|
|
31961
31971
|
} catch {
|
|
31962
31972
|
return void 0;
|
|
31963
31973
|
}
|
|
31964
31974
|
}
|
|
31975
|
+
var PRIOR_DISPATCH_ACTIVE_STATUSES = /* @__PURE__ */ new Set(["in_progress", "queued", "waiting", "requested", "pending"]);
|
|
31965
31976
|
function planeFailureRow(workflow, evidence) {
|
|
31966
31977
|
return { workflow, runUrlNote: evidence, conclusion: "failure" };
|
|
31967
31978
|
}
|
|
@@ -32040,10 +32051,13 @@ async function appendMmiGetRedeploy(deps, input, dispatch) {
|
|
|
32040
32051
|
async function appendServedChannelStage(deps, input, dispatch) {
|
|
32041
32052
|
if (!isHubControlRepo(input.repo)) return dispatch;
|
|
32042
32053
|
const remedy = `${getPublishDispatchCommand(input.repo)} -f mode=stage`;
|
|
32043
|
-
const prior = await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "stage"]);
|
|
32054
|
+
const prior = await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "stage"], { includeActive: true });
|
|
32044
32055
|
let row;
|
|
32045
|
-
if (prior) {
|
|
32056
|
+
if (prior && !prior.active) {
|
|
32046
32057
|
row = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {}, conclusion: "success" };
|
|
32058
|
+
} else if (prior?.active) {
|
|
32059
|
+
row = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {}, conclusion: "pending" };
|
|
32060
|
+
if (input.watch) row.conclusion = await watchTenantRun(deps, prior.runId, input.repo);
|
|
32047
32061
|
} else {
|
|
32048
32062
|
const since = (deps.now ?? Date.now)();
|
|
32049
32063
|
try {
|
|
@@ -32058,7 +32072,7 @@ async function appendServedChannelStage(deps, input, dispatch) {
|
|
|
32058
32072
|
};
|
|
32059
32073
|
}
|
|
32060
32074
|
const correlated = await correlateGetPublishRun(deps, since, HUB_SERVED_PRODUCT, "stage");
|
|
32061
|
-
if (correlated.read
|
|
32075
|
+
if (correlated.read === "failed") {
|
|
32062
32076
|
return {
|
|
32063
32077
|
...dispatch,
|
|
32064
32078
|
note: `${dispatch.note}; get-publish stage dispatched but its run could not be READ \u2014 check Actions; recover with \`${remedy}\``,
|
|
@@ -32066,6 +32080,14 @@ async function appendServedChannelStage(deps, input, dispatch) {
|
|
|
32066
32080
|
workflowRuns: [...dispatch.workflowRuns ?? [], planeFailureRow("get-publish.yml", "stage dispatched, but its run could not be read")]
|
|
32067
32081
|
};
|
|
32068
32082
|
}
|
|
32083
|
+
if (correlated.read === "absent") {
|
|
32084
|
+
const dispatchedAt = new Date(since).toISOString();
|
|
32085
|
+
return {
|
|
32086
|
+
...dispatch,
|
|
32087
|
+
note: `${dispatch.note}; get-publish stage dispatched at ${dispatchedAt} but its run is not listed yet \u2014 recorded PENDING; \`devops release --resume\` re-correlates it by title and time`,
|
|
32088
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "get-publish.yml", runUrlNote: `stage dispatched ${dispatchedAt}; run not listed within the correlation window`, conclusion: "pending", dispatchedAt, titleTokens: [HUB_SERVED_PRODUCT, "stage"] }]
|
|
32089
|
+
};
|
|
32090
|
+
}
|
|
32069
32091
|
row = { workflow: "get-publish.yml", runId: correlated.runId, ...correlated.runUrl ? { runUrl: correlated.runUrl } : {}, conclusion: "pending" };
|
|
32070
32092
|
if (input.watch) row.conclusion = await watchTenantRun(deps, correlated.runId, input.repo);
|
|
32071
32093
|
}
|
|
@@ -32090,12 +32112,15 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32090
32112
|
if (input.publishStatus !== "success") {
|
|
32091
32113
|
return withNote(`served channel NOT moved (publish did not succeed) \u2014 after the publish is green, run \`${remedy}\``);
|
|
32092
32114
|
}
|
|
32093
|
-
const prior = input.releaseSha ? await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "pointer"]) : void 0;
|
|
32115
|
+
const prior = input.releaseSha ? await findPriorGreenDispatch(deps, input.repo, "get-publish.yml", input.releaseSha, [HUB_SERVED_PRODUCT, "pointer"], { includeActive: true }) : void 0;
|
|
32094
32116
|
let runRow;
|
|
32095
32117
|
let runStatus;
|
|
32096
|
-
if (prior) {
|
|
32118
|
+
if (prior && !prior.active) {
|
|
32097
32119
|
runRow = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {} };
|
|
32098
32120
|
runStatus = "success";
|
|
32121
|
+
} else if (prior?.active) {
|
|
32122
|
+
runRow = { workflow: "get-publish.yml", runId: prior.runId, ...prior.runUrl ? { runUrl: prior.runUrl } : {} };
|
|
32123
|
+
runStatus = input.watch ? await watchTenantRun(deps, prior.runId, input.repo) : "pending";
|
|
32099
32124
|
} else {
|
|
32100
32125
|
const version = input.tag?.replace(/^v/, "") ?? (deps.readLauncherVersion ?? readLocalLauncherVersion)();
|
|
32101
32126
|
if (!version) return withNote("the local launcher version could not be read \u2014 nothing was compared, so the move is unverified");
|
|
@@ -32107,9 +32132,17 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32107
32132
|
return withNote(`get-publish dispatch FAILED: ${msg}. The release itself landed, but the SERVED CHANNEL IS UNCHANGED \u2014 recover with \`${pointerRemedy}\``, "failure", planeFailureRow("get-publish.yml", `dispatch refused: ${msg}`));
|
|
32108
32133
|
}
|
|
32109
32134
|
const correlated = await correlateGetPublishRun(deps, since, HUB_SERVED_PRODUCT, "pointer");
|
|
32110
|
-
if (correlated.read
|
|
32135
|
+
if (correlated.read === "failed") {
|
|
32111
32136
|
return withNote(`get-publish dispatched but its run could not be READ: ${correlated.error ?? "no matching run appeared"}. Check the Actions tab; if it never ran, recover with \`${pointerRemedy}\``, "failure", planeFailureRow("get-publish.yml", `dispatched, but its run could not be read`));
|
|
32112
32137
|
}
|
|
32138
|
+
if (correlated.read === "absent") {
|
|
32139
|
+
const dispatchedAt = new Date(since).toISOString();
|
|
32140
|
+
return {
|
|
32141
|
+
...dispatch,
|
|
32142
|
+
note: `${dispatch.note}; get-publish pointer dispatched at ${dispatchedAt} but its run is not listed yet \u2014 recorded PENDING; \`devops release --resume\` re-correlates it by title and time`,
|
|
32143
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "get-publish.yml", runUrlNote: `pointer dispatched ${dispatchedAt}; run not listed within the correlation window`, conclusion: "pending", dispatchedAt, titleTokens: [HUB_SERVED_PRODUCT, "pointer"] }]
|
|
32144
|
+
};
|
|
32145
|
+
}
|
|
32113
32146
|
const { runId, runUrl } = correlated;
|
|
32114
32147
|
runRow = { workflow: "get-publish.yml", ...runId ? { runId } : {}, ...runUrl ? { runUrl } : {} };
|
|
32115
32148
|
if (!input.watch) {
|
|
@@ -32121,7 +32154,7 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32121
32154
|
}
|
|
32122
32155
|
runStatus = await watchTenantRun(deps, runId, input.repo);
|
|
32123
32156
|
}
|
|
32124
|
-
if (runStatus
|
|
32157
|
+
if (runStatus === "failure") {
|
|
32125
32158
|
return {
|
|
32126
32159
|
...dispatch,
|
|
32127
32160
|
note: `${dispatch.note}; get-publish.yml ${runStatus} \u2014 the SERVED CHANNEL IS UNCHANGED; recover with \`${pointerRemedy}\``,
|
|
@@ -32129,6 +32162,13 @@ async function appendServedChannelPublish(deps, input, dispatch, redeployed) {
|
|
|
32129
32162
|
deployStatus: "failure"
|
|
32130
32163
|
};
|
|
32131
32164
|
}
|
|
32165
|
+
if (runStatus === "pending") {
|
|
32166
|
+
return {
|
|
32167
|
+
...dispatch,
|
|
32168
|
+
note: `${dispatch.note}; get-publish.yml pointer run is still unverified (pending) \u2014 the served channel move is not yet proven; \`devops release --resume\` re-reads it`,
|
|
32169
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { ...runRow, conclusion: "pending" }]
|
|
32170
|
+
};
|
|
32171
|
+
}
|
|
32132
32172
|
const expected = input.tag ? input.tag.replace(/^v/, "") : (deps.readLauncherVersion ?? readLocalLauncherVersion)();
|
|
32133
32173
|
const runs2 = [...dispatch.workflowRuns ?? [], { ...runRow, conclusion: "success" }];
|
|
32134
32174
|
if (!channelUrl) {
|
|
@@ -32269,6 +32309,14 @@ async function appendInstallHomePublish(deps, input, dispatch) {
|
|
|
32269
32309
|
deployStatus: "failure"
|
|
32270
32310
|
};
|
|
32271
32311
|
}
|
|
32312
|
+
if (correlated.read === "absent") {
|
|
32313
|
+
const dispatchedAt = new Date(since).toISOString();
|
|
32314
|
+
return {
|
|
32315
|
+
...dispatch,
|
|
32316
|
+
note: note(`installer home publish for ${product} dispatched at ${dispatchedAt} but its run is not listed yet \u2014 recorded PENDING; \`devops release --resume\` re-correlates it by title and time`),
|
|
32317
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "get-publish.yml", runUrlNote: `installer home (${product}) dispatched ${dispatchedAt}; run not listed within the correlation window`, conclusion: "pending", dispatchedAt, titleTokens: [product] }]
|
|
32318
|
+
};
|
|
32319
|
+
}
|
|
32272
32320
|
const row = { workflow: "get-publish.yml", ...correlated.runId ? { runId: correlated.runId } : {}, ...correlated.runUrl ? { runUrl: correlated.runUrl } : {} };
|
|
32273
32321
|
if (!input.watch) {
|
|
32274
32322
|
return {
|
|
@@ -32278,7 +32326,7 @@ async function appendInstallHomePublish(deps, input, dispatch) {
|
|
|
32278
32326
|
};
|
|
32279
32327
|
}
|
|
32280
32328
|
const status = await watchTenantRun(deps, correlated.runId, HUB_REPO2);
|
|
32281
|
-
if (status
|
|
32329
|
+
if (status === "failure") {
|
|
32282
32330
|
return {
|
|
32283
32331
|
...dispatch,
|
|
32284
32332
|
note: note(`get-publish.yml (product=${product}) ${status} \u2014 the INSTALLER HOME IS UNCHANGED; recover with \`${remedy}\``),
|
|
@@ -32286,6 +32334,13 @@ async function appendInstallHomePublish(deps, input, dispatch) {
|
|
|
32286
32334
|
deployStatus: "failure"
|
|
32287
32335
|
};
|
|
32288
32336
|
}
|
|
32337
|
+
if (status === "pending") {
|
|
32338
|
+
return {
|
|
32339
|
+
...dispatch,
|
|
32340
|
+
note: note(`get-publish.yml (product=${product}) run is still unverified (pending) \u2014 the installer home is not yet proven published; \`devops release --resume\` re-reads it`),
|
|
32341
|
+
workflowRuns: [...dispatch.workflowRuns ?? [], { ...row, conclusion: "pending" }]
|
|
32342
|
+
};
|
|
32343
|
+
}
|
|
32289
32344
|
return {
|
|
32290
32345
|
...dispatch,
|
|
32291
32346
|
note: note(`installer home published for ${product} (get-publish.yml green)`),
|
|
@@ -32324,8 +32379,10 @@ async function appendServedChannelLegs(deps, input, dispatch) {
|
|
|
32324
32379
|
]);
|
|
32325
32380
|
return mergeServedLegs(dispatch, home2, proof2);
|
|
32326
32381
|
}
|
|
32327
|
-
const
|
|
32328
|
-
const
|
|
32382
|
+
const stageRows = (prepared.workflowRuns ?? []).filter((r) => r.workflow === "get-publish.yml");
|
|
32383
|
+
const stageGreen = stageRows.every((r) => r.conclusion === "success");
|
|
32384
|
+
const stagePending = !stageGreen && stageRows.every((r) => r.conclusion !== "failure");
|
|
32385
|
+
const channel = stageGreen || !isHubControlRepo(input.repo) || publishStatus !== "success" ? await appendServedChannelPublish(deps, { repo: input.repo, publishStatus, watch: input.watch, releaseSha: input.releaseSha, tag: input.tag }, dispatch, Promise.resolve(prepared)) : { ...dispatch, note: `${dispatch.note}; served channel NOT moved (stage ${stagePending ? "still pending \u2014 `devops release --resume` re-correlates the stage run and moves the pointer" : "not green"}) \u2014 recover with \`${getPublishDispatchCommand(input.repo)} -f mode=stage\`, then \`${getPublishDispatchCommand(input.repo)} -f mode=pointer -f version=${input.tag.replace(/^v/, "")}\`` };
|
|
32329
32386
|
const out = mergeServedLegs(dispatch, {
|
|
32330
32387
|
...dispatch,
|
|
32331
32388
|
note: dispatch.note + prepared.note,
|
|
@@ -44822,6 +44879,56 @@ function registerRunRerun(run, writeToken, projects) {
|
|
|
44822
44879
|
});
|
|
44823
44880
|
}
|
|
44824
44881
|
|
|
44882
|
+
// src/run-dispatch.ts
|
|
44883
|
+
init_cli_shared();
|
|
44884
|
+
async function dispatchWorkflow(repo, workflow, ref, inputs, deps) {
|
|
44885
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new Error("run dispatch: invalid repository");
|
|
44886
|
+
if (!/^[A-Za-z0-9_.-]+\.ya?ml$/.test(workflow)) throw new Error("run dispatch: workflow must be a YAML filename");
|
|
44887
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/.test(ref) || ref.includes("..") || ref.includes("//") || ref.endsWith("/")) throw new Error("run dispatch: invalid ref");
|
|
44888
|
+
if (inputs.length > 10) throw new Error("run dispatch: at most 10 inputs");
|
|
44889
|
+
const fields = ["-f", `ref=${ref}`];
|
|
44890
|
+
const names = /* @__PURE__ */ new Set();
|
|
44891
|
+
for (const input of inputs) {
|
|
44892
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]{0,63})=([^\r\n]{0,2048})$/.exec(input);
|
|
44893
|
+
if (!match || names.has(match[1])) throw new Error("run dispatch: invalid or duplicate input");
|
|
44894
|
+
names.add(match[1]);
|
|
44895
|
+
fields.push("-f", `inputs[${match[1]}]=${match[2]}`);
|
|
44896
|
+
}
|
|
44897
|
+
const projects = await deps.projects();
|
|
44898
|
+
if (!projects) throw new Error("run dispatch: registry unavailable; no dispatch attempted");
|
|
44899
|
+
if (!projects.some((p) => (p.repos ?? []).some((r) => typeof r === "string" && registryRepoKey(qualifyRegistryRepo(r)) === registryRepoKey(repo)))) {
|
|
44900
|
+
throw new Error("run dispatch: repository is not registered; no dispatch attempted");
|
|
44901
|
+
}
|
|
44902
|
+
let status;
|
|
44903
|
+
try {
|
|
44904
|
+
status = await deps.post(`repos/${repo}/actions/workflows/${workflow}/dispatches`, fields);
|
|
44905
|
+
} catch {
|
|
44906
|
+
throw new Error("run dispatch: outcome unknown; inspect GitHub before any retry");
|
|
44907
|
+
}
|
|
44908
|
+
if (status !== 204) throw new Error("run dispatch: outcome unknown; inspect GitHub before any retry");
|
|
44909
|
+
return { ok: true, repo, workflow, ref, credential: "app_installation", status: "dispatch-requested", httpStatus: status };
|
|
44910
|
+
}
|
|
44911
|
+
function appDispatchPost(writeToken, exec3 = execFileP) {
|
|
44912
|
+
return async (path2, fields) => {
|
|
44913
|
+
const token = await writeToken();
|
|
44914
|
+
const { stdout } = await exec3("gh", ["api", "--include", "--method", "POST", path2, ...fields], {
|
|
44915
|
+
timeout: 2e4,
|
|
44916
|
+
env: { ...process.env, GH_TOKEN: token, MMI_GH_POOL: "app" }
|
|
44917
|
+
});
|
|
44918
|
+
return Number(/^HTTP\/[\d.]+ (\d{3})/.exec(stdout)?.[1] ?? 0);
|
|
44919
|
+
};
|
|
44920
|
+
}
|
|
44921
|
+
function registerRunDispatch(run, writeToken, projects) {
|
|
44922
|
+
run.command("dispatch <workflow>").description("request one workflow dispatch using the org App; an ambiguous result is never retried").requiredOption("--repo <owner/repo>", "registered repository").requiredOption("--ref <ref>", "workflow ref").option("--input <name=value>", "workflow input (repeatable)", (v, values) => [...values, v], []).option("--json", "machine-readable receipt").action(async (workflow, options) => {
|
|
44923
|
+
try {
|
|
44924
|
+
const receipt = await dispatchWorkflow(options.repo, workflow, options.ref, options.input, { projects, post: appDispatchPost(writeToken) });
|
|
44925
|
+
console.log(options.json ? JSON.stringify(receipt) : `run dispatch: requested ${receipt.repo}/${receipt.workflow} at ${receipt.ref}`);
|
|
44926
|
+
} catch (error) {
|
|
44927
|
+
return fail(error.message);
|
|
44928
|
+
}
|
|
44929
|
+
});
|
|
44930
|
+
}
|
|
44931
|
+
|
|
44825
44932
|
// src/devops-vars.ts
|
|
44826
44933
|
var import_node_crypto13 = require("node:crypto");
|
|
44827
44934
|
init_github_client();
|
|
@@ -46300,7 +46407,7 @@ function refuseNotClaimable(e, json) {
|
|
|
46300
46407
|
process.exitCode = 1;
|
|
46301
46408
|
return true;
|
|
46302
46409
|
}
|
|
46303
|
-
var BOARD_READ_HELP = "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n\nThe --json report is an OBJECT \u2014 never an array
|
|
46410
|
+
var BOARD_READ_HELP = "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n\nThe --json report is an OBJECT \u2014 never an array:\n {project, viewer, repo, primary, secondary, items, warnings, partial[, source, builtAt, eventCursor, ...]}\n`primary` is this repo and `secondary` the other repos in scope; each is a bucket object\n {userOwned, claimable, taken, unownedInFlight}\nand each bucket is an array of items. Filter the BUCKET for ownership context:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n node -e \"const b=require('./.jerv/tmp/board.json');console.log(b.primary.claimable.length)\"\n\n`items` (#9974) is that same flat row array, already on the full report: {ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}\n node -e \"const b=require('./.jerv/tmp/board.json');for(const i of b.items)console.log(i.number,i.status,i.title)\"\nIt is built from the same flattener as --flat, so `const rows = j.items || j` is now safe on either form.\n\nAdd --flat with --json for that array as the WHOLE output (no project/viewer/warnings envelope):\n(#9595: claim is the parsed mmi-claim evidence object, null when unclaimed; openPullRequests lists the OPEN\n PRs whose closing-issue body links name the item. On the FULL JSON object that list lives at\n `report.pullRequests` \u2014 there is no top-level `openPullRequests`, and #9596 hand-written projections\n that assume one crash on `.filter`.)\n\nNever sniff the envelope \u2014 read `report.items` (or `primary`/`secondary`) by name. `j.items ?? j.rows ?? j`,\n`Object.values(j)` and any generic \"find the list\" wrapper are still guesses: before `items` existed they\nthrew `items is not iterable` before any row was read (#9956), and `j.rows` has never been a key. For a\nnumber|status|claim|title projection across non-Done rows, read --json --flat --out .jerv/tmp/board.json\nand filter that array locally (identical rows to `report.items`):\n mmi-cli oracle board read --json --flat --out .jerv/tmp/board.json\n\nA cached snapshot read renders `source: cache \xB7 built <timestamp> \xB7 fresh|stale` (#9584) and carries\n`builtAt` under --json, so a same-turn re-read of the same builtAt buys nothing new.\n\nUnder --json, parse stdout ONLY and check the exit code first. A failure still prints a\nhuman-readable reason on stderr, so never pipe 2>&1 into a JSON parser (#9151) \u2014 the merged\ntext is not JSON and hides the real board failure.\n\nA zero-byte or empty .jerv/tmp/*.json means the read FAILED \u2014 `Unexpected end of JSON input` is\nthe symptom of shell-redirected empty stdout, not malformed JSON \u2014 re-run with --out (which writes\nthe file only on success) and check the exit code before parsing.\n\nChanging an existing item's Status is a move, never a hand-written `gh api graphql`\n`updateProjectV2ItemFieldValue` mutation with copied `PVT_`/`PVTI_` node ids and a manual Status\nfield-id lookup:\n mmi-cli oracle board move <status> <ref>\n";
|
|
46304
46411
|
function flattenBoardReport(report) {
|
|
46305
46412
|
const closingByRef = /* @__PURE__ */ new Map();
|
|
46306
46413
|
for (const pull of report.pullRequests ?? []) {
|
|
@@ -46322,6 +46429,9 @@ function flattenBoardReport(report) {
|
|
|
46322
46429
|
openPullRequests: closingByRef.get(ref) ?? []
|
|
46323
46430
|
}))));
|
|
46324
46431
|
}
|
|
46432
|
+
function withBoardItems(report) {
|
|
46433
|
+
return { ...report, items: flattenBoardReport(report) };
|
|
46434
|
+
}
|
|
46325
46435
|
function registerBoardCommands(program3) {
|
|
46326
46436
|
function withDiscoverMissDetail(message2) {
|
|
46327
46437
|
const miss = lastBoardDiscoverMiss();
|
|
@@ -46346,7 +46456,7 @@ function registerBoardCommands(program3) {
|
|
|
46346
46456
|
snapshotOnly: o.snapshotOnly,
|
|
46347
46457
|
...o.minCursor !== void 0 ? { minCursor: Number(o.minCursor) } : {}
|
|
46348
46458
|
}, o.direct ? {} : { snapshot: registryClientDeps(config) });
|
|
46349
|
-
const output = o.json ? JSON.stringify(o.flat ? flattenBoardReport(report) : report) : renderBoardReport(report);
|
|
46459
|
+
const output = o.json ? JSON.stringify(o.flat ? flattenBoardReport(report) : withBoardItems(report)) : renderBoardReport(report);
|
|
46350
46460
|
if (!o.out) console.log(output);
|
|
46351
46461
|
else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
|
|
46352
46462
|
} catch (e) {
|
|
@@ -61128,9 +61238,10 @@ async function preparePrCreate(input, deps = createPreparationDeps(input)) {
|
|
|
61128
61238
|
const reads = await Promise.allSettled([
|
|
61129
61239
|
deps.claims(refs.candidate),
|
|
61130
61240
|
refs.candidate ? deps.docs(refs.candidate) : Promise.resolve(void 0),
|
|
61131
|
-
observePolicy(deps, refs.candidate)
|
|
61241
|
+
observePolicy(deps, refs.candidate),
|
|
61242
|
+
refs.candidate ? deps.queueWaiver?.(refs.candidate) : Promise.resolve(void 0)
|
|
61132
61243
|
]);
|
|
61133
|
-
const [claims, docs, policy] = reads;
|
|
61244
|
+
const [claims, docs, policy, waiver] = reads;
|
|
61134
61245
|
if (claims.status === "fulfilled") {
|
|
61135
61246
|
observations.push(...claims.value.findings.length ? claims.value.findings.map((detail) => ({ check: "claims", state: "failed", detail })) : [{ check: "claims", state: "passed", detail: "issue and claim evidence read" }]);
|
|
61136
61247
|
} else observations.push({ check: "claims", state: "unavailable", detail: `issue/claim evidence unavailable (${rejectionReason(claims.reason)}); retry the read` });
|
|
@@ -61138,6 +61249,8 @@ async function preparePrCreate(input, deps = createPreparationDeps(input)) {
|
|
|
61138
61249
|
else if (docs.value) observations.push({ check: "docs", state: docs.value.state, detail: [docs.value.detail, docs.value.fix].filter(Boolean).join(" \u2014 ") });
|
|
61139
61250
|
if (policy.status === "rejected") observations.push({ check: "policy", state: "unavailable", detail: `committed test-policy observation unavailable (${rejectionReason(policy.reason)}); check policy validity and fetch full candidate history before retrying` });
|
|
61140
61251
|
else if (policy.value) observations.push(policy.value);
|
|
61252
|
+
if (waiver.status === "rejected") observations.push({ check: "queue", state: "skipped", detail: `the queue squash waiver placement was not checked (${rejectionReason(waiver.reason)}); this is non-blocking` });
|
|
61253
|
+
else if (waiver.value) observations.push(waiver.value);
|
|
61141
61254
|
const findings = observations.filter((item) => item.state === "failed" || item.state === "unavailable");
|
|
61142
61255
|
let attempted = false;
|
|
61143
61256
|
return {
|
|
@@ -61238,6 +61351,34 @@ function createPreparationDeps(input, io = {}) {
|
|
|
61238
61351
|
return {
|
|
61239
61352
|
candidate: readCandidate,
|
|
61240
61353
|
publish: (proof) => publishPreparedPrHead(proof, exec3),
|
|
61354
|
+
// #9831/#9968: publication happens only in commit(), after this read-only preparation, so the queue's
|
|
61355
|
+
// buried-waiver refusal is decided here — with the queue's own reader and setting reads — before an
|
|
61356
|
+
// absent remote head is pushed and a PR opened. A range or setting it cannot read is a skip (#9968).
|
|
61357
|
+
queueWaiver: async (candidate) => {
|
|
61358
|
+
const result = (state, detail) => ({ check: "queue", state, detail });
|
|
61359
|
+
if (!candidate.root) return void 0;
|
|
61360
|
+
const range = await exec3("git", ["-C", candidate.root, "log", "--reverse", "--format=%B%x00", `${candidate.baseSha}..${candidate.headSha}`], { timeout: GIT_TIMEOUT_MS }).then((read) => ({ log: read.stdout }), (error) => ({ reason: rejectionReason(error) }));
|
|
61361
|
+
if (!("log" in range)) return result("skipped", `candidate commits ${candidate.baseSha}..${candidate.headSha} could not be read (${range.reason}), so the queue squash waiver placement was not checked; this is non-blocking`);
|
|
61362
|
+
const messages = range.log.split("\0").filter((message2) => message2.trim()).map((message2) => {
|
|
61363
|
+
const [headline, ...body] = message2.trim().split("\n");
|
|
61364
|
+
return { headline, body: body.join("\n") };
|
|
61365
|
+
});
|
|
61366
|
+
const refusal = checkQueueSquashOverride("Candidate PR", "0", messages, candidate.root);
|
|
61367
|
+
if (!refusal) return void 0;
|
|
61368
|
+
const [owner, name] = candidate.repo.split("/");
|
|
61369
|
+
const setting = await (async () => {
|
|
61370
|
+
const metadata = await client.graphql(
|
|
61371
|
+
"query($owner:String!,$name:String!,$base:String!) { repository(owner:$owner,name:$name) { squashMergeCommitMessage mergeQueue(branch:$base) { configuration { mergeMethod } } } }",
|
|
61372
|
+
{ owner, name, base: candidate.base }
|
|
61373
|
+
);
|
|
61374
|
+
const rules = await client.restPaginate(`repos/${candidate.repo}/rules/branches/${encodeURIComponent(candidate.base)}?per_page=100`);
|
|
61375
|
+
const methods = [...new Set(rules.filter((rule) => rule.type === "merge_queue").map((rule) => rule.parameters?.merge_method))];
|
|
61376
|
+
const method = methods.length === 1 ? methods[0] : !methods.length ? metadata.repository.mergeQueue?.configuration?.mergeMethod : void 0;
|
|
61377
|
+
return method === "SQUASH" && metadata.repository.squashMergeCommitMessage === "COMMIT_MESSAGES";
|
|
61378
|
+
})().then((squash) => squash, (error) => rejectionReason(error));
|
|
61379
|
+
if (typeof setting === "string") return result("skipped", `the repository's queue squash-message setting could not be read (${setting}), so the queue squash waiver placement was not checked; this is non-blocking`);
|
|
61380
|
+
return setting ? result("failed", `${refusal.error} \u2014 ${refusal.preparation.nextAction}`) : void 0;
|
|
61381
|
+
},
|
|
61241
61382
|
// #9946: the retry needs the exact candidate objects. Fetch each ref on its own so a fork-only head
|
|
61242
61383
|
// that origin cannot resolve never costs the base fetch too, and never surface a fetch failure as the
|
|
61243
61384
|
// reason the policy read stayed unreadable.
|
|
@@ -61258,25 +61399,6 @@ function createPreparationDeps(input, io = {}) {
|
|
|
61258
61399
|
if (!await docs.refResolves(candidate.root, candidate.headSha) || !await docs.refResolves(candidate.root, candidate.baseSha)) {
|
|
61259
61400
|
return result("skipped", "exact remote candidate objects are not available locally; committed test policy was not checked");
|
|
61260
61401
|
}
|
|
61261
|
-
const log = (await exec3("git", ["-C", candidate.root, "log", "--reverse", "--format=%B%x00", `${candidate.baseSha}..${candidate.headSha}`], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
61262
|
-
const messages = log.split("\0").filter((message2) => message2.trim()).map((message2) => {
|
|
61263
|
-
const [headline, ...body] = message2.trim().split("\n");
|
|
61264
|
-
return { headline, body: body.join("\n") };
|
|
61265
|
-
});
|
|
61266
|
-
const refusal = checkQueueSquashOverride("Candidate PR", "0", messages, candidate.root);
|
|
61267
|
-
if (refusal) {
|
|
61268
|
-
const [owner, name] = candidate.repo.split("/");
|
|
61269
|
-
const metadata = await client.graphql(
|
|
61270
|
-
"query($owner:String!,$name:String!,$base:String!) { repository(owner:$owner,name:$name) { squashMergeCommitMessage mergeQueue(branch:$base) { configuration { mergeMethod } } } }",
|
|
61271
|
-
{ owner, name, base: candidate.base }
|
|
61272
|
-
);
|
|
61273
|
-
const rules = await client.restPaginate(`repos/${candidate.repo}/rules/branches/${encodeURIComponent(candidate.base)}?per_page=100`);
|
|
61274
|
-
const methods = [...new Set(rules.filter((rule) => rule.type === "merge_queue").map((rule) => rule.parameters?.merge_method))];
|
|
61275
|
-
const method = methods.length === 1 ? methods[0] : !methods.length ? metadata.repository.mergeQueue?.configuration?.mergeMethod : void 0;
|
|
61276
|
-
if (method === "SQUASH" && metadata.repository.squashMergeCommitMessage === "COMMIT_MESSAGES") {
|
|
61277
|
-
return result("failed", `${refusal.error} \u2014 ${refusal.preparation.nextAction}`);
|
|
61278
|
-
}
|
|
61279
|
-
}
|
|
61280
61402
|
let policy;
|
|
61281
61403
|
try {
|
|
61282
61404
|
policy = runTestPolicy(candidate.root, { candidate: { head: candidate.headSha, base: candidate.baseSha } });
|
|
@@ -63054,9 +63176,11 @@ function renderResidueSweepLines(report) {
|
|
|
63054
63176
|
const more = report.unrecorded.length > 5 ? `, \u2026 ${report.unrecorded.length - 5} more` : "";
|
|
63055
63177
|
lines2.push(`pr merge: ${report.unrecorded.length} unregistered folder(s) with content under the worktree root${where} were left untouched (not recorded by a merge): ${listed.map((u) => u.path).join(", ")}${more}`);
|
|
63056
63178
|
for (const u of listed) lines2.push(`pr merge: left untouched ${u.path} \u2014 ${provenanceText(u)}`);
|
|
63179
|
+
lines2.push(`pr merge: recovery: ${RESIDUE_RECOVERY_MOVE}`);
|
|
63057
63180
|
}
|
|
63058
63181
|
return lines2;
|
|
63059
63182
|
}
|
|
63183
|
+
var RESIDUE_RECOVERY_MOVE = `run \`git worktree list --porcelain\` in the primary checkout and confirm the path is absent and its provenance above shows no live lane (not registered; admin dir gone or .git pointer missing); nothing can record it for this sweep (only \`pr merge\` records its own merged worktree in <git-common-dir>/${RESIDUE_LEDGER_FILE}), so removing a non-empty unrecorded folder stays a human decision \u2014 hand the confirmed path to a human; this sweep never deletes it`;
|
|
63060
63184
|
function provenanceText(u) {
|
|
63061
63185
|
const pointer = u.gitdir ? `.git \u2192 ${u.gitdir}${u.gitdirPresent ? "" : " (admin dir gone)"}` : ".git pointer missing";
|
|
63062
63186
|
const registration = `worktrees/${u.registrationId} ${u.registrationPresent ? "registered" : "not registered"}`;
|
|
@@ -73836,6 +73960,7 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
|
|
|
73836
73960
|
"ci cancel-orphans",
|
|
73837
73961
|
"run cancel",
|
|
73838
73962
|
"run rerun",
|
|
73963
|
+
"run dispatch",
|
|
73839
73964
|
// #9779/#9780: the vars verbs and archive-status validate --repo/name/value before the first
|
|
73840
73965
|
// dispatch, so their scoped App mint waits for the action handler like the run writes above.
|
|
73841
73966
|
"vars get",
|
|
@@ -74715,13 +74840,18 @@ ci.command("hosted-usage").description("read-only hosted tick and lane-demand al
|
|
|
74715
74840
|
});
|
|
74716
74841
|
registerCiChecksWait(ci);
|
|
74717
74842
|
registerCancelOrphans(ci, async () => (await mintCommandWriteToken(appActorDeps(), "ci cancel-orphans")).token);
|
|
74718
|
-
var runRoot = program2.command("run").description("bounded workflow-run writes
|
|
74843
|
+
var runRoot = program2.command("run").description("bounded workflow-run writes on the org App actions:write profile");
|
|
74719
74844
|
registerRunCancel(runRoot, async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run cancel")).token);
|
|
74720
74845
|
registerRunRerun(
|
|
74721
74846
|
runRoot,
|
|
74722
74847
|
async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run rerun")).token,
|
|
74723
74848
|
async () => fetchProjectsList(registryClientDeps(await loadConfig()))
|
|
74724
74849
|
);
|
|
74850
|
+
registerRunDispatch(
|
|
74851
|
+
runRoot,
|
|
74852
|
+
async () => (await mintCommandWriteToken(appActorDeps(), "run dispatch")).token,
|
|
74853
|
+
async () => fetchProjectsList(registryClientDeps(await loadConfig()))
|
|
74854
|
+
);
|
|
74725
74855
|
var varsAppClient = (commandPath4) => async () => (
|
|
74726
74856
|
// The concrete client always provides restWithStatus (optional on the interface only so hand-built
|
|
74727
74857
|
// test stubs of GitHubClient keep compiling).
|
package/package.json
CHANGED