@mutmutco/cli 4.3.0 → 4.3.2
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 +1891 -543
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,7 +3416,7 @@ function useColor() {
|
|
|
3416
3416
|
var program = new Command();
|
|
3417
3417
|
|
|
3418
3418
|
// src/command-composition.ts
|
|
3419
|
-
var
|
|
3419
|
+
var import_node_fs48 = require("node:fs");
|
|
3420
3420
|
|
|
3421
3421
|
// src/clean-exit.ts
|
|
3422
3422
|
var UNDICI_GLOBAL_DISPATCHER_SYMBOL = Object.getOwnPropertySymbols(globalThis).find(
|
|
@@ -3465,19 +3465,19 @@ function hardExit(code) {
|
|
|
3465
3465
|
}
|
|
3466
3466
|
var STDIO_FLUSH_TIMEOUT_MS = 2e3;
|
|
3467
3467
|
function flushStream(stream) {
|
|
3468
|
-
return new Promise((
|
|
3468
|
+
return new Promise((resolve7) => {
|
|
3469
3469
|
try {
|
|
3470
|
-
stream.write("", () =>
|
|
3470
|
+
stream.write("", () => resolve7());
|
|
3471
3471
|
} catch {
|
|
3472
|
-
|
|
3472
|
+
resolve7();
|
|
3473
3473
|
}
|
|
3474
3474
|
});
|
|
3475
3475
|
}
|
|
3476
3476
|
async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
|
|
3477
3477
|
await Promise.race([
|
|
3478
3478
|
Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
|
|
3479
|
-
new Promise((
|
|
3480
|
-
setTimeout(
|
|
3479
|
+
new Promise((resolve7) => {
|
|
3480
|
+
setTimeout(resolve7, timeoutMs).unref?.();
|
|
3481
3481
|
})
|
|
3482
3482
|
]);
|
|
3483
3483
|
}
|
|
@@ -3485,7 +3485,7 @@ async function cleanExit(code) {
|
|
|
3485
3485
|
process.exitCode = code;
|
|
3486
3486
|
await closeHttpPool();
|
|
3487
3487
|
await flushStdio();
|
|
3488
|
-
await new Promise((
|
|
3488
|
+
await new Promise((resolve7) => setImmediate(resolve7));
|
|
3489
3489
|
return void 0;
|
|
3490
3490
|
}
|
|
3491
3491
|
var CLI_EXIT_WATCHDOG_MS = (() => {
|
|
@@ -3930,7 +3930,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
|
3930
3930
|
const attempts = opts.attempts ?? 3;
|
|
3931
3931
|
const baseDelayMs = opts.baseDelayMs ?? 250;
|
|
3932
3932
|
const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
|
|
3933
|
-
const sleep2 = opts.sleep ?? ((ms) => new Promise((
|
|
3933
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
3934
3934
|
let lastErr;
|
|
3935
3935
|
for (let i = 0; i < attempts; i++) {
|
|
3936
3936
|
const isLast = i === attempts - 1;
|
|
@@ -4198,8 +4198,8 @@ async function readStdin(opts = {}) {
|
|
|
4198
4198
|
})().catch(() => {
|
|
4199
4199
|
});
|
|
4200
4200
|
let timer;
|
|
4201
|
-
const timeout = new Promise((
|
|
4202
|
-
timer = setTimeout(
|
|
4201
|
+
const timeout = new Promise((resolve7) => {
|
|
4202
|
+
timer = setTimeout(resolve7, timeoutMs);
|
|
4203
4203
|
});
|
|
4204
4204
|
try {
|
|
4205
4205
|
await Promise.race([drain, timeout]);
|
|
@@ -4253,12 +4253,12 @@ function killProcessTree(pid) {
|
|
|
4253
4253
|
function execFileHard(file, args, options) {
|
|
4254
4254
|
const { timeout, step, ...rest } = options;
|
|
4255
4255
|
const started = Date.now();
|
|
4256
|
-
return new Promise((
|
|
4256
|
+
return new Promise((resolve7, reject) => {
|
|
4257
4257
|
const child2 = (0, import_node_child_process2.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
|
|
4258
4258
|
clearTimeout(timer);
|
|
4259
4259
|
if (expired) return;
|
|
4260
4260
|
if (error) reject(error);
|
|
4261
|
-
else
|
|
4261
|
+
else resolve7({ stdout: String(stdout), stderr: String(stderr) });
|
|
4262
4262
|
});
|
|
4263
4263
|
let expired = false;
|
|
4264
4264
|
const timer = setTimeout(() => {
|
|
@@ -5219,8 +5219,8 @@ function unknownCommandDomainGuide(parentPath, token) {
|
|
|
5219
5219
|
}
|
|
5220
5220
|
|
|
5221
5221
|
// src/command-composition.ts
|
|
5222
|
-
var
|
|
5223
|
-
var
|
|
5222
|
+
var import_node_os20 = require("node:os");
|
|
5223
|
+
var import_node_path45 = require("node:path");
|
|
5224
5224
|
|
|
5225
5225
|
// src/board-read.ts
|
|
5226
5226
|
var import_node_fs13 = require("node:fs");
|
|
@@ -5330,7 +5330,7 @@ function rateLimitedReceipt(opts) {
|
|
|
5330
5330
|
};
|
|
5331
5331
|
}
|
|
5332
5332
|
async function runWithRateLimitBackoff(operation, opts) {
|
|
5333
|
-
const sleep2 = opts.sleep ?? ((ms) => new Promise((
|
|
5333
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
5334
5334
|
const now = opts.now ?? Date.now;
|
|
5335
5335
|
const log = opts.log ?? ((message) => console.warn(message));
|
|
5336
5336
|
const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
|
|
@@ -8016,13 +8016,47 @@ async function defaultCompareRefs(exec, repo, base, head) {
|
|
|
8016
8016
|
return "unknown";
|
|
8017
8017
|
}
|
|
8018
8018
|
}
|
|
8019
|
+
async function publishAbsentPrHeadIfOwned(repo, head, exec) {
|
|
8020
|
+
if (isForkStyleHead(head)) return false;
|
|
8021
|
+
let originRepo;
|
|
8022
|
+
try {
|
|
8023
|
+
const { stdout } = await exec("git", ["remote", "get-url", "--push", "origin"], { timeout: GIT_TIMEOUT_MS });
|
|
8024
|
+
originRepo = repoFromRemoteUrl(stdout.trim());
|
|
8025
|
+
} catch {
|
|
8026
|
+
return false;
|
|
8027
|
+
}
|
|
8028
|
+
if (!originRepo || originRepo.toLowerCase() !== repo.toLowerCase()) return false;
|
|
8029
|
+
let currentBranch2;
|
|
8030
|
+
try {
|
|
8031
|
+
currentBranch2 = (await exec("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
8032
|
+
} catch {
|
|
8033
|
+
return false;
|
|
8034
|
+
}
|
|
8035
|
+
if (!currentBranch2 || currentBranch2 !== head) return false;
|
|
8036
|
+
let commit;
|
|
8037
|
+
try {
|
|
8038
|
+
commit = (await exec("git", ["rev-parse", "--verify", "HEAD^{commit}"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
8039
|
+
} catch {
|
|
8040
|
+
return false;
|
|
8041
|
+
}
|
|
8042
|
+
if (!commit) return false;
|
|
8043
|
+
try {
|
|
8044
|
+
await exec("git", ["push", "origin", `${commit}:refs/heads/${head}`], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
8045
|
+
return true;
|
|
8046
|
+
} catch {
|
|
8047
|
+
return false;
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8019
8050
|
async function preflightPrCreateRemoteHead(args, deps) {
|
|
8020
8051
|
const repo = flagValue(args, "--repo");
|
|
8021
8052
|
const head = flagValue(args, "--head");
|
|
8022
8053
|
if (!repo || !head || isForkStyleHead(head)) return void 0;
|
|
8023
8054
|
const readRemoteHead = deps.readRemoteHead ?? ((input) => defaultReadRemoteHead(deps.exec, input.repo, input.head));
|
|
8024
|
-
|
|
8025
|
-
if (presence === "absent")
|
|
8055
|
+
let presence = await readRemoteHead({ repo, head });
|
|
8056
|
+
if (presence === "absent") {
|
|
8057
|
+
if (!await publishAbsentPrHeadIfOwned(repo, head, deps.exec)) return unpushedHeadMessage(head);
|
|
8058
|
+
presence = "present";
|
|
8059
|
+
}
|
|
8026
8060
|
if (presence !== "present") return void 0;
|
|
8027
8061
|
const base = flagValue(args, "--base");
|
|
8028
8062
|
if (!base) return void 0;
|
|
@@ -8438,7 +8472,7 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
|
|
|
8438
8472
|
}
|
|
8439
8473
|
async function ghCreate(args, deps = {}) {
|
|
8440
8474
|
const exec = deps.exec ?? execFileP;
|
|
8441
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((
|
|
8475
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
8442
8476
|
const now = deps.now ?? Date.now;
|
|
8443
8477
|
const read = deps.readFile ?? import_promises.readFile;
|
|
8444
8478
|
const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec, input));
|
|
@@ -9151,8 +9185,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
|
|
|
9151
9185
|
async function drainHttpPoolBeforeSpawn() {
|
|
9152
9186
|
const drained = await Promise.race([
|
|
9153
9187
|
closeHttpPool().then(() => true),
|
|
9154
|
-
new Promise((
|
|
9155
|
-
setTimeout(() =>
|
|
9188
|
+
new Promise((resolve7) => {
|
|
9189
|
+
setTimeout(() => resolve7(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
|
|
9156
9190
|
})
|
|
9157
9191
|
]);
|
|
9158
9192
|
if (!drained) destroyHttpPool();
|
|
@@ -9400,8 +9434,22 @@ function repoFromSelector(selector) {
|
|
|
9400
9434
|
return void 0;
|
|
9401
9435
|
}
|
|
9402
9436
|
}
|
|
9437
|
+
var REPO_OPTION_SHAPES = "owner/repo or host/owner/repo";
|
|
9438
|
+
function invalidRepoOption(repo, hint) {
|
|
9439
|
+
const base = `invalid repo "${repo}" (expected ${REPO_OPTION_SHAPES})`;
|
|
9440
|
+
return new Error(hint ? `${base} \u2014 ${hint}` : base);
|
|
9441
|
+
}
|
|
9442
|
+
async function validateRepoOption(repo) {
|
|
9443
|
+
const value = repo.trim();
|
|
9444
|
+
const parts = value.split("/");
|
|
9445
|
+
if ((parts.length === 2 || parts.length === 3) && parts.every((p) => p !== "")) return value;
|
|
9446
|
+
const origin = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"])) ?? repoFromRemoteUrl(originUrlFromGitConfig() ?? "");
|
|
9447
|
+
const originName = origin?.split("/")[1];
|
|
9448
|
+
const hint = originName !== void 0 && value.toLowerCase() === originName.toLowerCase() ? `did you mean ${origin}?` : void 0;
|
|
9449
|
+
throw invalidRepoOption(repo, hint);
|
|
9450
|
+
}
|
|
9403
9451
|
async function resolveRepo(repo) {
|
|
9404
|
-
if (repo) return repo;
|
|
9452
|
+
if (repo) return validateRepoOption(repo);
|
|
9405
9453
|
const fromOrigin = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"])) ?? repoFromRemoteUrl(originUrlFromGitConfig() ?? "");
|
|
9406
9454
|
if (fromOrigin) return fromOrigin;
|
|
9407
9455
|
try {
|
|
@@ -12271,7 +12319,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
|
|
|
12271
12319
|
await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
|
|
12272
12320
|
return cliPriorityToFieldName(priority);
|
|
12273
12321
|
}
|
|
12274
|
-
var defaultRetrySleep = (ms) => new Promise((
|
|
12322
|
+
var defaultRetrySleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12275
12323
|
async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
|
|
12276
12324
|
const attempts = Math.max(1, opts.attempts ?? 5);
|
|
12277
12325
|
const delayMs = opts.delayMs ?? 300;
|
|
@@ -15274,10 +15322,10 @@ var rollout_plan_default = {
|
|
|
15274
15322
|
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)."
|
|
15275
15323
|
},
|
|
15276
15324
|
baseline: {
|
|
15277
|
-
version: "4.3.
|
|
15278
|
-
tag: "v4.3.
|
|
15279
|
-
commit: "
|
|
15280
|
-
npm: "@mutmutco/cli@4.3.
|
|
15325
|
+
version: "4.3.2",
|
|
15326
|
+
tag: "v4.3.2",
|
|
15327
|
+
commit: "c4aa995c1dfc",
|
|
15328
|
+
npm: "@mutmutco/cli@4.3.2"
|
|
15281
15329
|
},
|
|
15282
15330
|
exitCriterion: "fleet-n-of-n",
|
|
15283
15331
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15294,14 +15342,14 @@ var rollout_plan_default = {
|
|
|
15294
15342
|
repo: "mutmutco/mmi-hub",
|
|
15295
15343
|
role: "canary",
|
|
15296
15344
|
schedule: "train",
|
|
15297
|
-
v3Target: "v4.3.
|
|
15345
|
+
v3Target: "v4.3.2"
|
|
15298
15346
|
}
|
|
15299
15347
|
],
|
|
15300
15348
|
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.",
|
|
15301
15349
|
rollback: {
|
|
15302
15350
|
independent: true,
|
|
15303
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15304
|
-
v3Target: "v4.3.
|
|
15351
|
+
mechanism: "npm dist-tag latest -> 4.3.2 and redeploy the Hub Lambda from tag v4.3.2 (c4aa995c1dfc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15352
|
+
v3Target: "v4.3.2 (@mutmutco/cli@4.3.2, tag commit c4aa995c1dfc \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15305
15353
|
}
|
|
15306
15354
|
},
|
|
15307
15355
|
{
|
|
@@ -16169,6 +16217,7 @@ function validateEnum(flag, allowed, value, command) {
|
|
|
16169
16217
|
var ISSUE_STATES = ["open", "closed", "all"];
|
|
16170
16218
|
var PR_STATES = ["open", "all"];
|
|
16171
16219
|
async function resolveBoardProjectId(repoOption) {
|
|
16220
|
+
if (repoOption !== void 0) await validateRepoOption(repoOption);
|
|
16172
16221
|
try {
|
|
16173
16222
|
const floor = await loadConfig();
|
|
16174
16223
|
if (!floor.sagaApiUrl) return void 0;
|
|
@@ -17270,7 +17319,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
|
|
|
17270
17319
|
async function predictMergeConflicts(deps, ours, theirs) {
|
|
17271
17320
|
return runMergeTreePreflight(deps, ours, theirs);
|
|
17272
17321
|
}
|
|
17273
|
-
async function mergeWithToleratedResolution(deps, sourceRef, label,
|
|
17322
|
+
async function mergeWithToleratedResolution(deps, sourceRef, label, resolve7, extraTolerated = []) {
|
|
17274
17323
|
try {
|
|
17275
17324
|
await deps.run("git", ["merge", sourceRef, "--no-edit"]);
|
|
17276
17325
|
return;
|
|
@@ -17284,7 +17333,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, ex
|
|
|
17284
17333
|
unmerged.length === 0 ? `${label} merge failed without conflicted paths \u2014 merge aborted; inspect the repo state and rerun` : `${label} merge conflicts on untolerated path(s): ${blocking.join(", ")} \u2014 merge aborted (the train is misaligned; reconcile the branches via an approved alignment PR, then rerun)`
|
|
17285
17334
|
);
|
|
17286
17335
|
}
|
|
17287
|
-
await deps.run("git", ["checkout", `--${
|
|
17336
|
+
await deps.run("git", ["checkout", `--${resolve7}`, "--", ...unmerged]);
|
|
17288
17337
|
await deps.run("git", ["add", "--", ...unmerged]);
|
|
17289
17338
|
await deps.run("git", ["commit", "--no-edit"]);
|
|
17290
17339
|
}
|
|
@@ -17463,7 +17512,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
|
|
|
17463
17512
|
var CORRELATE_PAGE_LIMIT = 50;
|
|
17464
17513
|
var RUN_CONFIRM_ATTEMPTS = 3;
|
|
17465
17514
|
var RUN_CONFIRM_DELAY_MS = 1e3;
|
|
17466
|
-
var defaultSleep = (ms) => new Promise((
|
|
17515
|
+
var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
17467
17516
|
function resolveSleep(deps) {
|
|
17468
17517
|
return deps.sleep ?? defaultSleep;
|
|
17469
17518
|
}
|
|
@@ -18292,12 +18341,6 @@ async function discoverShaWorkflowRuns(deps, repo, headSha, seenRunIds) {
|
|
|
18292
18341
|
}
|
|
18293
18342
|
return extra;
|
|
18294
18343
|
}
|
|
18295
|
-
async function deriveHistoricalDeployStatus(deps, ctx, model, releaseSha) {
|
|
18296
|
-
const targets = prodReleaseTargets(model);
|
|
18297
|
-
if (!targets.length) return "pending";
|
|
18298
|
-
const repo = model === "hub-serverless" ? HUB_REPO2 : ctx.repo;
|
|
18299
|
-
return aggregateWorkflowRuns(await listOwnWorkflowRuns(deps, repo, targets, releaseSha));
|
|
18300
|
-
}
|
|
18301
18344
|
async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince, autoRunHeadSha, dispatchFailure = "throw", publishDir) {
|
|
18302
18345
|
if (isCentralDispatchModel(model)) {
|
|
18303
18346
|
const since = (deps.now ?? Date.now)();
|
|
@@ -18592,9 +18635,9 @@ function renderTenantControl(r) {
|
|
|
18592
18635
|
}
|
|
18593
18636
|
|
|
18594
18637
|
// src/train-apply-phases.ts
|
|
18595
|
-
var
|
|
18596
|
-
var
|
|
18597
|
-
var
|
|
18638
|
+
var import_node_fs20 = require("node:fs");
|
|
18639
|
+
var import_promises3 = require("node:fs/promises");
|
|
18640
|
+
var import_node_path19 = require("node:path");
|
|
18598
18641
|
|
|
18599
18642
|
// src/actions-billing-preflight.ts
|
|
18600
18643
|
var CANARY_WORKFLOW = "actions-job-start-canary.yml";
|
|
@@ -18818,37 +18861,1077 @@ Resume it: ${resumeCommand}
|
|
|
18818
18861
|
);
|
|
18819
18862
|
}
|
|
18820
18863
|
|
|
18864
|
+
// src/release-ledger.ts
|
|
18865
|
+
var import_node_fs19 = require("node:fs");
|
|
18866
|
+
var import_node_path18 = require("node:path");
|
|
18867
|
+
var import_node_crypto5 = require("node:crypto");
|
|
18868
|
+
|
|
18869
|
+
// src/file-lock.ts
|
|
18870
|
+
var import_promises2 = require("node:fs/promises");
|
|
18871
|
+
var import_node_path17 = require("node:path");
|
|
18872
|
+
var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
18873
|
+
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
18874
|
+
var FileLockBusyError = class extends Error {
|
|
18875
|
+
lockPath;
|
|
18876
|
+
constructor(label, lockPath, maxWaitMs) {
|
|
18877
|
+
super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
|
|
18878
|
+
this.name = "FileLockBusyError";
|
|
18879
|
+
this.lockPath = lockPath;
|
|
18880
|
+
}
|
|
18881
|
+
};
|
|
18882
|
+
function resolveFileLockOpts(opts = {}) {
|
|
18883
|
+
return {
|
|
18884
|
+
staleMs: opts.staleMs ?? 3e4,
|
|
18885
|
+
retryMs: opts.retryMs ?? 50,
|
|
18886
|
+
maxWaitMs: opts.maxWaitMs ?? 5e3,
|
|
18887
|
+
label: opts.label ?? "file lock"
|
|
18888
|
+
};
|
|
18889
|
+
}
|
|
18890
|
+
async function acquireFileLock(lockPath, opts, deadline) {
|
|
18891
|
+
let immediateRetries = 0;
|
|
18892
|
+
for (; ; ) {
|
|
18893
|
+
let handle;
|
|
18894
|
+
try {
|
|
18895
|
+
handle = await (0, import_promises2.open)(lockPath, "wx");
|
|
18896
|
+
} catch (e) {
|
|
18897
|
+
const code = e.code;
|
|
18898
|
+
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
18899
|
+
const retryNow = async () => {
|
|
18900
|
+
if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
18901
|
+
if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
|
|
18902
|
+
};
|
|
18903
|
+
try {
|
|
18904
|
+
const age = Date.now() - (await (0, import_promises2.stat)(lockPath)).mtimeMs;
|
|
18905
|
+
if (age > opts.staleMs) {
|
|
18906
|
+
try {
|
|
18907
|
+
await (0, import_promises2.unlink)(lockPath);
|
|
18908
|
+
await retryNow();
|
|
18909
|
+
continue;
|
|
18910
|
+
} catch (unlinkError) {
|
|
18911
|
+
if (unlinkError instanceof FileLockBusyError) throw unlinkError;
|
|
18912
|
+
const unlinkCode = unlinkError.code;
|
|
18913
|
+
if (unlinkCode === "ENOENT") {
|
|
18914
|
+
await retryNow();
|
|
18915
|
+
continue;
|
|
18916
|
+
}
|
|
18917
|
+
}
|
|
18918
|
+
}
|
|
18919
|
+
} catch (statError) {
|
|
18920
|
+
if (statError instanceof FileLockBusyError) throw statError;
|
|
18921
|
+
if (statError.code !== "ENOENT") throw statError;
|
|
18922
|
+
await retryNow();
|
|
18923
|
+
continue;
|
|
18924
|
+
}
|
|
18925
|
+
if (Date.now() >= deadline) {
|
|
18926
|
+
throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
18927
|
+
}
|
|
18928
|
+
await sleep(opts.retryMs);
|
|
18929
|
+
continue;
|
|
18930
|
+
}
|
|
18931
|
+
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
18932
|
+
try {
|
|
18933
|
+
await handle.writeFile(token);
|
|
18934
|
+
} catch (e) {
|
|
18935
|
+
await handle.close().catch(() => void 0);
|
|
18936
|
+
throw e;
|
|
18937
|
+
}
|
|
18938
|
+
return { token, handle };
|
|
18939
|
+
}
|
|
18940
|
+
}
|
|
18941
|
+
async function fileLockHeldBy(lockPath, token) {
|
|
18942
|
+
try {
|
|
18943
|
+
return await (0, import_promises2.readFile)(lockPath, "utf8") === token;
|
|
18944
|
+
} catch {
|
|
18945
|
+
return false;
|
|
18946
|
+
}
|
|
18947
|
+
}
|
|
18948
|
+
async function releaseFileLock(lockPath, guard) {
|
|
18949
|
+
await guard.handle.close().catch(() => void 0);
|
|
18950
|
+
if (await fileLockHeldBy(lockPath, guard.token)) {
|
|
18951
|
+
await (0, import_promises2.unlink)(lockPath).catch(() => void 0);
|
|
18952
|
+
}
|
|
18953
|
+
}
|
|
18954
|
+
async function withFileLock(lockPath, opts, fn) {
|
|
18955
|
+
const resolved = resolveFileLockOpts(opts);
|
|
18956
|
+
await (0, import_promises2.mkdir)((0, import_node_path17.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
18957
|
+
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
18958
|
+
try {
|
|
18959
|
+
return await fn();
|
|
18960
|
+
} finally {
|
|
18961
|
+
await releaseFileLock(lockPath, guard);
|
|
18962
|
+
}
|
|
18963
|
+
}
|
|
18964
|
+
|
|
18965
|
+
// src/release-ledger.ts
|
|
18966
|
+
var RELEASE_LEDGER_SCHEMA = "mmi-release-ledger/v1";
|
|
18967
|
+
var RELEASE_LEDGER_PHASES = [
|
|
18968
|
+
"promotion",
|
|
18969
|
+
"ordinaryCi",
|
|
18970
|
+
"githubRelease",
|
|
18971
|
+
"publish",
|
|
18972
|
+
"deploy",
|
|
18973
|
+
"alignment"
|
|
18974
|
+
];
|
|
18975
|
+
var PHASE_SET = new Set(RELEASE_LEDGER_PHASES);
|
|
18976
|
+
var STATE_SET = /* @__PURE__ */ new Set(["pending", "complete", "failed", "skipped"]);
|
|
18977
|
+
var ReleaseLedgerError = class extends Error {
|
|
18978
|
+
kind;
|
|
18979
|
+
constructor(kind, message) {
|
|
18980
|
+
super(message);
|
|
18981
|
+
this.name = "ReleaseLedgerError";
|
|
18982
|
+
this.kind = kind;
|
|
18983
|
+
}
|
|
18984
|
+
};
|
|
18985
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
18986
|
+
var MAX_TEXT = 500;
|
|
18987
|
+
function truncate(text, max) {
|
|
18988
|
+
return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
18989
|
+
}
|
|
18990
|
+
function normalizeLedgerAnchors(anchors) {
|
|
18991
|
+
const repo = anchors.repo?.trim();
|
|
18992
|
+
const tag = anchors.tag?.trim();
|
|
18993
|
+
const tagSha = anchors.tagSha?.trim().toLowerCase();
|
|
18994
|
+
if (!repo || !/^[^/]+\/[^/]+$/.test(repo)) {
|
|
18995
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: invalid repo anchor ${JSON.stringify(anchors.repo ?? "")}`);
|
|
18996
|
+
}
|
|
18997
|
+
if (!tag || !/^v\d+\.\d+\.\d+(-rc\.\d+)?$/.test(tag)) {
|
|
18998
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: invalid tag anchor ${JSON.stringify(anchors.tag ?? "")}`);
|
|
18999
|
+
}
|
|
19000
|
+
if (!tagSha || !SHA_RE.test(tagSha)) {
|
|
19001
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: invalid tagSha anchor ${JSON.stringify(anchors.tagSha ?? "")}`);
|
|
19002
|
+
}
|
|
19003
|
+
return { repo, tag, tagSha };
|
|
19004
|
+
}
|
|
19005
|
+
function emptyPhases() {
|
|
19006
|
+
return Object.fromEntries(RELEASE_LEDGER_PHASES.map((p) => [p, { state: "pending", updatedAt: "never" }]));
|
|
19007
|
+
}
|
|
19008
|
+
function validatePhaseRecord(phase, raw) {
|
|
19009
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
19010
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} is not an object`);
|
|
19011
|
+
}
|
|
19012
|
+
const rec = raw;
|
|
19013
|
+
const state = rec.state;
|
|
19014
|
+
if (typeof state !== "string" || !STATE_SET.has(state)) {
|
|
19015
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has invalid state ${JSON.stringify(state)}`);
|
|
19016
|
+
}
|
|
19017
|
+
if (rec.sha !== void 0 && (typeof rec.sha !== "string" || !SHA_RE.test(rec.sha))) {
|
|
19018
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed sha`);
|
|
19019
|
+
}
|
|
19020
|
+
if (rec.runId !== void 0 && (typeof rec.runId !== "number" || !Number.isSafeInteger(rec.runId) || rec.runId <= 0)) {
|
|
19021
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed runId`);
|
|
19022
|
+
}
|
|
19023
|
+
if (rec.runRepo !== void 0 && (typeof rec.runRepo !== "string" || !/^[\w.-]+\/[\w.-]+$/.test(rec.runRepo))) {
|
|
19024
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed runRepo`);
|
|
19025
|
+
}
|
|
19026
|
+
if (rec.runProvenance !== void 0 && rec.runProvenance !== "sha" && rec.runProvenance !== "dispatch") {
|
|
19027
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed runProvenance ${JSON.stringify(rec.runProvenance)}`);
|
|
19028
|
+
}
|
|
19029
|
+
for (const key of ["runUrl", "note", "error", "workflow", "event"]) {
|
|
19030
|
+
if (rec[key] !== void 0 && (typeof rec[key] !== "string" || rec[key].length > MAX_TEXT * 4)) {
|
|
19031
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed ${key}`);
|
|
19032
|
+
}
|
|
19033
|
+
}
|
|
19034
|
+
if (rec.retries !== void 0 && (typeof rec.retries !== "number" || !Number.isSafeInteger(rec.retries) || rec.retries < 0 || rec.retries > 9)) {
|
|
19035
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed retries value`);
|
|
19036
|
+
}
|
|
19037
|
+
if (typeof rec.updatedAt !== "string" || !rec.updatedAt) {
|
|
19038
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} is missing updatedAt`);
|
|
19039
|
+
}
|
|
19040
|
+
return rec;
|
|
19041
|
+
}
|
|
19042
|
+
function parseReleaseLedger(raw) {
|
|
19043
|
+
let parsed;
|
|
19044
|
+
try {
|
|
19045
|
+
parsed = JSON.parse(raw);
|
|
19046
|
+
} catch (e) {
|
|
19047
|
+
throw new ReleaseLedgerError("unreadable", `release ledger is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 refusing to resume against unreadable state (#5987)`);
|
|
19048
|
+
}
|
|
19049
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
19050
|
+
throw new ReleaseLedgerError("unreadable", "release ledger is not a JSON object");
|
|
19051
|
+
}
|
|
19052
|
+
const obj = parsed;
|
|
19053
|
+
if (obj.schema !== RELEASE_LEDGER_SCHEMA) {
|
|
19054
|
+
throw new ReleaseLedgerError(
|
|
19055
|
+
"unknown-schema",
|
|
19056
|
+
`release ledger schema ${JSON.stringify(obj.schema ?? "(missing)")} is not ${JSON.stringify(RELEASE_LEDGER_SCHEMA)} \u2014 a NEWER ledger this CLI cannot interpret must fail closed; do not delete or hand-edit the file, use a CLI that understands it`
|
|
19057
|
+
);
|
|
19058
|
+
}
|
|
19059
|
+
const repo = obj.repo;
|
|
19060
|
+
const tag = obj.tag;
|
|
19061
|
+
const tagSha = obj.tagSha;
|
|
19062
|
+
if (typeof repo !== "string" || !repo || typeof tag !== "string" || !tag || typeof tagSha !== "string" || !SHA_RE.test(tagSha)) {
|
|
19063
|
+
throw new ReleaseLedgerError("unreadable", `release ledger has malformed identity anchors (repo/tag/tagSha)`);
|
|
19064
|
+
}
|
|
19065
|
+
if (typeof obj.updatedAt !== "string" || !obj.updatedAt) {
|
|
19066
|
+
throw new ReleaseLedgerError("unreadable", "release ledger is missing updatedAt");
|
|
19067
|
+
}
|
|
19068
|
+
const phasesRaw = obj.phases;
|
|
19069
|
+
if (typeof phasesRaw !== "object" || phasesRaw === null || Array.isArray(phasesRaw)) {
|
|
19070
|
+
throw new ReleaseLedgerError("unreadable", "release ledger phases block is not an object");
|
|
19071
|
+
}
|
|
19072
|
+
const phases = { ...emptyPhases() };
|
|
19073
|
+
for (const [phase, rec] of Object.entries(phasesRaw)) {
|
|
19074
|
+
if (!PHASE_SET.has(phase)) continue;
|
|
19075
|
+
phases[phase] = validatePhaseRecord(phase, rec);
|
|
19076
|
+
}
|
|
19077
|
+
return { schema: RELEASE_LEDGER_SCHEMA, repo, tag, tagSha, phases, updatedAt: obj.updatedAt };
|
|
19078
|
+
}
|
|
19079
|
+
function validateLedgerAnchors(ledger, expected) {
|
|
19080
|
+
const anchors = normalizeAnchorsForCompare(expected);
|
|
19081
|
+
if (ledger.repo.toLowerCase() !== anchors.repo.toLowerCase()) {
|
|
19082
|
+
throw new ReleaseLedgerError(
|
|
19083
|
+
"repo-mismatch",
|
|
19084
|
+
`release ledger belongs to ${ledger.repo}, not ${anchors.repo} \u2014 refusing to mix releases across repos (#5987)`
|
|
19085
|
+
);
|
|
19086
|
+
}
|
|
19087
|
+
if (ledger.tag !== anchors.tag) {
|
|
19088
|
+
throw new ReleaseLedgerError(
|
|
19089
|
+
"tag-mismatch",
|
|
19090
|
+
`release ledger belongs to ${ledger.tag}, not ${anchors.tag} \u2014 refusing to resume a different tag against it (#5987)`
|
|
19091
|
+
);
|
|
19092
|
+
}
|
|
19093
|
+
if (ledger.tagSha.toLowerCase() !== anchors.tagSha.toLowerCase()) {
|
|
19094
|
+
throw new ReleaseLedgerError(
|
|
19095
|
+
"tag-sha-mismatch",
|
|
19096
|
+
`release ledger anchors ${ledger.tag} at ${ledger.tagSha.slice(0, 12)} but live origin reports ${anchors.tagSha.slice(0, 12)} \u2014 the immutable tag moved or the ledger is stale; failing closed instead of trusting either (#5987)`
|
|
19097
|
+
);
|
|
19098
|
+
}
|
|
19099
|
+
}
|
|
19100
|
+
function normalizeAnchorsForCompare(expected) {
|
|
19101
|
+
const repo = expected.repo?.trim() ?? "";
|
|
19102
|
+
const tag = expected.tag?.trim() ?? "";
|
|
19103
|
+
const tagSha = expected.tagSha?.trim().toLowerCase() ?? "";
|
|
19104
|
+
if (!repo || !tag || !SHA_RE.test(tagSha)) {
|
|
19105
|
+
throw new ReleaseLedgerError("invalid-record", `release ledger: invalid live anchors (repo=${JSON.stringify(repo)}, tag=${JSON.stringify(tag)})`);
|
|
19106
|
+
}
|
|
19107
|
+
return { repo, tag, tagSha };
|
|
19108
|
+
}
|
|
19109
|
+
function resolvePrimaryGitDir(cwd) {
|
|
19110
|
+
const gitDir = resolveGitDir(cwd);
|
|
19111
|
+
if (!gitDir) {
|
|
19112
|
+
throw new ReleaseLedgerError("no-checkout", `release ledger: no git checkout found above ${cwd}`);
|
|
19113
|
+
}
|
|
19114
|
+
let common;
|
|
19115
|
+
try {
|
|
19116
|
+
common = (0, import_node_fs19.readFileSync)((0, import_node_path18.join)(gitDir, "commondir"), "utf8").trim();
|
|
19117
|
+
} catch (e) {
|
|
19118
|
+
if (e.code !== "ENOENT") {
|
|
19119
|
+
throw new ReleaseLedgerError("unreadable", `release ledger: the worktree gitdir's commondir pointer could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not primary (#5987/#4713)`);
|
|
19120
|
+
}
|
|
19121
|
+
}
|
|
19122
|
+
if (!common) return gitDir;
|
|
19123
|
+
const commonDir = (0, import_node_path18.isAbsolute)(common) ? common : (0, import_node_path18.resolve)(gitDir, common);
|
|
19124
|
+
try {
|
|
19125
|
+
(0, import_node_fs19.readFileSync)((0, import_node_path18.join)(commonDir, "HEAD"), "utf8");
|
|
19126
|
+
} catch (e) {
|
|
19127
|
+
throw new ReleaseLedgerError("no-checkout", `release ledger: the worktree's commondir pointer names ${commonDir}, which has no readable HEAD (${e instanceof Error ? e.message : String(e)}) \u2014 the primary checkout may have been removed; refusing to write the ledger into a doomed location`);
|
|
19128
|
+
}
|
|
19129
|
+
return commonDir;
|
|
19130
|
+
}
|
|
19131
|
+
function releaseLedgerPath(cwd) {
|
|
19132
|
+
return (0, import_node_path18.join)(resolvePrimaryGitDir(cwd), "mmi-runtime", "release", "phases.json");
|
|
19133
|
+
}
|
|
19134
|
+
function atomicWriteJson(path2, ledger) {
|
|
19135
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
|
|
19136
|
+
const tmp = `${path2}.tmp-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`;
|
|
19137
|
+
(0, import_node_fs19.writeFileSync)(tmp, `${JSON.stringify(ledger, null, 2)}
|
|
19138
|
+
`, "utf8");
|
|
19139
|
+
(0, import_node_fs19.renameSync)(tmp, path2);
|
|
19140
|
+
}
|
|
19141
|
+
async function withReleaseLedger(path2, expected, fn, opts = {}) {
|
|
19142
|
+
return withFileLock(`${path2}.lock`, { label: "release ledger", maxWaitMs: opts.maxWaitMs ?? 5e3 }, async () => {
|
|
19143
|
+
let current;
|
|
19144
|
+
let raw;
|
|
19145
|
+
try {
|
|
19146
|
+
raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
19147
|
+
} catch (e) {
|
|
19148
|
+
if (e.code !== "ENOENT") {
|
|
19149
|
+
throw new ReleaseLedgerError("unreadable", `release ledger at ${path2} could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not absent (#5987)`);
|
|
19150
|
+
}
|
|
19151
|
+
}
|
|
19152
|
+
if (raw !== void 0) {
|
|
19153
|
+
current = parseReleaseLedger(raw);
|
|
19154
|
+
if (expected) validateLedgerAnchors(current, expected);
|
|
19155
|
+
}
|
|
19156
|
+
const { ledger, value } = await fn(current);
|
|
19157
|
+
if (ledger) atomicWriteJson(path2, ledger);
|
|
19158
|
+
return value;
|
|
19159
|
+
});
|
|
19160
|
+
}
|
|
19161
|
+
function openReleaseLedgerWriter(cwd, opts = {}) {
|
|
19162
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
19163
|
+
let path2;
|
|
19164
|
+
try {
|
|
19165
|
+
path2 = releaseLedgerPath(cwd);
|
|
19166
|
+
} catch (e) {
|
|
19167
|
+
const failure = e;
|
|
19168
|
+
return {
|
|
19169
|
+
path: "(unresolved)",
|
|
19170
|
+
record: async () => ({ ok: false, error: failure instanceof Error ? failure.message : String(failure) }),
|
|
19171
|
+
load: async () => {
|
|
19172
|
+
throw failure;
|
|
19173
|
+
}
|
|
19174
|
+
};
|
|
19175
|
+
}
|
|
19176
|
+
return {
|
|
19177
|
+
path: path2,
|
|
19178
|
+
record: async (anchors, phase, entry) => {
|
|
19179
|
+
try {
|
|
19180
|
+
return await withReleaseLedger(
|
|
19181
|
+
path2,
|
|
19182
|
+
anchors,
|
|
19183
|
+
(current) => {
|
|
19184
|
+
const normalized = normalizeLedgerAnchors(anchors);
|
|
19185
|
+
const base = current ?? {
|
|
19186
|
+
schema: RELEASE_LEDGER_SCHEMA,
|
|
19187
|
+
repo: normalized.repo,
|
|
19188
|
+
tag: normalized.tag,
|
|
19189
|
+
tagSha: normalized.tagSha,
|
|
19190
|
+
phases: emptyPhases(),
|
|
19191
|
+
updatedAt: now().toISOString()
|
|
19192
|
+
};
|
|
19193
|
+
const previousRetries = base.phases[phase].retries ?? 0;
|
|
19194
|
+
const record = {
|
|
19195
|
+
...entry,
|
|
19196
|
+
note: entry.note !== void 0 ? truncate(entry.note, MAX_TEXT) : void 0,
|
|
19197
|
+
error: entry.error !== void 0 ? truncate(entry.error, MAX_TEXT * 2) : void 0,
|
|
19198
|
+
retries: entry.retries ?? previousRetries,
|
|
19199
|
+
updatedAt: now().toISOString()
|
|
19200
|
+
};
|
|
19201
|
+
const updated = {
|
|
19202
|
+
...base,
|
|
19203
|
+
phases: { ...base.phases, [phase]: record },
|
|
19204
|
+
updatedAt: now().toISOString()
|
|
19205
|
+
};
|
|
19206
|
+
return { ledger: updated, value: { ok: true } };
|
|
19207
|
+
},
|
|
19208
|
+
{ maxWaitMs: opts.maxWaitMs }
|
|
19209
|
+
);
|
|
19210
|
+
} catch (e) {
|
|
19211
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
19212
|
+
}
|
|
19213
|
+
},
|
|
19214
|
+
load: async (anchors) => {
|
|
19215
|
+
try {
|
|
19216
|
+
return await withReleaseLedger(
|
|
19217
|
+
path2,
|
|
19218
|
+
anchors,
|
|
19219
|
+
(current) => ({ value: current }),
|
|
19220
|
+
{ maxWaitMs: opts.maxWaitMs }
|
|
19221
|
+
);
|
|
19222
|
+
} catch (e) {
|
|
19223
|
+
if (e instanceof ReleaseLedgerError) throw e;
|
|
19224
|
+
throw new ReleaseLedgerError("unreadable", e instanceof Error ? e.message : String(e));
|
|
19225
|
+
}
|
|
19226
|
+
}
|
|
19227
|
+
};
|
|
19228
|
+
}
|
|
19229
|
+
function compactEntry(entry) {
|
|
19230
|
+
return Object.fromEntries(Object.entries(entry).filter(([, v]) => v !== void 0));
|
|
19231
|
+
}
|
|
19232
|
+
function phaseEntry(entry) {
|
|
19233
|
+
return compactEntry(entry);
|
|
19234
|
+
}
|
|
19235
|
+
var PHASE_RANK = { failed: 0, pending: 1, complete: 2, skipped: 2 };
|
|
19236
|
+
function deriveReleasePhaseStatus(ledger) {
|
|
19237
|
+
const legs = RELEASE_LEDGER_PHASES.map((phase) => {
|
|
19238
|
+
const rec = ledger.phases[phase];
|
|
19239
|
+
return {
|
|
19240
|
+
phase,
|
|
19241
|
+
state: rec.state,
|
|
19242
|
+
...rec.runId !== void 0 ? { runId: rec.runId } : {},
|
|
19243
|
+
...rec.runRepo !== void 0 ? { runRepo: rec.runRepo } : {},
|
|
19244
|
+
...rec.workflow !== void 0 ? { workflow: rec.workflow } : {},
|
|
19245
|
+
...rec.event !== void 0 ? { event: rec.event } : {},
|
|
19246
|
+
...rec.note !== void 0 ? { note: rec.note } : {},
|
|
19247
|
+
...rec.error !== void 0 ? { error: rec.error } : {}
|
|
19248
|
+
};
|
|
19249
|
+
});
|
|
19250
|
+
const worst = legs.reduce((acc, leg) => Math.min(acc, PHASE_RANK[leg.state]), 2);
|
|
19251
|
+
return { phaseStatus: worst === 0 ? "phases-failed" : worst === 1 ? "phases-pending" : "phases-green", legs };
|
|
19252
|
+
}
|
|
19253
|
+
function safeRunAction(runId, runRepo, state) {
|
|
19254
|
+
return state === "failed" ? `next: diagnose with gh run view ${runId} --repo ${runRepo} --log-failed; rerun by hand only when the cause is fixed: gh run rerun ${runId} --repo ${runRepo} --failed (never re-tag)` : `next: gh run watch ${runId} --repo ${runRepo} --exit-status`;
|
|
19255
|
+
}
|
|
19256
|
+
function formatReleaseLedgerReport(report) {
|
|
19257
|
+
const lines = [`release phase ledger: ${report.phaseStatus.toUpperCase()} (${report.path}) \u2014 phase status of the recorded boundaries, NOT the release verdict`];
|
|
19258
|
+
for (const leg of report.legs) {
|
|
19259
|
+
const detail = leg.error ?? leg.note;
|
|
19260
|
+
const run = [leg.workflow, leg.event ? `(${leg.event})` : void 0, leg.runId != null ? `run ${leg.runId}` : void 0, leg.runRepo ? `in ${leg.runRepo}` : void 0].filter(Boolean).join(" ");
|
|
19261
|
+
lines.push(` - ${leg.phase}: ${leg.state.toUpperCase()}${run ? ` \u2014 ${run}` : ""}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
19262
|
+
if ((leg.state === "failed" || leg.state === "pending") && leg.runId != null && leg.runRepo) {
|
|
19263
|
+
lines.push(` ${safeRunAction(leg.runId, leg.runRepo, leg.state === "failed" ? "failed" : "pending")}`);
|
|
19264
|
+
}
|
|
19265
|
+
}
|
|
19266
|
+
return lines;
|
|
19267
|
+
}
|
|
19268
|
+
function releaseLedgerReport(ledger, path2) {
|
|
19269
|
+
if (!ledger) return void 0;
|
|
19270
|
+
const derived = deriveReleasePhaseStatus(ledger);
|
|
19271
|
+
return { path: path2, phases: ledger.phases, phaseStatus: derived.phaseStatus, legs: derived.legs };
|
|
19272
|
+
}
|
|
19273
|
+
var TRANSIENT_TRANSPORT_MARKERS = [
|
|
19274
|
+
/connection (?:reset|refused|aborted|timed? ?out)/i,
|
|
19275
|
+
/\bECONN(?:RESET|REFUSED|ABORTED|TIMEDOUT)\b/,
|
|
19276
|
+
/\bE(?:HOST|NET)UNREACH\b/,
|
|
19277
|
+
/\bEAI_AGAIN\b/,
|
|
19278
|
+
/\bEPIPE\b/,
|
|
19279
|
+
/lost communication with (?:the )?github actions runner service/i,
|
|
19280
|
+
/runner lost communication/i,
|
|
19281
|
+
/\b(?:502|503|504)\b[^0-9]{0,40}(?:bad gateway|service unavailable|gateway time-?out|server error)/i,
|
|
19282
|
+
/(?:bad gateway|service unavailable|gateway time-?out)/i,
|
|
19283
|
+
/fetch failed/i,
|
|
19284
|
+
/network (?:error|failure|unreachable|timeout|timed? ?out)/i,
|
|
19285
|
+
/socket hang ?up/i,
|
|
19286
|
+
/remote end hung ?up unexpectedly/i,
|
|
19287
|
+
/early EOF/i,
|
|
19288
|
+
/tls handshake timeout/i
|
|
19289
|
+
];
|
|
19290
|
+
function classifyTransientTransportFailure(evidence) {
|
|
19291
|
+
if (!evidence) return false;
|
|
19292
|
+
return TRANSIENT_TRANSPORT_MARKERS.some((marker) => marker.test(evidence));
|
|
19293
|
+
}
|
|
19294
|
+
var TRANSIENT_EVIDENCE_MAX_CHARS = 16e3;
|
|
19295
|
+
var AUTO_RERUN_MAX_RETRIES = 1;
|
|
19296
|
+
function failedRunIdentity(run, repo) {
|
|
19297
|
+
return `${run.workflow}${run.event ? ` (${run.event})` : ""} run ${run.runId} in ${repo}`;
|
|
19298
|
+
}
|
|
19299
|
+
function manualRunAction(run, repo) {
|
|
19300
|
+
return `safe next action: diagnose with gh run view ${run.runId} --repo ${repo} --log-failed; rerun by hand only when the cause is fixed: gh run rerun ${run.runId} --repo ${repo} --failed (never re-tag)`;
|
|
19301
|
+
}
|
|
19302
|
+
async function readFailedRunsOnSha(deps, repo, sha) {
|
|
19303
|
+
try {
|
|
19304
|
+
const rows = JSON.parse(await deps.run("gh", [
|
|
19305
|
+
"run",
|
|
19306
|
+
"list",
|
|
19307
|
+
"--repo",
|
|
19308
|
+
repo,
|
|
19309
|
+
"--commit",
|
|
19310
|
+
sha,
|
|
19311
|
+
"--limit",
|
|
19312
|
+
"20",
|
|
19313
|
+
"--json",
|
|
19314
|
+
"databaseId,url,workflowName,event,status,conclusion"
|
|
19315
|
+
]));
|
|
19316
|
+
if (!Array.isArray(rows)) return [];
|
|
19317
|
+
return rows.filter((r) => typeof r.databaseId === "number" && r.status === "completed" && (r.conclusion === "failure" || r.conclusion === "cancelled")).map((r) => ({
|
|
19318
|
+
workflow: r.workflowName ?? `run:${r.databaseId}`,
|
|
19319
|
+
runId: r.databaseId,
|
|
19320
|
+
runUrl: r.url,
|
|
19321
|
+
repo,
|
|
19322
|
+
...r.event ? { event: r.event } : {},
|
|
19323
|
+
conclusion: r.conclusion ?? "failure"
|
|
19324
|
+
}));
|
|
19325
|
+
} catch {
|
|
19326
|
+
return [];
|
|
19327
|
+
}
|
|
19328
|
+
}
|
|
19329
|
+
async function transientEvidenceForRun(deps, repo, runId) {
|
|
19330
|
+
const parts = [];
|
|
19331
|
+
try {
|
|
19332
|
+
parts.push(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "jobs", "--jq", '[.jobs[] | select(.conclusion=="failure") | {name:.name, steps:[.steps[] | select(.conclusion=="failure") | .name]}] | tostring']));
|
|
19333
|
+
} catch {
|
|
19334
|
+
}
|
|
19335
|
+
try {
|
|
19336
|
+
parts.push(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--log-failed"]));
|
|
19337
|
+
} catch {
|
|
19338
|
+
}
|
|
19339
|
+
const evidence = parts.filter(Boolean).join("\n");
|
|
19340
|
+
return evidence.length > TRANSIENT_EVIDENCE_MAX_CHARS ? evidence.slice(-TRANSIENT_EVIDENCE_MAX_CHARS) : evidence;
|
|
19341
|
+
}
|
|
19342
|
+
function extractFailedRequiredContexts(failure) {
|
|
19343
|
+
const match = /required train check failed:\s*([^\n(]+)/.exec(failure);
|
|
19344
|
+
if (!match) return [];
|
|
19345
|
+
return match[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
19346
|
+
}
|
|
19347
|
+
async function readFailedCheckRunsOnSha(deps, repo, sha) {
|
|
19348
|
+
try {
|
|
19349
|
+
const rows = JSON.parse(await deps.run("gh", [
|
|
19350
|
+
"api",
|
|
19351
|
+
`repos/${repo}/commits/${sha}/check-runs`,
|
|
19352
|
+
"--jq",
|
|
19353
|
+
'[.check_runs[] | select(.status == "completed" and .conclusion != "success" and .conclusion != null) | {name: .name, url: .details_url}]'
|
|
19354
|
+
]));
|
|
19355
|
+
if (!Array.isArray(rows)) return [];
|
|
19356
|
+
return rows.flatMap((r) => {
|
|
19357
|
+
if (typeof r.name !== "string" || typeof r.url !== "string") return [];
|
|
19358
|
+
const id = /\/actions\/runs\/(\d+)/.exec(r.url);
|
|
19359
|
+
return [{ name: r.name, ...id ? { runId: Number(id[1]) } : {} }];
|
|
19360
|
+
});
|
|
19361
|
+
} catch {
|
|
19362
|
+
return [];
|
|
19363
|
+
}
|
|
19364
|
+
}
|
|
19365
|
+
async function correlateCandidateRunsToRequiredContexts(deps, repo, sha, candidates, contexts) {
|
|
19366
|
+
if (contexts.length === 0) {
|
|
19367
|
+
return {
|
|
19368
|
+
unique: false,
|
|
19369
|
+
note: `the failure names no failed required context \u2014 rerunning the first failed run could rerun an unrelated workflow; manual intervention required; safe next action: gh run list --repo ${repo} --commit ${sha.slice(0, 12)} to identify the run`
|
|
19370
|
+
};
|
|
19371
|
+
}
|
|
19372
|
+
const failedChecks = await readFailedCheckRunsOnSha(deps, repo, sha);
|
|
19373
|
+
const contextRunIds = /* @__PURE__ */ new Map();
|
|
19374
|
+
for (const check of failedChecks) {
|
|
19375
|
+
if (check.runId != null && contexts.includes(check.name)) contextRunIds.set(check.runId, check.name);
|
|
19376
|
+
}
|
|
19377
|
+
const proven = candidates.filter((c) => c.runId != null && contextRunIds.has(c.runId));
|
|
19378
|
+
if (proven.length === 1) return { unique: true, candidate: proven[0] };
|
|
19379
|
+
if (proven.length > 1) {
|
|
19380
|
+
return {
|
|
19381
|
+
unique: false,
|
|
19382
|
+
note: `${proven.length} failed runs correspond to the failed required contexts (${contexts.join(", ")}): ${proven.map((c) => failedRunIdentity(c, c.repo ?? repo)).join("; ")} \u2014 ambiguous which to rerun; manual intervention required; safe next action: gh run view <run-id> --repo ${repo} --log-failed`
|
|
19383
|
+
};
|
|
19384
|
+
}
|
|
19385
|
+
return {
|
|
19386
|
+
unique: false,
|
|
19387
|
+
note: `no enumerated failed run corresponds to the failed required contexts (${contexts.join(", ")}) \u2014 the failed run(s) are unrelated to the required gate; manual intervention required; safe next action: gh run view <run-id> --repo ${repo} --log-failed`
|
|
19388
|
+
};
|
|
19389
|
+
}
|
|
19390
|
+
async function transientRecoveryForFailedPhase(deps, input) {
|
|
19391
|
+
const { ledger, anchors, phase, sha, failure } = input;
|
|
19392
|
+
const enumerated = !input.runs;
|
|
19393
|
+
const provenance = input.runsProvenance ?? "sha";
|
|
19394
|
+
const recordProvenance = provenance === "watched" ? "dispatch" : "sha";
|
|
19395
|
+
let candidates = (input.runs ?? await transientRecoveryEnumerate(deps, input.repo, sha)).filter((r) => typeof r.runId === "number" && (r.conclusion === "failure" || r.conclusion === "cancelled"));
|
|
19396
|
+
const first = candidates[0];
|
|
19397
|
+
await ledger.record(anchors, phase, phaseEntry({
|
|
19398
|
+
state: "failed",
|
|
19399
|
+
sha,
|
|
19400
|
+
error: failure,
|
|
19401
|
+
...first ? {
|
|
19402
|
+
runId: first.runId,
|
|
19403
|
+
...first.runUrl ? { runUrl: first.runUrl } : {},
|
|
19404
|
+
runRepo: first.repo ?? input.repo,
|
|
19405
|
+
runProvenance: recordProvenance,
|
|
19406
|
+
workflow: first.workflow,
|
|
19407
|
+
...first.event ? { event: first.event } : {}
|
|
19408
|
+
} : {}
|
|
19409
|
+
}));
|
|
19410
|
+
const recorded = await ledger.load(anchors).catch(() => void 0);
|
|
19411
|
+
const retriesSoFar = recorded?.phases[phase].retries ?? 0;
|
|
19412
|
+
if (retriesSoFar >= AUTO_RERUN_MAX_RETRIES) {
|
|
19413
|
+
const spent = first ? ` for ${failedRunIdentity(first, first.repo ?? input.repo)}` : "";
|
|
19414
|
+
return {
|
|
19415
|
+
reran: false,
|
|
19416
|
+
recovered: false,
|
|
19417
|
+
...first?.runId != null ? { runId: first.runId, runRepo: first.repo ?? input.repo } : {},
|
|
19418
|
+
note: `automatic rerun already spent for ${phase}${spent} \u2014 manual intervention required; ${first?.runId != null ? manualRunAction(first, first.repo ?? input.repo) : "safe next action: identify the failed run on the Actions tab"}`
|
|
19419
|
+
};
|
|
19420
|
+
}
|
|
19421
|
+
if (candidates.length === 0) {
|
|
19422
|
+
return { reran: false, recovered: false, note: `no failed run with an id could be classified on ${sha.slice(0, 12)} \u2014 manual intervention required (no marker proof, no automatic rerun); safe next action: gh run list --repo ${input.repo} --commit ${sha.slice(0, 12)} to identify the run` };
|
|
19423
|
+
}
|
|
19424
|
+
if (enumerated && phase === "ordinaryCi") {
|
|
19425
|
+
const correlation = await correlateCandidateRunsToRequiredContexts(deps, input.repo, sha, candidates, extractFailedRequiredContexts(failure));
|
|
19426
|
+
if (!correlation.unique) return { reran: false, recovered: false, note: correlation.note };
|
|
19427
|
+
candidates = [correlation.candidate];
|
|
19428
|
+
}
|
|
19429
|
+
const candidate = candidates[0];
|
|
19430
|
+
const runRepo = candidate.repo ?? input.repo;
|
|
19431
|
+
let meta;
|
|
19432
|
+
try {
|
|
19433
|
+
meta = JSON.parse(await deps.run("gh", [
|
|
19434
|
+
"run",
|
|
19435
|
+
"view",
|
|
19436
|
+
String(candidate.runId),
|
|
19437
|
+
"--repo",
|
|
19438
|
+
runRepo,
|
|
19439
|
+
"--json",
|
|
19440
|
+
"headSha,status,conclusion,url"
|
|
19441
|
+
]));
|
|
19442
|
+
} catch (e) {
|
|
19443
|
+
return { reran: false, recovered: false, note: `could not READ failed run ${failedRunIdentity(candidate, runRepo)} to prove its identity (${e instanceof Error ? e.message : String(e)}) \u2014 no automatic rerun without proof; ${manualRunAction(candidate, runRepo)}` };
|
|
19444
|
+
}
|
|
19445
|
+
const shaProven = provenance === "watched" || (meta.headSha ?? "").toLowerCase() === sha.toLowerCase();
|
|
19446
|
+
if (!shaProven || meta.conclusion !== "failure") {
|
|
19447
|
+
return { reran: false, recovered: false, note: `run ${failedRunIdentity(candidate, runRepo)} does not prove a failed run on ${sha.slice(0, 12)} \u2014 no automatic rerun; ${manualRunAction(candidate, runRepo)}` };
|
|
19448
|
+
}
|
|
19449
|
+
const evidence = await transientEvidenceForRun(deps, runRepo, candidate.runId);
|
|
19450
|
+
if (!classifyTransientTransportFailure(evidence)) {
|
|
19451
|
+
return { reran: false, recovered: false, note: `run ${failedRunIdentity(candidate, runRepo)} failed on evidence that is NOT a proven transient transport/network failure \u2014 manual intervention required; ${manualRunAction(candidate, runRepo)}` };
|
|
19452
|
+
}
|
|
19453
|
+
const reservation = await ledger.record(anchors, phase, phaseEntry({
|
|
19454
|
+
state: "pending",
|
|
19455
|
+
sha,
|
|
19456
|
+
runId: candidate.runId,
|
|
19457
|
+
...meta.url ?? candidate.runUrl ? { runUrl: meta.url ?? candidate.runUrl } : {},
|
|
19458
|
+
runRepo,
|
|
19459
|
+
runProvenance: recordProvenance,
|
|
19460
|
+
workflow: candidate.workflow,
|
|
19461
|
+
...candidate.event ? { event: candidate.event } : {},
|
|
19462
|
+
note: "ONE automatic transient rerun RESERVED (cap spent durably) \u2014 dispatch pending",
|
|
19463
|
+
retries: retriesSoFar + 1
|
|
19464
|
+
}));
|
|
19465
|
+
if (!reservation.ok) {
|
|
19466
|
+
return { reran: false, recovered: false, note: `the rerun reservation could not be durably recorded (${reservation.error}) \u2014 no automatic rerun without a durable cap; manual intervention required; ${manualRunAction(candidate, runRepo)}` };
|
|
19467
|
+
}
|
|
19468
|
+
try {
|
|
19469
|
+
await deps.run("gh", ["run", "rerun", String(candidate.runId), "--repo", runRepo, "--failed"]);
|
|
19470
|
+
} catch (e) {
|
|
19471
|
+
await ledger.record(anchors, phase, phaseEntry({
|
|
19472
|
+
state: "failed",
|
|
19473
|
+
sha,
|
|
19474
|
+
runId: candidate.runId,
|
|
19475
|
+
...meta.url ?? candidate.runUrl ? { runUrl: meta.url ?? candidate.runUrl } : {},
|
|
19476
|
+
runRepo,
|
|
19477
|
+
runProvenance: recordProvenance,
|
|
19478
|
+
workflow: candidate.workflow,
|
|
19479
|
+
...candidate.event ? { event: candidate.event } : {},
|
|
19480
|
+
error: `automatic rerun dispatch failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
19481
|
+
retries: retriesSoFar + 1
|
|
19482
|
+
}));
|
|
19483
|
+
return {
|
|
19484
|
+
reran: true,
|
|
19485
|
+
recovered: false,
|
|
19486
|
+
runId: candidate.runId,
|
|
19487
|
+
runRepo,
|
|
19488
|
+
note: `automatic rerun of ${failedRunIdentity(candidate, runRepo)} FAILED to dispatch (${e instanceof Error ? e.message : String(e)}) \u2014 the one automatic rerun remains spent; manual intervention required; ${manualRunAction(candidate, runRepo)}`
|
|
19489
|
+
};
|
|
19490
|
+
}
|
|
19491
|
+
const watched = await (input.watchRun ?? defaultWatchRun)(deps, candidate.runId, runRepo);
|
|
19492
|
+
const runFields = {
|
|
19493
|
+
sha,
|
|
19494
|
+
runId: candidate.runId,
|
|
19495
|
+
...meta.url ?? candidate.runUrl ? { runUrl: meta.url ?? candidate.runUrl } : {},
|
|
19496
|
+
runRepo,
|
|
19497
|
+
runProvenance: recordProvenance,
|
|
19498
|
+
workflow: candidate.workflow,
|
|
19499
|
+
...candidate.event ? { event: candidate.event } : {}
|
|
19500
|
+
};
|
|
19501
|
+
if (watched === "success") {
|
|
19502
|
+
await ledger.record(anchors, phase, phaseEntry({
|
|
19503
|
+
state: "complete",
|
|
19504
|
+
...runFields,
|
|
19505
|
+
note: `recovered: the ONE automatic rerun was watched green in this invocation`,
|
|
19506
|
+
retries: retriesSoFar + 1
|
|
19507
|
+
}));
|
|
19508
|
+
return {
|
|
19509
|
+
reran: true,
|
|
19510
|
+
recovered: true,
|
|
19511
|
+
runId: candidate.runId,
|
|
19512
|
+
runRepo,
|
|
19513
|
+
note: `${failedRunIdentity(candidate, runRepo)} was a proven transient transport failure \u2014 the ONE automatic rerun was dispatched and watched GREEN; continuing in this invocation`
|
|
19514
|
+
};
|
|
19515
|
+
}
|
|
19516
|
+
if (watched === "failure") {
|
|
19517
|
+
await ledger.record(anchors, phase, phaseEntry({
|
|
19518
|
+
state: "failed",
|
|
19519
|
+
...runFields,
|
|
19520
|
+
error: "the automatic rerun was watched FAILURE \u2014 not transient after all",
|
|
19521
|
+
retries: retriesSoFar + 1
|
|
19522
|
+
}));
|
|
19523
|
+
return {
|
|
19524
|
+
reran: true,
|
|
19525
|
+
recovered: false,
|
|
19526
|
+
runId: candidate.runId,
|
|
19527
|
+
runRepo,
|
|
19528
|
+
note: `the automatic rerun of ${failedRunIdentity(candidate, runRepo)} FAILED \u2014 the one automatic rerun is spent; manual intervention required; ${manualRunAction(candidate, runRepo)}`
|
|
19529
|
+
};
|
|
19530
|
+
}
|
|
19531
|
+
return {
|
|
19532
|
+
reran: true,
|
|
19533
|
+
recovered: false,
|
|
19534
|
+
runId: candidate.runId,
|
|
19535
|
+
runRepo,
|
|
19536
|
+
note: `the automatic rerun of ${failedRunIdentity(candidate, runRepo)} did not resolve within the bounded watch \u2014 typed PENDING (the one automatic rerun is spent); safe next action: gh run watch ${candidate.runId} --repo ${runRepo} --exit-status`
|
|
19537
|
+
};
|
|
19538
|
+
}
|
|
19539
|
+
async function transientRecoveryEnumerate(deps, repo, sha) {
|
|
19540
|
+
return readFailedRunsOnSha(deps, repo, sha);
|
|
19541
|
+
}
|
|
19542
|
+
var RUN_CONFIRM_ATTEMPTS2 = 3;
|
|
19543
|
+
var RUN_CONFIRM_DELAY_MS2 = 1e3;
|
|
19544
|
+
async function defaultWatchRun(deps, runId, repo) {
|
|
19545
|
+
let watchSaidSuccess = true;
|
|
19546
|
+
try {
|
|
19547
|
+
await deps.run("gh", ["run", "watch", String(runId), "--repo", repo, "--exit-status"]);
|
|
19548
|
+
} catch {
|
|
19549
|
+
watchSaidSuccess = false;
|
|
19550
|
+
}
|
|
19551
|
+
let confirmError;
|
|
19552
|
+
for (let attempt = 0; attempt < RUN_CONFIRM_ATTEMPTS2; attempt++) {
|
|
19553
|
+
if (attempt > 0) await new Promise((resolve7) => setTimeout(resolve7, RUN_CONFIRM_DELAY_MS2));
|
|
19554
|
+
try {
|
|
19555
|
+
const parsed = JSON.parse(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "status,conclusion"]));
|
|
19556
|
+
if (parsed.status === "completed") return parsed.conclusion === "success" ? "success" : "failure";
|
|
19557
|
+
confirmError = void 0;
|
|
19558
|
+
} catch (e) {
|
|
19559
|
+
confirmError = e;
|
|
19560
|
+
}
|
|
19561
|
+
}
|
|
19562
|
+
if (!watchSaidSuccess) return "failure";
|
|
19563
|
+
if (confirmError) return "pending";
|
|
19564
|
+
return "success";
|
|
19565
|
+
}
|
|
19566
|
+
function alignmentPhaseEntry(dev, rc) {
|
|
19567
|
+
if (!dev && !rc) return { state: "skipped", note: "no protected-branch alignment leg on this release" };
|
|
19568
|
+
const pending = dev?.status === "pr-pending" || rc?.status === "pr-pending";
|
|
19569
|
+
const note = [dev ? `development: ${dev.note}` : void 0, rc ? `rc: ${rc.note}` : void 0].filter(Boolean).join("; ");
|
|
19570
|
+
return { state: pending ? "pending" : "complete", note };
|
|
19571
|
+
}
|
|
19572
|
+
function runRowState(run, fallback) {
|
|
19573
|
+
if (!run) return fallback === "success" ? "complete" : fallback === "failure" ? "failed" : "pending";
|
|
19574
|
+
return run.conclusion === "success" ? "complete" : run.conclusion === "failure" ? "failed" : "pending";
|
|
19575
|
+
}
|
|
19576
|
+
function runMetadata(run, runRepo, provenance) {
|
|
19577
|
+
return {
|
|
19578
|
+
...run.runId != null ? { runId: run.runId } : {},
|
|
19579
|
+
...run.runUrl != null ? { runUrl: run.runUrl } : {},
|
|
19580
|
+
...run.runId != null ? { runRepo, runProvenance: provenance } : {},
|
|
19581
|
+
...run.workflow ? { workflow: run.workflow } : {},
|
|
19582
|
+
...run.event ? { event: run.event } : {}
|
|
19583
|
+
};
|
|
19584
|
+
}
|
|
19585
|
+
function releaseRunRepoFor(model, productRepo) {
|
|
19586
|
+
return model === "hub-serverless" || isCentralDispatchModel(model) ? HUB_REPO2 : productRepo;
|
|
19587
|
+
}
|
|
19588
|
+
function deployPhaseInput(model, dispatch, opts = {}) {
|
|
19589
|
+
const sha = opts.releaseSha;
|
|
19590
|
+
const runRepo = releaseRunRepoFor(model, "");
|
|
19591
|
+
switch (model) {
|
|
19592
|
+
case "hub-serverless": {
|
|
19593
|
+
const deployRun = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "deploy.yml");
|
|
19594
|
+
return {
|
|
19595
|
+
state: runRowState(deployRun, dispatch.deployStatus),
|
|
19596
|
+
...sha ? { sha } : {},
|
|
19597
|
+
...runMetadata(deployRun ?? {}, runRepo, "sha"),
|
|
19598
|
+
note: deployRun ? `${deployRun.workflow} release run` : dispatch.note
|
|
19599
|
+
};
|
|
19600
|
+
}
|
|
19601
|
+
case "registry-publish":
|
|
19602
|
+
return { state: "skipped", ...sha ? { sha } : {}, note: "registry-publish deploys by publishing \u2014 the release-event publish.yml run is the deploy plane" };
|
|
19603
|
+
case "tenant-container":
|
|
19604
|
+
case "solo-container":
|
|
19605
|
+
case "static-cdn":
|
|
19606
|
+
return {
|
|
19607
|
+
state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
|
|
19608
|
+
...sha ? { sha } : {},
|
|
19609
|
+
...runMetadata({ runId: dispatch.runId, runUrl: dispatch.runUrl, workflow: "tenant-deploy.yml", event: "workflow_dispatch" }, runRepo, "dispatch"),
|
|
19610
|
+
note: dispatch.note
|
|
19611
|
+
};
|
|
19612
|
+
default:
|
|
19613
|
+
return {
|
|
19614
|
+
state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
|
|
19615
|
+
...sha ? { sha } : {},
|
|
19616
|
+
note: dispatch.note
|
|
19617
|
+
};
|
|
19618
|
+
}
|
|
19619
|
+
}
|
|
19620
|
+
function publishPhaseInput(model, dispatch, publishDispatch, publishRequired, opts = {}) {
|
|
19621
|
+
const sha = opts.releaseSha;
|
|
19622
|
+
const publishRow = publishDispatch ? {
|
|
19623
|
+
state: publishDispatch.deployStatus === "success" ? "complete" : publishDispatch.deployStatus === "failure" ? "failed" : "pending",
|
|
19624
|
+
...runMetadata({ runId: publishDispatch.runId, runUrl: publishDispatch.runUrl, workflow: "tenant-publish.yml", event: "workflow_dispatch" }, HUB_REPO2, "dispatch"),
|
|
19625
|
+
note: publishDispatch.note
|
|
19626
|
+
} : void 0;
|
|
19627
|
+
switch (model) {
|
|
19628
|
+
case "hub-serverless": {
|
|
19629
|
+
const publishRun = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "publish.yml");
|
|
19630
|
+
return publishRow ?? {
|
|
19631
|
+
state: runRowState(publishRun, dispatch.deployStatus),
|
|
19632
|
+
...sha ? { sha } : {},
|
|
19633
|
+
...runMetadata(publishRun ?? {}, HUB_REPO2, "sha"),
|
|
19634
|
+
note: publishRun ? `${publishRun.workflow} release run` : dispatch.note
|
|
19635
|
+
};
|
|
19636
|
+
}
|
|
19637
|
+
case "registry-publish": {
|
|
19638
|
+
const publishRun = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "publish.yml");
|
|
19639
|
+
return publishRow ?? {
|
|
19640
|
+
state: runRowState(publishRun, dispatch.deployStatus),
|
|
19641
|
+
...sha ? { sha } : {},
|
|
19642
|
+
...runMetadata(publishRun ?? {}, opts.repo ?? "", "sha"),
|
|
19643
|
+
note: publishRun ? `${publishRun.workflow} release run` : dispatch.note
|
|
19644
|
+
};
|
|
19645
|
+
}
|
|
19646
|
+
case "tenant-container":
|
|
19647
|
+
case "solo-container":
|
|
19648
|
+
case "static-cdn":
|
|
19649
|
+
return publishRow ?? (publishRequired ? { state: "skipped", ...sha ? { sha } : {}, note: opts.skipPublishNote ?? "publish skipped \u2014 deploy not confirmed" } : { state: "skipped", ...sha ? { sha } : {}, note: "publishRequired=false \u2014 no publish leg" });
|
|
19650
|
+
default:
|
|
19651
|
+
return { state: "skipped", ...sha ? { sha } : {}, note: `${model} has no publish leg` };
|
|
19652
|
+
}
|
|
19653
|
+
}
|
|
19654
|
+
function phaseInputsFromRunRows(model, rows, tagSha, opts = {}) {
|
|
19655
|
+
const deployRun = rows.find((r) => r.workflow === "deploy.yml");
|
|
19656
|
+
const publishRun = rows.find((r) => r.workflow === "publish.yml");
|
|
19657
|
+
switch (model) {
|
|
19658
|
+
case "hub-serverless":
|
|
19659
|
+
return {
|
|
19660
|
+
deploy: {
|
|
19661
|
+
state: runRowState(deployRun, "pending"),
|
|
19662
|
+
sha: tagSha,
|
|
19663
|
+
...runMetadata(deployRun ?? {}, HUB_REPO2, "sha"),
|
|
19664
|
+
note: deployRun ? `${deployRun.workflow} run on the release SHA` : "deploy.yml release run not yet correlated"
|
|
19665
|
+
},
|
|
19666
|
+
publish: publishRun ? {
|
|
19667
|
+
state: runRowState(publishRun, "pending"),
|
|
19668
|
+
sha: tagSha,
|
|
19669
|
+
...runMetadata(publishRun, HUB_REPO2, "sha"),
|
|
19670
|
+
note: `${publishRun.workflow} run on the release SHA`
|
|
19671
|
+
} : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
|
|
19672
|
+
};
|
|
19673
|
+
case "registry-publish":
|
|
19674
|
+
return {
|
|
19675
|
+
deploy: { state: "skipped", sha: tagSha, note: "registry-publish deploys by publishing \u2014 the release-event publish.yml run is the deploy plane" },
|
|
19676
|
+
publish: publishRun ? {
|
|
19677
|
+
state: runRowState(publishRun, "pending"),
|
|
19678
|
+
sha: tagSha,
|
|
19679
|
+
...runMetadata(publishRun, opts.repo ?? "", "sha"),
|
|
19680
|
+
note: `${publishRun.workflow} run on the release SHA`
|
|
19681
|
+
} : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
|
|
19682
|
+
};
|
|
19683
|
+
default:
|
|
19684
|
+
return {
|
|
19685
|
+
deploy: { state: "pending", sha: tagSha, note: `${model} deploys outside the release SHA's run set \u2014 historical outcome uncorrelatable` },
|
|
19686
|
+
publish: { state: "skipped", sha: tagSha, note: "no historical publish leg correlated for this model" }
|
|
19687
|
+
};
|
|
19688
|
+
}
|
|
19689
|
+
}
|
|
19690
|
+
async function reverifyPersistedRunPhases(deps, path2, anchors, input) {
|
|
19691
|
+
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
19692
|
+
const notes = [];
|
|
19693
|
+
const result = await withReleaseLedger(
|
|
19694
|
+
path2,
|
|
19695
|
+
anchors,
|
|
19696
|
+
async (current) => {
|
|
19697
|
+
if (!current) return { value: { ledger: void 0, notes } };
|
|
19698
|
+
const phases = { ...current.phases };
|
|
19699
|
+
for (const phase of ["deploy", "publish"]) {
|
|
19700
|
+
const rec = phases[phase];
|
|
19701
|
+
if (rec.updatedAt === "never") continue;
|
|
19702
|
+
if (rec.runId == null) {
|
|
19703
|
+
if (rec.state === "complete") notes.push(`${phase}: persisted complete without a run id \u2014 the live deploy/publish re-derivation below decides the leg`);
|
|
19704
|
+
continue;
|
|
19705
|
+
}
|
|
19706
|
+
if (rec.runProvenance !== "sha" && rec.runProvenance !== "dispatch") {
|
|
19707
|
+
throw new ReleaseLedgerError(
|
|
19708
|
+
"invalid-record",
|
|
19709
|
+
`the persisted ${phase} boundary names run ${rec.runId} without valid runProvenance ('sha'|'dispatch' required, got ${JSON.stringify(rec.runProvenance ?? null)}) \u2014 a run-backed boundary without provenance cannot be re-verified; failing closed (#5987)`
|
|
19710
|
+
);
|
|
19711
|
+
}
|
|
19712
|
+
const runRepo = rec.runRepo ?? input.repo;
|
|
19713
|
+
let meta;
|
|
19714
|
+
try {
|
|
19715
|
+
meta = JSON.parse(await deps.run("gh", [
|
|
19716
|
+
"run",
|
|
19717
|
+
"view",
|
|
19718
|
+
String(rec.runId),
|
|
19719
|
+
"--repo",
|
|
19720
|
+
runRepo,
|
|
19721
|
+
"--json",
|
|
19722
|
+
"status,conclusion,headSha,url"
|
|
19723
|
+
]));
|
|
19724
|
+
} catch (e) {
|
|
19725
|
+
throw new ReleaseLedgerError(
|
|
19726
|
+
"unreadable",
|
|
19727
|
+
`resume could not re-verify the persisted ${phase} boundary (run ${rec.runId} in ${runRepo}: ${e instanceof Error ? e.message : String(e)}) \u2014 a failed live read is unverified, not passable; retry when GitHub answers (#5987/#4713)`
|
|
19728
|
+
);
|
|
19729
|
+
}
|
|
19730
|
+
if (rec.runProvenance === "sha" && (meta.headSha ?? "").toLowerCase() !== input.sha.toLowerCase()) {
|
|
19731
|
+
throw new ReleaseLedgerError(
|
|
19732
|
+
"tag-sha-mismatch",
|
|
19733
|
+
`the persisted ${phase} boundary names run ${rec.runId} at ${(meta.headSha ?? "").slice(0, 12)} but the release tag is ${input.sha.slice(0, 12)} \u2014 ledger contradicted by live proof; failing closed (#5987)`
|
|
19734
|
+
);
|
|
19735
|
+
}
|
|
19736
|
+
const liveLabel = `${rec.workflow ? `${rec.workflow}${rec.event ? ` (${rec.event})` : ""} ` : ""}run ${rec.runId} in ${runRepo}`;
|
|
19737
|
+
if (meta.status !== "completed") {
|
|
19738
|
+
phases[phase] = { ...rec, state: "pending", note: `re-verified live: ${liveLabel} still in flight`, updatedAt: now().toISOString() };
|
|
19739
|
+
notes.push(`${phase}: ${liveLabel} still in flight \u2014 leg re-verified as pending`);
|
|
19740
|
+
continue;
|
|
19741
|
+
}
|
|
19742
|
+
if (meta.conclusion === "success") {
|
|
19743
|
+
if (rec.state !== "complete") {
|
|
19744
|
+
phases[phase] = { ...rec, state: "complete", note: `re-verified live: ${liveLabel} concluded success`, updatedAt: now().toISOString() };
|
|
19745
|
+
notes.push(`${phase}: ${liveLabel} re-verified green live`);
|
|
19746
|
+
}
|
|
19747
|
+
continue;
|
|
19748
|
+
}
|
|
19749
|
+
if (rec.state === "complete") {
|
|
19750
|
+
throw new ReleaseLedgerError(
|
|
19751
|
+
"contradicted",
|
|
19752
|
+
`the persisted ${phase} boundary says COMPLETE but live ${liveLabel} concluded ${meta.conclusion ?? "not-success"} \u2014 ledger contradicted by live GitHub proof; failing closed instead of trusting either (#5987)`
|
|
19753
|
+
);
|
|
19754
|
+
}
|
|
19755
|
+
phases[phase] = { ...rec, state: "failed", error: `live re-verification: ${liveLabel} concluded ${meta.conclusion}`, updatedAt: now().toISOString() };
|
|
19756
|
+
notes.push(`${phase}: ${liveLabel} failed live \u2014 leg re-verified as failed`);
|
|
19757
|
+
}
|
|
19758
|
+
const ledger = { ...current, phases, updatedAt: now().toISOString() };
|
|
19759
|
+
return { ledger, value: { ledger, notes } };
|
|
19760
|
+
}
|
|
19761
|
+
);
|
|
19762
|
+
return result;
|
|
19763
|
+
}
|
|
19764
|
+
async function clearLedgerFile(path2, anchors, maxWaitMs) {
|
|
19765
|
+
return withFileLock(`${path2}.lock`, { label: "release ledger", maxWaitMs }, async () => {
|
|
19766
|
+
let raw;
|
|
19767
|
+
try {
|
|
19768
|
+
raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
19769
|
+
} catch (e) {
|
|
19770
|
+
if (e.code === "ENOENT") return { cleared: false };
|
|
19771
|
+
return { cleared: false, note: `release ledger at ${path2} could not be read for clearing (${e instanceof Error ? e.message : String(e)}) \u2014 left in place` };
|
|
19772
|
+
}
|
|
19773
|
+
let ledger;
|
|
19774
|
+
try {
|
|
19775
|
+
ledger = parseReleaseLedger(raw);
|
|
19776
|
+
} catch (e) {
|
|
19777
|
+
return { cleared: false, note: `release ledger at ${path2} is unreadable (${e instanceof Error ? e.message : String(e)}) \u2014 left in place; inspect it by hand` };
|
|
19778
|
+
}
|
|
19779
|
+
try {
|
|
19780
|
+
validateLedgerAnchors(ledger, anchors);
|
|
19781
|
+
} catch (e) {
|
|
19782
|
+
return { cleared: false, note: `release ledger at ${path2} belongs to ${ledger.tag} @ ${ledger.tagSha.slice(0, 12)} \u2014 not this release; left in place (${e instanceof Error ? e.message : String(e)})` };
|
|
19783
|
+
}
|
|
19784
|
+
(0, import_node_fs19.unlinkSync)(path2);
|
|
19785
|
+
return { cleared: true, note: `cleared the release ledger at ${path2} for the live-proven aborted ${anchors.tag}` };
|
|
19786
|
+
});
|
|
19787
|
+
}
|
|
19788
|
+
async function clearReleaseLedger(cwd, anchors, opts = {}) {
|
|
19789
|
+
let path2;
|
|
19790
|
+
try {
|
|
19791
|
+
path2 = releaseLedgerPath(cwd);
|
|
19792
|
+
} catch {
|
|
19793
|
+
return void 0;
|
|
19794
|
+
}
|
|
19795
|
+
return (await clearLedgerFile(path2, anchors, opts.maxWaitMs)).note;
|
|
19796
|
+
}
|
|
19797
|
+
async function reconcileLedgerAfterFailedTagPush(deps, ledger, anchors) {
|
|
19798
|
+
let remoteOut;
|
|
19799
|
+
try {
|
|
19800
|
+
remoteOut = await deps.run("git", ["ls-remote", "origin", `refs/tags/${anchors.tag}`]);
|
|
19801
|
+
} catch (e) {
|
|
19802
|
+
return {
|
|
19803
|
+
action: "kept-unproven",
|
|
19804
|
+
note: `the tag-existence read for ${anchors.tag} FAILED (${e instanceof Error ? e.message : String(e)}) \u2014 the just-started all-pending ledger is LEFT in place (never cleared on a failed read); it will block the next release until this release is resolved by hand (#5987)`
|
|
19805
|
+
};
|
|
19806
|
+
}
|
|
19807
|
+
const remoteSha = remoteOut.trim().split(/\s+/)[0] ?? "";
|
|
19808
|
+
if (remoteSha.toLowerCase() === anchors.tagSha.toLowerCase()) {
|
|
19809
|
+
return {
|
|
19810
|
+
action: "kept-present",
|
|
19811
|
+
note: `origin shows ${anchors.tag} at this release SHA ${remoteSha.slice(0, 12)} \u2014 the partial release state stands and the ledger is kept; resume with mmi-cli devops release --resume (do not re-tag) (#5987)`
|
|
19812
|
+
};
|
|
19813
|
+
}
|
|
19814
|
+
let clear;
|
|
19815
|
+
try {
|
|
19816
|
+
clear = await clearLedgerFile(ledger.path, anchors);
|
|
19817
|
+
} catch (e) {
|
|
19818
|
+
clear = { cleared: false, note: `clear failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
19819
|
+
}
|
|
19820
|
+
if (!clear.cleared && clear.note) {
|
|
19821
|
+
return { action: "kept-unproven", note: `the just-started ledger could not be cleared after origin proved this run has no resumable tag (${clear.note}) \u2014 resolve it by hand (#5987)` };
|
|
19822
|
+
}
|
|
19823
|
+
return {
|
|
19824
|
+
action: "cleared",
|
|
19825
|
+
note: remoteSha ? `origin shows ${anchors.tag} at foreign SHA ${remoteSha.slice(0, 12)}, not this run's ${anchors.tagSha.slice(0, 12)} \u2014 this run's just-started ledger was cleared; let the owning release finish before retrying (#5987)` : `origin proves ${anchors.tag} was never pushed (no tag on origin) \u2014 the just-started all-pending ledger was cleared, so the next release is not blocked; rerun the release once the failure above is fixed (#5987)`
|
|
19826
|
+
};
|
|
19827
|
+
}
|
|
19828
|
+
async function startReleaseLedger(ledger, anchors, opts = {}) {
|
|
19829
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
19830
|
+
try {
|
|
19831
|
+
return await withReleaseLedger(
|
|
19832
|
+
ledger.path,
|
|
19833
|
+
anchors,
|
|
19834
|
+
(current) => {
|
|
19835
|
+
if (current) return { value: { ok: true } };
|
|
19836
|
+
const normalized = normalizeLedgerAnchors(anchors);
|
|
19837
|
+
const fresh = {
|
|
19838
|
+
schema: RELEASE_LEDGER_SCHEMA,
|
|
19839
|
+
repo: normalized.repo,
|
|
19840
|
+
tag: normalized.tag,
|
|
19841
|
+
tagSha: normalized.tagSha,
|
|
19842
|
+
phases: emptyPhases(),
|
|
19843
|
+
updatedAt: now().toISOString()
|
|
19844
|
+
};
|
|
19845
|
+
return { ledger: fresh, value: { ok: true } };
|
|
19846
|
+
}
|
|
19847
|
+
);
|
|
19848
|
+
} catch (e) {
|
|
19849
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
19850
|
+
}
|
|
19851
|
+
}
|
|
19852
|
+
async function beginReleaseLedger(deps, cwd, targetTag) {
|
|
19853
|
+
let path2;
|
|
19854
|
+
try {
|
|
19855
|
+
path2 = releaseLedgerPath(cwd);
|
|
19856
|
+
} catch {
|
|
19857
|
+
return { ok: true };
|
|
19858
|
+
}
|
|
19859
|
+
return withFileLock(`${path2}.lock`, { label: "release ledger" }, async () => {
|
|
19860
|
+
let raw;
|
|
19861
|
+
try {
|
|
19862
|
+
raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
19863
|
+
} catch (e) {
|
|
19864
|
+
if (e.code === "ENOENT") return { ok: true };
|
|
19865
|
+
return { ok: false, error: `the release ledger at ${path2} could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not absent (#5987/#4713)` };
|
|
19866
|
+
}
|
|
19867
|
+
let prior;
|
|
19868
|
+
try {
|
|
19869
|
+
prior = parseReleaseLedger(raw);
|
|
19870
|
+
} catch (e) {
|
|
19871
|
+
return { ok: false, error: `the active release ledger at ${path2} is unreadable (${e instanceof Error ? e.message : String(e)}) \u2014 resolve it by hand before starting a release (#5987)` };
|
|
19872
|
+
}
|
|
19873
|
+
if (targetTag && prior.tag === targetTag) return { ok: true };
|
|
19874
|
+
const derived = deriveReleasePhaseStatus(prior);
|
|
19875
|
+
if (derived.phaseStatus !== "phases-green") {
|
|
19876
|
+
const open2 = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
|
|
19877
|
+
return {
|
|
19878
|
+
ok: false,
|
|
19879
|
+
error: `a prior release ledger for ${prior.tag} is ${derived.phaseStatus} (${open2.map((l) => `${l.phase}=${l.state}`).join(", ")}) \u2014 a new release must not stack on an unresolved one. Finish it first: mmi-cli devops release --resume (or, only when proven unpublished, mmi-cli devops release --abort --apply) (#5987)`
|
|
19880
|
+
};
|
|
19881
|
+
}
|
|
19882
|
+
let liveSha = "";
|
|
19883
|
+
try {
|
|
19884
|
+
liveSha = (await deps.run("git", ["ls-remote", "origin", `refs/tags/${prior.tag}`])).trim().split(/\s+/)[0] ?? "";
|
|
19885
|
+
} catch (e) {
|
|
19886
|
+
return { ok: false, error: `could not prove the completed ${prior.tag} live (${e instanceof Error ? e.message : String(e)}) \u2014 refusing to archive the ledger; retry when origin answers (#5987/#4713)` };
|
|
19887
|
+
}
|
|
19888
|
+
if (liveSha.toLowerCase() !== prior.tagSha.toLowerCase()) {
|
|
19889
|
+
return { ok: false, error: `the completed ${prior.tag} ledger says phases-green but origin reports ${liveSha.slice(0, 12) || "no tag"} \u2260 ${prior.tagSha.slice(0, 12)} \u2014 refusing to archive on unproven state; resolve by hand (#5987)` };
|
|
19890
|
+
}
|
|
19891
|
+
const archivePath = (0, import_node_path18.join)((0, import_node_path18.dirname)(path2), `phases.archive-${prior.tag}.json`);
|
|
19892
|
+
try {
|
|
19893
|
+
(0, import_node_fs19.unlinkSync)(archivePath);
|
|
19894
|
+
} catch (e) {
|
|
19895
|
+
if (e.code !== "ENOENT") {
|
|
19896
|
+
return { ok: false, error: `could not clear the stale archive at ${archivePath} (${e instanceof Error ? e.message : String(e)}) \u2014 the ledger for ${prior.tag} stays active (#5987)` };
|
|
19897
|
+
}
|
|
19898
|
+
}
|
|
19899
|
+
(0, import_node_fs19.renameSync)(path2, archivePath);
|
|
19900
|
+
return { ok: true, note: `archived the completed release ledger ${prior.tag} to ${archivePath} (live-proven at ${prior.tagSha.slice(0, 12)})` };
|
|
19901
|
+
});
|
|
19902
|
+
}
|
|
19903
|
+
|
|
18821
19904
|
// src/train-apply-phases.ts
|
|
18822
19905
|
var TRAIN_BUMP_INTENTS = ["major", "minor", "patch"];
|
|
18823
19906
|
function readLocalGateWorkflows() {
|
|
18824
|
-
const dir = (0,
|
|
19907
|
+
const dir = (0, import_node_path19.join)(".github", "workflows");
|
|
18825
19908
|
let names;
|
|
18826
19909
|
try {
|
|
18827
|
-
names = (0,
|
|
19910
|
+
names = (0, import_node_fs20.readdirSync)(dir);
|
|
18828
19911
|
} catch {
|
|
18829
19912
|
return null;
|
|
18830
19913
|
}
|
|
18831
19914
|
const files = [];
|
|
18832
19915
|
for (const name of names.filter(isGateWorkflowPath)) {
|
|
18833
19916
|
try {
|
|
18834
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
19917
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs20.readFileSync)((0, import_node_path19.join)(dir, name), "utf8") });
|
|
18835
19918
|
} catch {
|
|
18836
19919
|
}
|
|
18837
19920
|
}
|
|
18838
19921
|
return files;
|
|
18839
19922
|
}
|
|
18840
19923
|
function readLocalWorkflows() {
|
|
18841
|
-
const dir = (0,
|
|
19924
|
+
const dir = (0, import_node_path19.join)(".github", "workflows");
|
|
18842
19925
|
let names;
|
|
18843
19926
|
try {
|
|
18844
|
-
names = (0,
|
|
19927
|
+
names = (0, import_node_fs20.readdirSync)(dir);
|
|
18845
19928
|
} catch {
|
|
18846
19929
|
return null;
|
|
18847
19930
|
}
|
|
18848
19931
|
const files = [];
|
|
18849
19932
|
for (const name of names.filter((n) => /\.ya?ml$/i.test(n))) {
|
|
18850
19933
|
try {
|
|
18851
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
19934
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs20.readFileSync)((0, import_node_path19.join)(dir, name), "utf8") });
|
|
18852
19935
|
} catch {
|
|
18853
19936
|
}
|
|
18854
19937
|
}
|
|
@@ -18986,7 +20069,7 @@ async function waitForFoldIndexLock(deps, startBranch, resumeCommand) {
|
|
|
18986
20069
|
const deadline = now() + GIT_INDEX_LOCK_WAIT_MS;
|
|
18987
20070
|
for (; ; ) {
|
|
18988
20071
|
try {
|
|
18989
|
-
await (0,
|
|
20072
|
+
await (0, import_promises3.stat)(lockPath);
|
|
18990
20073
|
} catch (e) {
|
|
18991
20074
|
if (e.code === "ENOENT") return;
|
|
18992
20075
|
throw foldFailureGuidance(
|
|
@@ -19316,8 +20399,35 @@ async function gateTrueMergeCommit(deps, ctx, tag, releaseSha, requiredChecks) {
|
|
|
19316
20399
|
}
|
|
19317
20400
|
}
|
|
19318
20401
|
}
|
|
19319
|
-
async function
|
|
20402
|
+
async function recordPhase(ledger, deps, anchors, phase, entry, opts = {}) {
|
|
20403
|
+
if (!ledger) return;
|
|
20404
|
+
const res = await ledger.record(anchors, phase, entry);
|
|
20405
|
+
if (!res.ok) {
|
|
20406
|
+
const message = `release ledger write FAILED for phase '${phase}' (${res.error}) - already landed and immutable: ${opts.landed ?? "no boundary recorded yet"}. Nothing is rolled back, but this run stops here rather than mutating untracked: resolve the ledger at the recorded path and rerun mmi-cli devops release --resume (#5987)`;
|
|
20407
|
+
if (opts.strict !== false) throw new Error(message);
|
|
20408
|
+
deps.warn?.(message);
|
|
20409
|
+
}
|
|
20410
|
+
}
|
|
20411
|
+
async function recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, phase, input) {
|
|
20412
|
+
if (!ledger) return { reran: false, recovered: false, note: "" };
|
|
20413
|
+
try {
|
|
20414
|
+
return await transientRecoveryForFailedPhase(deps, {
|
|
20415
|
+
ledger,
|
|
20416
|
+
anchors,
|
|
20417
|
+
phase,
|
|
20418
|
+
repo: input.repo,
|
|
20419
|
+
sha: input.sha,
|
|
20420
|
+
failure: input.failure,
|
|
20421
|
+
...input.runs ? { runs: input.runs } : {},
|
|
20422
|
+
runsProvenance: input.watched ? "watched" : "sha"
|
|
20423
|
+
});
|
|
20424
|
+
} catch (e) {
|
|
20425
|
+
return { reran: false, recovered: false, note: `release ledger: the transient recovery read failed (${e instanceof Error ? e.message : String(e)}) - manual intervention required` };
|
|
20426
|
+
}
|
|
20427
|
+
}
|
|
20428
|
+
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger) {
|
|
19320
20429
|
const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
|
|
20430
|
+
const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
|
|
19321
20431
|
try {
|
|
19322
20432
|
await verifyPublishDryRun(deps, ctx, meta, deployModel);
|
|
19323
20433
|
} catch (e) {
|
|
@@ -19344,45 +20454,128 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
19344
20454
|
}
|
|
19345
20455
|
}
|
|
19346
20456
|
let tagPush;
|
|
20457
|
+
let tagLedgerStarted = false;
|
|
20458
|
+
const recoveryInput = { repo: ctx.repo, tag, stage: "main", deployModel, meta, branchHints };
|
|
19347
20459
|
try {
|
|
19348
20460
|
await assertComputedReleaseTagStillNext(deps, tag);
|
|
19349
20461
|
if (deployModel === "hub-serverless") {
|
|
19350
20462
|
await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", tag.replace(/^v/, ""), "--release-commit", releaseSha]);
|
|
19351
20463
|
}
|
|
20464
|
+
if (ledger) {
|
|
20465
|
+
const start = await startReleaseLedger(ledger, ledgerAnchors);
|
|
20466
|
+
if (!start.ok) {
|
|
20467
|
+
throw new Error(
|
|
20468
|
+
`release refused before the tag was pushed: the durable phase ledger could not be started at ${ledger.path} (${start.error}) - nothing was tagged or pushed; repair the ledger location and rerun the release (#5987)`
|
|
20469
|
+
);
|
|
20470
|
+
}
|
|
20471
|
+
tagLedgerStarted = true;
|
|
20472
|
+
}
|
|
19352
20473
|
tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe, ctx.repo);
|
|
19353
20474
|
} catch (e) {
|
|
20475
|
+
if (ledger && tagLedgerStarted) {
|
|
20476
|
+
const decision = await reconcileLedgerAfterFailedTagPush(deps, ledger, ledgerAnchors);
|
|
20477
|
+
const annotated = new Error(`${e instanceof Error ? e.message : String(e)}
|
|
20478
|
+
|
|
20479
|
+
${decision.note}`);
|
|
20480
|
+
if (decision.action === "kept-present") throw partialTrainRecoveryError(annotated, recoveryInput);
|
|
20481
|
+
if (decision.action === "kept-unproven") throw annotated;
|
|
20482
|
+
throw await recoverFailedFold(deps, annotated, startBranch, preFold.mainSha, resumeCommand);
|
|
20483
|
+
}
|
|
19354
20484
|
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
19355
20485
|
}
|
|
19356
20486
|
const tagPushSince = (deps.now ?? Date.now)();
|
|
19357
20487
|
let checks;
|
|
19358
|
-
const recoveryInput = { repo: ctx.repo, tag, stage: "main", deployModel, meta, branchHints };
|
|
19359
20488
|
try {
|
|
19360
20489
|
checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks, tagPush.pushed ? tagPushSince : void 0);
|
|
19361
20490
|
} catch (e) {
|
|
19362
|
-
|
|
20491
|
+
const failureText = e instanceof Error ? e.message : String(e);
|
|
20492
|
+
const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, ledgerAnchors, "ordinaryCi", {
|
|
20493
|
+
repo: ctx.repo,
|
|
20494
|
+
sha: releaseSha,
|
|
20495
|
+
failure: failureText,
|
|
20496
|
+
watched: false
|
|
20497
|
+
});
|
|
20498
|
+
if (recovery.reran && recovery.recovered) {
|
|
20499
|
+
try {
|
|
20500
|
+
checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks, void 0);
|
|
20501
|
+
} catch (e2) {
|
|
20502
|
+
const spent = await recoverPhaseWithMaybeTransientRerun(deps, ledger, ledgerAnchors, "ordinaryCi", {
|
|
20503
|
+
repo: ctx.repo,
|
|
20504
|
+
sha: releaseSha,
|
|
20505
|
+
failure: e2 instanceof Error ? e2.message : String(e2),
|
|
20506
|
+
watched: false
|
|
20507
|
+
});
|
|
20508
|
+
throw partialTrainRecoveryError(new Error(`${e2 instanceof Error ? e2.message : String(e2)}
|
|
20509
|
+
|
|
20510
|
+
${spent.note}`), recoveryInput);
|
|
20511
|
+
}
|
|
20512
|
+
} else {
|
|
20513
|
+
throw partialTrainRecoveryError(new Error(`${failureText}
|
|
20514
|
+
|
|
20515
|
+
${recovery.note}`), recoveryInput);
|
|
20516
|
+
}
|
|
19363
20517
|
}
|
|
20518
|
+
await recordPhase(ledger, deps, ledgerAnchors, "ordinaryCi", phaseEntry({ state: "complete", sha: releaseSha, note: checks }), {
|
|
20519
|
+
landed: "the immutable tag pushed at " + releaseSha.slice(0, 12)
|
|
20520
|
+
});
|
|
19364
20521
|
if (trueMergeGateNote) checks = `${trueMergeGateNote}; ${checks}`;
|
|
19365
20522
|
try {
|
|
19366
20523
|
await runGitPush(deps, ["push", "origin", "main"]);
|
|
19367
20524
|
} catch (e) {
|
|
20525
|
+
await recordPhase(ledger, deps, ledgerAnchors, "promotion", phaseEntry({ state: "failed", sha: releaseSha, error: `origin/main push failed after ${tag} was pushed: ${e instanceof Error ? e.message : String(e)}` }), { landed: "the immutable tag pushed at " + releaseSha.slice(0, 12) });
|
|
19368
20526
|
throw partialTrainRecoveryError(e, recoveryInput);
|
|
19369
20527
|
}
|
|
20528
|
+
await recordPhase(ledger, deps, ledgerAnchors, "promotion", phaseEntry({ state: "complete", sha: releaseSha, note: `immutable ${tag} pushed at ${releaseSha.slice(0, 12)}; origin/main fast-forwarded to it` }), { landed: "the immutable tag and the green required-check wall" });
|
|
19370
20529
|
const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
19371
20530
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
|
|
20531
|
+
await recordPhase(ledger, deps, ledgerAnchors, "githubRelease", phaseEntry({ state: "complete", sha: releaseSha, runUrl: releaseUrl, note: `verified GitHub Release ${tag} against ${releaseSha.slice(0, 12)}` }), { landed: "the immutable tag, the green required-check wall, and the origin/main fast-forward" });
|
|
19372
20532
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
19373
20533
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
19374
|
-
const
|
|
20534
|
+
const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report", meta.publishDir);
|
|
20535
|
+
let deployDispatch = deployDispatch0;
|
|
20536
|
+
let deployRecoveryNote;
|
|
20537
|
+
if (deployDispatch0.deployStatus === "failure" && ledger) {
|
|
20538
|
+
const deployRunRepo = releaseRunRepoFor(deployModel, ctx.repo);
|
|
20539
|
+
const failedRow = (deployDispatch0.workflowRuns ?? []).find((r) => r.runId != null && r.conclusion === "failure");
|
|
20540
|
+
const dispatchRun = deployDispatch0.runId != null ? [{ workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow", runId: deployDispatch0.runId, runUrl: deployDispatch0.runUrl, conclusion: "failure", repo: deployRunRepo, ...isCentralDispatchModel(deployModel) ? { event: "workflow_dispatch" } : {} }] : [];
|
|
20541
|
+
const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, ledgerAnchors, failedRow?.workflow === "publish.yml" ? "publish" : "deploy", {
|
|
20542
|
+
repo: deployRunRepo,
|
|
20543
|
+
sha: releaseSha,
|
|
20544
|
+
failure: `watched deploy run(s) failed on the release SHA: ${deployDispatch0.note}`,
|
|
20545
|
+
runs: deployDispatch0.workflowRuns?.filter((r) => r.runId != null).map((r) => ({ ...r, repo: deployRunRepo })) ?? (deployDispatch0.runId != null ? [{ workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow", runId: deployDispatch0.runId, runUrl: deployDispatch0.runUrl, conclusion: "failure", repo: deployRunRepo, ...isCentralDispatchModel(deployModel) ? { event: "workflow_dispatch" } : {} }] : []),
|
|
20546
|
+
watched: isCentralDispatchModel(deployModel)
|
|
20547
|
+
});
|
|
20548
|
+
if (recovery.reran && recovery.recovered) {
|
|
20549
|
+
deployDispatch = {
|
|
20550
|
+
...deployDispatch0,
|
|
20551
|
+
deployStatus: "success",
|
|
20552
|
+
workflowRuns: deployDispatch0.workflowRuns?.map((r) => recovery.runId != null && r.runId === recovery.runId ? { ...r, conclusion: "success" } : r),
|
|
20553
|
+
note: `${deployDispatch0.note}; ${recovery.note}`
|
|
20554
|
+
};
|
|
20555
|
+
} else if (recovery.note) {
|
|
20556
|
+
deployRecoveryNote = recovery.note;
|
|
20557
|
+
}
|
|
20558
|
+
}
|
|
20559
|
+
await recordPhase(ledger, deps, ledgerAnchors, "deploy", phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha })), {
|
|
20560
|
+
landed: "the immutable tag, the green required-check wall, the origin/main fast-forward, and the verified GitHub Release"
|
|
20561
|
+
});
|
|
19375
20562
|
const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
|
|
19376
|
-
let
|
|
19377
|
-
dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, dispatch);
|
|
20563
|
+
let publishSkipNote;
|
|
19378
20564
|
if (!publishDispatch && deployDispatch.deployStatus !== "success" && meta.publishRequired && (deployModel === "tenant-container" || deployModel === "solo-container")) {
|
|
19379
|
-
|
|
19380
|
-
dispatch = {
|
|
19381
|
-
...dispatch,
|
|
19382
|
-
note: `${dispatch.note}; tenant-publish.yml skipped (${reason})`
|
|
19383
|
-
};
|
|
20565
|
+
publishSkipNote = deployDispatch.deployStatus === "failure" ? "box deploy failed - redeploy the box before publishing" : "box deploy not confirmed (run with --watch) - publish after the box deploy lands";
|
|
19384
20566
|
}
|
|
19385
|
-
|
|
20567
|
+
await recordPhase(ledger, deps, ledgerAnchors, "publish", phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, {
|
|
20568
|
+
releaseSha,
|
|
20569
|
+
repo: ctx.repo,
|
|
20570
|
+
...publishSkipNote !== void 0 ? { skipPublishNote: publishSkipNote } : {}
|
|
20571
|
+
})), {
|
|
20572
|
+
landed: "the immutable tag, the green required-check wall, origin/main, the verified GitHub Release, and the dispatched deploy path"
|
|
20573
|
+
});
|
|
20574
|
+
let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
|
|
20575
|
+
dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, dispatch);
|
|
20576
|
+
if (publishSkipNote) dispatch = { ...dispatch, note: `${dispatch.note}; tenant-publish.yml skipped (${publishSkipNote})` };
|
|
20577
|
+
if (deployRecoveryNote) dispatch = { ...dispatch, note: `${dispatch.note}; ${deployRecoveryNote}` };
|
|
20578
|
+
return { checks, releaseUrl, announceNote, dispatch, ledgerAnchors };
|
|
19386
20579
|
}
|
|
19387
20580
|
function trainResumeExitCode(state) {
|
|
19388
20581
|
if (state === "failed") return 1;
|
|
@@ -19492,6 +20685,35 @@ async function runReleaseResumePipeline(deps, options, prepared) {
|
|
|
19492
20685
|
);
|
|
19493
20686
|
}
|
|
19494
20687
|
const base = { command: "release-resume", repo: ctx.repo, tag, tagSha, deployModel };
|
|
20688
|
+
const ledger = openReleaseLedgerWriter(process.cwd());
|
|
20689
|
+
const anchors = { repo: ctx.repo, tag, tagSha };
|
|
20690
|
+
const begin = await beginReleaseLedger(deps, process.cwd(), tag);
|
|
20691
|
+
if (!begin.ok) throw new Error(`release --resume refused: ${begin.error}`);
|
|
20692
|
+
else if (begin.note) deps.warn?.(`release ledger: ${begin.note}`);
|
|
20693
|
+
let persisted;
|
|
20694
|
+
try {
|
|
20695
|
+
persisted = await ledger.load(anchors);
|
|
20696
|
+
} catch (e) {
|
|
20697
|
+
throw new Error(
|
|
20698
|
+
`release --resume: ${e instanceof Error ? e.message : String(e)}
|
|
20699
|
+
Live origin/tag/Release proof stays authoritative; nothing was written. Inspect ${ledger.path} before retrying.`
|
|
20700
|
+
);
|
|
20701
|
+
}
|
|
20702
|
+
if (persisted) {
|
|
20703
|
+
let reverifyNotes = [];
|
|
20704
|
+
try {
|
|
20705
|
+
const reverified = await reverifyPersistedRunPhases(deps, ledger.path, anchors, { sha: tagSha, repo: ctx.repo });
|
|
20706
|
+
persisted = reverified.ledger;
|
|
20707
|
+
reverifyNotes = reverified.notes;
|
|
20708
|
+
} catch (e) {
|
|
20709
|
+
throw new Error(
|
|
20710
|
+
`release --resume: ${e instanceof Error ? e.message : String(e)}
|
|
20711
|
+
Nothing was written. Inspect ${ledger.path} (or clear it only after proving the release state by hand).`
|
|
20712
|
+
);
|
|
20713
|
+
}
|
|
20714
|
+
for (const note of reverifyNotes) deps.warn?.(`release ledger: ${note}`);
|
|
20715
|
+
}
|
|
20716
|
+
const ledgerMode = persisted ? "strict" : "migration";
|
|
19495
20717
|
const partialRelease = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
19496
20718
|
if (!partialRelease) {
|
|
19497
20719
|
try {
|
|
@@ -19506,12 +20728,44 @@ async function runReleaseResumePipeline(deps, options, prepared) {
|
|
|
19506
20728
|
steps2.push(`development alignment: ${devRollForward2.status}`);
|
|
19507
20729
|
const rcAlignment2 = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
|
|
19508
20730
|
if (rcAlignment2) steps2.push(`rc alignment: ${rcAlignment2.status}`);
|
|
19509
|
-
const
|
|
20731
|
+
const historicalTargets = prodReleaseTargets(deployModel);
|
|
20732
|
+
const historicalRepo = releaseRunRepoFor(deployModel, ctx.repo);
|
|
20733
|
+
let historicalRows = historicalTargets.length ? await listOwnWorkflowRuns(deps, historicalRepo, historicalTargets, tagSha) : [];
|
|
20734
|
+
let historicalDeploy = historicalTargets.length ? aggregateWorkflowRuns(historicalRows) : "pending";
|
|
20735
|
+
let historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}`;
|
|
20736
|
+
if (historicalDeploy === "failure" && ledger) {
|
|
20737
|
+
const failedRow = historicalRows.find((r) => r.runId != null && r.conclusion === "failure");
|
|
20738
|
+
const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, failedRow?.workflow === "publish.yml" ? "publish" : "deploy", {
|
|
20739
|
+
repo: historicalRepo,
|
|
20740
|
+
sha: tagSha,
|
|
20741
|
+
failure: `historical deploy/publish run(s) failed on the release SHA: ${historicalRows.map((r) => `${r.workflow}=${r.conclusion}`).join(", ")}`,
|
|
20742
|
+
runs: historicalRows.filter((r) => r.runId != null).map((r) => ({ ...r, repo: historicalRepo })),
|
|
20743
|
+
watched: false
|
|
20744
|
+
});
|
|
20745
|
+
if (recovery.reran && recovery.recovered) {
|
|
20746
|
+
historicalRows = historicalRows.map((r) => recovery.runId != null && r.runId === recovery.runId ? { ...r, conclusion: "success" } : r);
|
|
20747
|
+
historicalDeploy = aggregateWorkflowRuns(historicalRows);
|
|
20748
|
+
historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}; ${recovery.note}`;
|
|
20749
|
+
} else if (recovery.note) {
|
|
20750
|
+
historicalNote = `${historicalNote}; ${recovery.note}`;
|
|
20751
|
+
}
|
|
20752
|
+
}
|
|
20753
|
+
if (persisted) {
|
|
20754
|
+
const phaseInputs = phaseInputsFromRunRows(deployModel, historicalRows, tagSha, { repo: ctx.repo });
|
|
20755
|
+
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(phaseInputs.deploy), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
|
|
20756
|
+
await recordPhase(ledger, deps, anchors, "publish", phaseEntry(phaseInputs.publish), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
|
|
20757
|
+
await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: the GitHub Release exists at the immutable tag SHA" }), { strict: true, landed: "the promotion (verified live)" });
|
|
20758
|
+
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: origin/main contains the immutable tag SHA" }), { strict: true, landed: "nothing beyond the already-public release" });
|
|
20759
|
+
}
|
|
19510
20760
|
const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
|
|
19511
|
-
note:
|
|
20761
|
+
note: historicalNote,
|
|
19512
20762
|
deployStatus: historicalDeploy
|
|
19513
20763
|
});
|
|
19514
20764
|
if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
|
|
20765
|
+
if (persisted) {
|
|
20766
|
+
await recordPhase(ledger, deps, anchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2, rcAlignment2)), { strict: true, landed: "the released, verified, deploy-resolved release (alignment PRs reused/landed)" });
|
|
20767
|
+
}
|
|
20768
|
+
const ledgerReport2 = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
19515
20769
|
const state2 = resumeStateOf(deriveTrainFollowUpStatus({
|
|
19516
20770
|
projectInfo: "ok",
|
|
19517
20771
|
deploy: deployFollowUpOutcome(dispatch2.deployStatus),
|
|
@@ -19528,6 +20782,7 @@ async function runReleaseResumePipeline(deps, options, prepared) {
|
|
|
19528
20782
|
devRollForward: devRollForward2,
|
|
19529
20783
|
rcAlignment: rcAlignment2,
|
|
19530
20784
|
dispatch: dispatch2,
|
|
20785
|
+
...ledgerReport2 ? { ledger: ledgerReport2 } : {},
|
|
19531
20786
|
note: state2 === "failed" ? `release ${tag} was already published, but its deploy/publish on the release SHA FAILED \u2014 re-run the failed job (do NOT re-tag); the protected alignment PRs were reused` : state2 === "pending" ? `release ${tag} was already published; reused its protected alignment PRs, but a deploy/publish or alignment follow-up is unresolved \u2014 watch it to completion` : `release ${tag} was already published; alignment is complete and the deploy on the release SHA is proven green`
|
|
19532
20787
|
};
|
|
19533
20788
|
}
|
|
@@ -19548,21 +20803,81 @@ async function runReleaseResumePipeline(deps, options, prepared) {
|
|
|
19548
20803
|
try {
|
|
19549
20804
|
steps.push(await waitForRequiredTrainChecks(deps, ctx, tagSha, requiredChecks, void 0));
|
|
19550
20805
|
} catch (e) {
|
|
19551
|
-
|
|
20806
|
+
const failureText = e instanceof Error ? e.message : String(e);
|
|
20807
|
+
const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, "ordinaryCi", {
|
|
20808
|
+
repo: ctx.repo,
|
|
20809
|
+
sha: tagSha,
|
|
20810
|
+
failure: failureText,
|
|
20811
|
+
watched: false
|
|
20812
|
+
});
|
|
20813
|
+
if (recovery.reran && recovery.recovered) {
|
|
20814
|
+
try {
|
|
20815
|
+
steps.push(await waitForRequiredTrainChecks(deps, ctx, tagSha, requiredChecks, void 0));
|
|
20816
|
+
} catch (e2) {
|
|
20817
|
+
const spent = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, "ordinaryCi", {
|
|
20818
|
+
repo: ctx.repo,
|
|
20819
|
+
sha: tagSha,
|
|
20820
|
+
failure: e2 instanceof Error ? e2.message : String(e2),
|
|
20821
|
+
watched: false
|
|
20822
|
+
});
|
|
20823
|
+
throw partialTrainRecoveryError(new Error(`${e2 instanceof Error ? e2.message : String(e2)}
|
|
20824
|
+
|
|
20825
|
+
${spent.note}`), recoveryInput);
|
|
20826
|
+
}
|
|
20827
|
+
} else {
|
|
20828
|
+
throw partialTrainRecoveryError(new Error(`${failureText}
|
|
20829
|
+
|
|
20830
|
+
${recovery.note}`), recoveryInput);
|
|
20831
|
+
}
|
|
19552
20832
|
}
|
|
20833
|
+
await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict: ledgerMode === "strict", landed: "the immutable tag on origin at the resumed SHA" });
|
|
19553
20834
|
await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/main`]);
|
|
19554
20835
|
steps.push(`pushed ${tagSha.slice(0, 12)} to origin/main`);
|
|
20836
|
+
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
|
|
19555
20837
|
const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
19556
20838
|
steps.push(`created the GitHub Release for ${tag}`);
|
|
19557
20839
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
|
|
19558
20840
|
steps.push("verified the Release published against the tagged commit");
|
|
20841
|
+
await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, runUrl: releaseUrl, note: "verified the Release published against the tagged commit" }), { strict: ledgerMode === "strict", landed: "the immutable tag, the re-proven wall, and the origin/main push" });
|
|
19559
20842
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
19560
20843
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
19561
|
-
const
|
|
20844
|
+
const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, tagSha, "report", meta.publishDir);
|
|
20845
|
+
let deployDispatch = deployDispatch0;
|
|
20846
|
+
let deployRecoveryNote;
|
|
20847
|
+
if (deployDispatch0.deployStatus === "failure" && ledger) {
|
|
20848
|
+
const deployRunRepo = releaseRunRepoFor(deployModel, ctx.repo);
|
|
20849
|
+
const failedRow = (deployDispatch0.workflowRuns ?? []).find((r) => r.runId != null && r.conclusion === "failure");
|
|
20850
|
+
const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, failedRow?.workflow === "publish.yml" ? "publish" : "deploy", {
|
|
20851
|
+
repo: deployRunRepo,
|
|
20852
|
+
sha: tagSha,
|
|
20853
|
+
failure: `watched deploy run(s) failed on the release SHA: ${deployDispatch0.note}`,
|
|
20854
|
+
runs: deployDispatch0.workflowRuns?.filter((r) => r.runId != null).map((r) => ({ ...r, repo: deployRunRepo })) ?? (deployDispatch0.runId != null ? [{ workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow", runId: deployDispatch0.runId, runUrl: deployDispatch0.runUrl, conclusion: "failure", repo: deployRunRepo, ...isCentralDispatchModel(deployModel) ? { event: "workflow_dispatch" } : {} }] : []),
|
|
20855
|
+
watched: isCentralDispatchModel(deployModel)
|
|
20856
|
+
});
|
|
20857
|
+
if (recovery.reran && recovery.recovered) {
|
|
20858
|
+
deployDispatch = {
|
|
20859
|
+
...deployDispatch0,
|
|
20860
|
+
deployStatus: "success",
|
|
20861
|
+
workflowRuns: deployDispatch0.workflowRuns?.map((r) => recovery.runId != null && r.runId === recovery.runId ? { ...r, conclusion: "success" } : r),
|
|
20862
|
+
note: `${deployDispatch0.note}; ${recovery.note}`
|
|
20863
|
+
};
|
|
20864
|
+
} else if (recovery.note) {
|
|
20865
|
+
deployRecoveryNote = recovery.note;
|
|
20866
|
+
}
|
|
20867
|
+
}
|
|
20868
|
+
steps.push(`dispatched the ${deployModel} deploy path`);
|
|
20869
|
+
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha: tagSha })), {
|
|
20870
|
+
strict: ledgerMode === "strict",
|
|
20871
|
+
landed: "the immutable tag, the re-proven wall, the origin/main push, and the verified GitHub Release"
|
|
20872
|
+
});
|
|
19562
20873
|
const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
|
|
20874
|
+
await recordPhase(ledger, deps, anchors, "publish", phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, { releaseSha: tagSha, repo: ctx.repo })), {
|
|
20875
|
+
strict: ledgerMode === "strict",
|
|
20876
|
+
landed: "the immutable tag, the re-proven wall, origin/main, the verified GitHub Release, and the dispatched deploy path"
|
|
20877
|
+
});
|
|
19563
20878
|
let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
|
|
19564
20879
|
dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, dispatch);
|
|
19565
|
-
|
|
20880
|
+
if (deployRecoveryNote) dispatch = { ...dispatch, note: `${dispatch.note}; ${deployRecoveryNote}` };
|
|
19566
20881
|
const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
|
|
19567
20882
|
steps.push(`development roll-forward: ${devRollForward.status}`);
|
|
19568
20883
|
const rcAlignment = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
|
|
@@ -19575,6 +20890,8 @@ async function runReleaseResumePipeline(deps, options, prepared) {
|
|
|
19575
20890
|
foldPort: "ok"
|
|
19576
20891
|
});
|
|
19577
20892
|
const state = resumeStateOf(followUp);
|
|
20893
|
+
await recordPhase(ledger, deps, anchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward, rcAlignment)), { strict: ledgerMode === "strict", landed: "the promotion, verified Release, and deploy path of the resumed release" });
|
|
20894
|
+
const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
19578
20895
|
return {
|
|
19579
20896
|
...base,
|
|
19580
20897
|
mode: "partial-release",
|
|
@@ -19586,6 +20903,7 @@ async function runReleaseResumePipeline(deps, options, prepared) {
|
|
|
19586
20903
|
dispatch,
|
|
19587
20904
|
devRollForward,
|
|
19588
20905
|
rcAlignment,
|
|
20906
|
+
...ledgerReport ? { ledger: ledgerReport } : {},
|
|
19589
20907
|
note: followUp === "failed" ? `resumed ${tag} (version preserved, not re-cut) \u2014 but a watched deploy/publish run FAILED; the Release stands, re-run the failed job (do NOT re-tag)` : followUp === "pending" ? `resumed ${tag} (version preserved, not re-cut) \u2014 a deploy/publish or alignment follow-up is unresolved; watch it to completion` : `resumed and completed ${tag} \u2014 the original version was preserved, not re-cut`
|
|
19590
20908
|
};
|
|
19591
20909
|
}
|
|
@@ -19679,13 +20997,14 @@ async function runReleaseAbort(deps, options = {}) {
|
|
|
19679
20997
|
if (restoredMainSha !== originMainSha) {
|
|
19680
20998
|
throw new Error(`release --abort removed ${tag}, but local main did not restore to origin/main; inspect before retrying`);
|
|
19681
20999
|
}
|
|
21000
|
+
const clearedLedgerNote = await clearReleaseLedger(process.cwd(), { repo: ctx.repo, tag, tagSha });
|
|
19682
21001
|
return {
|
|
19683
21002
|
command: "release-abort",
|
|
19684
21003
|
repo: ctx.repo,
|
|
19685
21004
|
tag,
|
|
19686
21005
|
tagSha,
|
|
19687
21006
|
restoredMainSha,
|
|
19688
|
-
note: `removed the unpublished ${tag} candidate and restored local main; repair development before recutting`
|
|
21007
|
+
note: `removed the unpublished ${tag} candidate and restored local main; repair development before recutting` + (clearedLedgerNote ? `; ${clearedLedgerNote}` : "")
|
|
19689
21008
|
};
|
|
19690
21009
|
}
|
|
19691
21010
|
async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
@@ -19732,6 +21051,25 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
|
19732
21051
|
if (options.watch && await watchTenantRun(deps, runId, ctx.repo) !== "success") {
|
|
19733
21052
|
throw new Error(`publish run ${runId} did not finish successfully after retry`);
|
|
19734
21053
|
}
|
|
21054
|
+
const ledger = openReleaseLedgerWriter(process.cwd());
|
|
21055
|
+
const existing = await ledger.load({ repo: ctx.repo, tag, tagSha }).catch(() => void 0);
|
|
21056
|
+
let ledgerNote;
|
|
21057
|
+
if (existing) {
|
|
21058
|
+
const record = await ledger.record({ repo: ctx.repo, tag, tagSha }, "publish", phaseEntry({
|
|
21059
|
+
state: options.watch ? "complete" : "pending",
|
|
21060
|
+
sha: tagSha,
|
|
21061
|
+
runId,
|
|
21062
|
+
runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
|
|
21063
|
+
runRepo: ctx.repo,
|
|
21064
|
+
runProvenance: "sha",
|
|
21065
|
+
workflow: "publish.yml",
|
|
21066
|
+
event: "release",
|
|
21067
|
+
note: options.watch ? `publish run ${runId} retried via release --retry-publish and passed (live-proven)` : `publish run ${runId} queued for retry via release --retry-publish`
|
|
21068
|
+
}));
|
|
21069
|
+
ledgerNote = record.ok ? options.watch ? "publish boundary updated to complete in the release ledger" : "publish boundary updated to pending in the release ledger" : `release ledger not updated: ${record.error}`;
|
|
21070
|
+
} else {
|
|
21071
|
+
ledgerNote = "no release ledger on disk for this release \u2014 nothing to update";
|
|
21072
|
+
}
|
|
19735
21073
|
return {
|
|
19736
21074
|
command: "release-retry-publish",
|
|
19737
21075
|
repo: ctx.repo,
|
|
@@ -19740,12 +21078,18 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
|
19740
21078
|
runId,
|
|
19741
21079
|
runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
|
|
19742
21080
|
status: options.watch ? "success" : "pending",
|
|
19743
|
-
note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} queued for retry`
|
|
21081
|
+
note: options.watch ? `publish run ${runId} retried and passed; ${ledgerNote}` : `publish run ${runId} queued for retry; ${ledgerNote}`
|
|
19744
21082
|
};
|
|
19745
21083
|
}
|
|
19746
21084
|
async function runTrainApplyPipeline(mode, input) {
|
|
19747
21085
|
const { deps, ctx, command, meta, branchHints, watch, options, bumpIntent } = input;
|
|
19748
21086
|
const directTrack = input.directTrack ?? false;
|
|
21087
|
+
const ledger = mode === "rcand" ? void 0 : openReleaseLedgerWriter(process.cwd());
|
|
21088
|
+
if (ledger) {
|
|
21089
|
+
const begin = await beginReleaseLedger(deps, process.cwd());
|
|
21090
|
+
if (!begin.ok) throw new Error(`release refused before any mutation: ${begin.error}`);
|
|
21091
|
+
else if (begin.note) deps.warn?.(`release ledger: ${begin.note}`);
|
|
21092
|
+
}
|
|
19749
21093
|
if (mode === "rcand") {
|
|
19750
21094
|
await requireBranch(deps, "development");
|
|
19751
21095
|
if (directTrack) {
|
|
@@ -19832,8 +21176,10 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
19832
21176
|
const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
19833
21177
|
return { versionFold: versionFold3, releaseSha: releaseSha3 };
|
|
19834
21178
|
});
|
|
19835
|
-
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2, branchHints);
|
|
21179
|
+
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2, ledgerAnchors: ledgerAnchors2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2, branchHints, ledger);
|
|
19836
21180
|
const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
|
|
21181
|
+
await recordPhase(ledger, deps, ledgerAnchors2, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
|
|
21182
|
+
const ledgerReport2 = releaseLedgerReport(await ledger?.load(ledgerAnchors2).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
19837
21183
|
if (directTrack) {
|
|
19838
21184
|
return {
|
|
19839
21185
|
...ctx,
|
|
@@ -19856,11 +21202,14 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
19856
21202
|
devRollForward: devRollForward2,
|
|
19857
21203
|
announceNote: announceNote2,
|
|
19858
21204
|
devNote: options.dev ? "--dev is a no-op on a direct-track repo \u2014 it already releases development -> main" : void 0,
|
|
19859
|
-
release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 }
|
|
21205
|
+
release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
|
|
21206
|
+
...ledgerReport2 ? { ledger: ledgerReport2 } : {}
|
|
19860
21207
|
};
|
|
19861
21208
|
}
|
|
19862
21209
|
const retirement2 = hasRcBranch ? await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease) : { status: "not-applicable", note: "no origin/rc branch \u2014 rc runtime retirement skipped" };
|
|
19863
21210
|
const rcAlignment2 = hasRcBranch ? await alignRcForward(deps, ctx, tag2) : void 0;
|
|
21211
|
+
await recordPhase(ledger, deps, ledgerAnchors2, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2, rcAlignment2)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
|
|
21212
|
+
const fullLedgerReport = releaseLedgerReport(await ledger?.load(ledgerAnchors2).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
19864
21213
|
const environments2 = await buildEnvironments(deps, ctx, deployModel2, d2.deployStatus, retirement2);
|
|
19865
21214
|
return {
|
|
19866
21215
|
...ctx,
|
|
@@ -19886,7 +21235,8 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
19886
21235
|
announceNote: announceNote2,
|
|
19887
21236
|
devNote: "released development -> main (--dev), skipping the rc candidate",
|
|
19888
21237
|
release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
|
|
19889
|
-
environments: environments2
|
|
21238
|
+
environments: environments2,
|
|
21239
|
+
...fullLedgerReport ? { ledger: fullLedgerReport } : {}
|
|
19890
21240
|
};
|
|
19891
21241
|
}
|
|
19892
21242
|
await requireBranch(deps, "rc");
|
|
@@ -19919,10 +21269,12 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
19919
21269
|
const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
19920
21270
|
return { versionFold: versionFold2, releaseSha: releaseSha2 };
|
|
19921
21271
|
});
|
|
19922
|
-
const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe, branchHints);
|
|
21272
|
+
const { checks, releaseUrl, announceNote, dispatch: d, ledgerAnchors } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe, branchHints, ledger);
|
|
19923
21273
|
const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
|
|
19924
21274
|
const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
|
|
19925
21275
|
const rcAlignment = await alignRcForward(deps, ctx, tag);
|
|
21276
|
+
await recordPhase(ledger, deps, ledgerAnchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward, rcAlignment)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
|
|
21277
|
+
const ledgerReport = releaseLedgerReport(await ledger?.load(ledgerAnchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
19926
21278
|
const environments = await buildEnvironments(deps, ctx, deployModel, d.deployStatus, retirement);
|
|
19927
21279
|
return {
|
|
19928
21280
|
...ctx,
|
|
@@ -19947,7 +21299,8 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
19947
21299
|
devRollForward,
|
|
19948
21300
|
announceNote,
|
|
19949
21301
|
release: { tag, url: releaseUrl, targetSha: releaseSha },
|
|
19950
|
-
environments
|
|
21302
|
+
environments,
|
|
21303
|
+
...ledgerReport ? { ledger: ledgerReport } : {}
|
|
19951
21304
|
};
|
|
19952
21305
|
}
|
|
19953
21306
|
async function latestLocalReleaseTag(deps) {
|
|
@@ -21015,20 +22368,20 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
21015
22368
|
}
|
|
21016
22369
|
|
|
21017
22370
|
// src/claude-binary-doctor.ts
|
|
21018
|
-
var
|
|
22371
|
+
var import_node_fs22 = require("node:fs");
|
|
21019
22372
|
var import_node_os11 = require("node:os");
|
|
21020
|
-
var
|
|
22373
|
+
var import_node_path21 = require("node:path");
|
|
21021
22374
|
|
|
21022
22375
|
// src/jerv-cli-spawn.ts
|
|
21023
|
-
var
|
|
22376
|
+
var import_node_fs21 = require("node:fs");
|
|
21024
22377
|
var import_node_os10 = require("node:os");
|
|
21025
|
-
var
|
|
22378
|
+
var import_node_path20 = require("node:path");
|
|
21026
22379
|
var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
|
|
21027
22380
|
var POSIX_NAMES = ["jerv-cli"];
|
|
21028
|
-
var JERV_CLI_ENTRY = (0,
|
|
22381
|
+
var JERV_CLI_ENTRY = (0, import_node_path20.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
|
|
21029
22382
|
function pathEnvEntries(pathEnv, platform2 = process.platform) {
|
|
21030
22383
|
if (platform2 !== "win32") {
|
|
21031
|
-
return pathEnv.split(
|
|
22384
|
+
return pathEnv.split(import_node_path20.delimiter).map((e) => e.trim()).filter(Boolean);
|
|
21032
22385
|
}
|
|
21033
22386
|
if (pathEnv.includes(";")) {
|
|
21034
22387
|
return pathEnv.split(";").map((e) => e.trim()).filter(Boolean);
|
|
@@ -21061,10 +22414,10 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.hom
|
|
|
21061
22414
|
push(normalizeSpawnPathEntry(entry, platform2));
|
|
21062
22415
|
}
|
|
21063
22416
|
if (platform2 === "win32") {
|
|
21064
|
-
if (env.APPDATA) push((0,
|
|
21065
|
-
if (env.LOCALAPPDATA) push((0,
|
|
22417
|
+
if (env.APPDATA) push((0, import_node_path20.join)(env.APPDATA, "npm"));
|
|
22418
|
+
if (env.LOCALAPPDATA) push((0, import_node_path20.join)(env.LOCALAPPDATA, "npm"));
|
|
21066
22419
|
} else {
|
|
21067
|
-
push((0,
|
|
22420
|
+
push((0, import_node_path20.join)(home, ".local", "bin"));
|
|
21068
22421
|
}
|
|
21069
22422
|
return out;
|
|
21070
22423
|
}
|
|
@@ -21072,23 +22425,23 @@ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.ho
|
|
|
21072
22425
|
const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
|
|
21073
22426
|
const out = [];
|
|
21074
22427
|
for (const dir of jervCliCandidateDirs(env, home, platform2)) {
|
|
21075
|
-
for (const name of names) out.push((0,
|
|
22428
|
+
for (const name of names) out.push((0, import_node_path20.join)(dir, name));
|
|
21076
22429
|
}
|
|
21077
22430
|
return out;
|
|
21078
22431
|
}
|
|
21079
|
-
function resolveJervCliPath(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform, exists =
|
|
22432
|
+
function resolveJervCliPath(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform, exists = import_node_fs21.existsSync) {
|
|
21080
22433
|
for (const candidate of jervCliCandidatePaths(env, home, platform2)) {
|
|
21081
22434
|
if (exists(candidate)) return candidate;
|
|
21082
22435
|
}
|
|
21083
22436
|
return void 0;
|
|
21084
22437
|
}
|
|
21085
|
-
function resolveJervCliNodeEntry(shimPath, exists =
|
|
21086
|
-
const entry = (0,
|
|
22438
|
+
function resolveJervCliNodeEntry(shimPath, exists = import_node_fs21.existsSync) {
|
|
22439
|
+
const entry = (0, import_node_path20.join)((0, import_node_path20.dirname)(shimPath), JERV_CLI_ENTRY);
|
|
21087
22440
|
return exists(entry) ? entry : void 0;
|
|
21088
22441
|
}
|
|
21089
22442
|
function jervCliExecFileArgs(args, opts = {}) {
|
|
21090
22443
|
const platform2 = opts.platform ?? process.platform;
|
|
21091
|
-
const exists = opts.exists ??
|
|
22444
|
+
const exists = opts.exists ?? import_node_fs21.existsSync;
|
|
21092
22445
|
const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os10.homedir)(), platform2, exists);
|
|
21093
22446
|
if (resolved) {
|
|
21094
22447
|
const entry = resolveJervCliNodeEntry(resolved, exists);
|
|
@@ -21178,26 +22531,26 @@ function globalNodeModulesRoots(host) {
|
|
|
21178
22531
|
out.push(dir);
|
|
21179
22532
|
};
|
|
21180
22533
|
const prefix = env.npm_config_prefix?.trim();
|
|
21181
|
-
if (prefix) push(platform2 === "win32" ? (0,
|
|
22534
|
+
if (prefix) push(platform2 === "win32" ? (0, import_node_path21.join)(prefix, "node_modules") : (0, import_node_path21.join)(prefix, "lib", "node_modules"));
|
|
21182
22535
|
for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os11.homedir)(), platform2)) {
|
|
21183
|
-
push((0,
|
|
21184
|
-
push((0,
|
|
22536
|
+
push((0, import_node_path21.join)(dir, "node_modules"));
|
|
22537
|
+
push((0, import_node_path21.join)((0, import_node_path21.dirname)(dir), "lib", "node_modules"));
|
|
21185
22538
|
}
|
|
21186
22539
|
return out;
|
|
21187
22540
|
}
|
|
21188
22541
|
function readHead(path2) {
|
|
21189
22542
|
let fd;
|
|
21190
22543
|
try {
|
|
21191
|
-
fd = (0,
|
|
22544
|
+
fd = (0, import_node_fs22.openSync)(path2, "r");
|
|
21192
22545
|
const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
|
|
21193
|
-
const read = (0,
|
|
22546
|
+
const read = (0, import_node_fs22.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
|
|
21194
22547
|
return buffer.subarray(0, read);
|
|
21195
22548
|
} catch {
|
|
21196
22549
|
return void 0;
|
|
21197
22550
|
} finally {
|
|
21198
22551
|
if (fd !== void 0) {
|
|
21199
22552
|
try {
|
|
21200
|
-
(0,
|
|
22553
|
+
(0, import_node_fs22.closeSync)(fd);
|
|
21201
22554
|
} catch {
|
|
21202
22555
|
}
|
|
21203
22556
|
}
|
|
@@ -21205,7 +22558,7 @@ function readHead(path2) {
|
|
|
21205
22558
|
}
|
|
21206
22559
|
function fileBytes(path2) {
|
|
21207
22560
|
try {
|
|
21208
|
-
return (0,
|
|
22561
|
+
return (0, import_node_fs22.statSync)(path2).size;
|
|
21209
22562
|
} catch {
|
|
21210
22563
|
return void 0;
|
|
21211
22564
|
}
|
|
@@ -21219,17 +22572,17 @@ function readClaudeBinaryState(host = {}) {
|
|
|
21219
22572
|
const arch = host.arch ?? process.arch;
|
|
21220
22573
|
const magic = EXECUTABLE_MAGIC[platform2];
|
|
21221
22574
|
if (!magic) return void 0;
|
|
21222
|
-
const packageRoot = globalNodeModulesRoots(host).map((root) => (0,
|
|
22575
|
+
const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path21.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs22.existsSync)((0, import_node_path21.join)(dir, "package.json")));
|
|
21223
22576
|
if (!packageRoot) return void 0;
|
|
21224
22577
|
const keys = platformPackageKeys(platform2, arch);
|
|
21225
22578
|
const fallbackPackage = `${PACKAGE}-${keys[0]}`;
|
|
21226
22579
|
let manifest;
|
|
21227
22580
|
try {
|
|
21228
|
-
manifest = JSON.parse((0,
|
|
22581
|
+
manifest = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path21.join)(packageRoot, "package.json"), "utf8"));
|
|
21229
22582
|
} catch (e) {
|
|
21230
22583
|
return {
|
|
21231
22584
|
state: "unreadable",
|
|
21232
|
-
binPath: (0,
|
|
22585
|
+
binPath: (0, import_node_path21.join)(packageRoot, "package.json"),
|
|
21233
22586
|
expectedMagic: magic.name,
|
|
21234
22587
|
platformPackage: fallbackPackage,
|
|
21235
22588
|
error: `package.json could not be read \u2014 ${e.message}`
|
|
@@ -21246,17 +22599,17 @@ function readClaudeBinaryState(host = {}) {
|
|
|
21246
22599
|
error: "package.json declares no `bin` entry \u2014 the executed file cannot be resolved"
|
|
21247
22600
|
};
|
|
21248
22601
|
}
|
|
21249
|
-
const binPath = (0,
|
|
21250
|
-
const binName = (0,
|
|
22602
|
+
const binPath = (0, import_node_path21.join)(packageRoot, binRelative);
|
|
22603
|
+
const binName = (0, import_node_path21.basename)(binRelative);
|
|
21251
22604
|
const optional = manifest.optionalDependencies;
|
|
21252
22605
|
const publishes = (name) => typeof optional === "object" && optional !== null && Object.prototype.hasOwnProperty.call(optional, name);
|
|
21253
22606
|
const published = keys.map((key) => `${PACKAGE}-${key}`).filter(publishes);
|
|
21254
22607
|
if (published.length === 0) return void 0;
|
|
21255
22608
|
const binIn = (name) => [
|
|
21256
|
-
(0,
|
|
21257
|
-
(0,
|
|
22609
|
+
(0, import_node_path21.join)(packageRoot, "node_modules", ...name.split("/"), binName),
|
|
22610
|
+
(0, import_node_path21.join)((0, import_node_path21.dirname)((0, import_node_path21.dirname)(packageRoot)), ...name.split("/"), binName)
|
|
21258
22611
|
];
|
|
21259
|
-
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0,
|
|
22612
|
+
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs22.existsSync)(file)) })).find((c) => c.path);
|
|
21260
22613
|
const platformPackage = found?.name ?? published[0];
|
|
21261
22614
|
let source;
|
|
21262
22615
|
let sourceProblem;
|
|
@@ -21267,7 +22620,7 @@ function readClaudeBinaryState(host = {}) {
|
|
|
21267
22620
|
} else {
|
|
21268
22621
|
source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
|
|
21269
22622
|
}
|
|
21270
|
-
if (!(0,
|
|
22623
|
+
if (!(0, import_node_fs22.existsSync)(binPath)) {
|
|
21271
22624
|
return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
|
|
21272
22625
|
}
|
|
21273
22626
|
const head = readHead(binPath);
|
|
@@ -21310,9 +22663,9 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
21310
22663
|
const platform2 = host.platform ?? process.platform;
|
|
21311
22664
|
const aside = `${probe.binPath}.stub-${Date.now()}`;
|
|
21312
22665
|
let renamed = false;
|
|
21313
|
-
if ((0,
|
|
22666
|
+
if ((0, import_node_fs22.existsSync)(probe.binPath)) {
|
|
21314
22667
|
try {
|
|
21315
|
-
(0,
|
|
22668
|
+
(0, import_node_fs22.renameSync)(probe.binPath, aside);
|
|
21316
22669
|
renamed = true;
|
|
21317
22670
|
onStep?.(`renamed the stub aside: ${aside}`);
|
|
21318
22671
|
} catch (e) {
|
|
@@ -21321,12 +22674,12 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
21321
22674
|
}
|
|
21322
22675
|
try {
|
|
21323
22676
|
onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
|
|
21324
|
-
(0,
|
|
21325
|
-
if (platform2 !== "win32") (0,
|
|
22677
|
+
(0, import_node_fs22.copyFileSync)(probe.source.path, probe.binPath);
|
|
22678
|
+
if (platform2 !== "win32") (0, import_node_fs22.chmodSync)(probe.binPath, 493);
|
|
21326
22679
|
} catch (e) {
|
|
21327
22680
|
if (renamed) {
|
|
21328
22681
|
try {
|
|
21329
|
-
(0,
|
|
22682
|
+
(0, import_node_fs22.renameSync)(aside, probe.binPath);
|
|
21330
22683
|
} catch {
|
|
21331
22684
|
return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
|
|
21332
22685
|
}
|
|
@@ -21340,7 +22693,7 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
21340
22693
|
let kept = false;
|
|
21341
22694
|
if (renamed) {
|
|
21342
22695
|
try {
|
|
21343
|
-
(0,
|
|
22696
|
+
(0, import_node_fs22.rmSync)(aside);
|
|
21344
22697
|
} catch {
|
|
21345
22698
|
kept = true;
|
|
21346
22699
|
}
|
|
@@ -22192,7 +23545,7 @@ function renderPluginCachePlan(plan, applied) {
|
|
|
22192
23545
|
}
|
|
22193
23546
|
|
|
22194
23547
|
// src/app-actor.ts
|
|
22195
|
-
var
|
|
23548
|
+
var import_node_crypto6 = require("node:crypto");
|
|
22196
23549
|
var APP_ACTOR_ENV = "MMI_ACTOR";
|
|
22197
23550
|
var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
22198
23551
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
@@ -22236,7 +23589,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
|
|
|
22236
23589
|
exp: now + APP_JWT_TTL_S,
|
|
22237
23590
|
iss: appId
|
|
22238
23591
|
}));
|
|
22239
|
-
const signer = (0,
|
|
23592
|
+
const signer = (0, import_node_crypto6.createSign)("RSA-SHA256");
|
|
22240
23593
|
signer.update(`${header}.${payload}`);
|
|
22241
23594
|
return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
|
|
22242
23595
|
}
|
|
@@ -22637,9 +23990,9 @@ function registerBoardCommands(program3) {
|
|
|
22637
23990
|
}
|
|
22638
23991
|
|
|
22639
23992
|
// src/bootstrap-commands.ts
|
|
22640
|
-
var
|
|
23993
|
+
var import_node_fs24 = require("node:fs");
|
|
22641
23994
|
var import_node_os12 = require("node:os");
|
|
22642
|
-
var
|
|
23995
|
+
var import_node_path23 = require("node:path");
|
|
22643
23996
|
|
|
22644
23997
|
// src/command-plans.ts
|
|
22645
23998
|
function parseTrainTag(tag) {
|
|
@@ -22799,8 +24152,8 @@ function renderSteps(title, steps) {
|
|
|
22799
24152
|
}
|
|
22800
24153
|
|
|
22801
24154
|
// src/port-registry.ts
|
|
22802
|
-
var
|
|
22803
|
-
var
|
|
24155
|
+
var import_node_fs23 = require("node:fs");
|
|
24156
|
+
var import_node_path22 = require("node:path");
|
|
22804
24157
|
|
|
22805
24158
|
// ../infra/port-geometry.mjs
|
|
22806
24159
|
var PORT_BLOCK = 100;
|
|
@@ -22814,8 +24167,8 @@ function nextPortBlock(registry2) {
|
|
|
22814
24167
|
return [base, base + PORT_SPAN];
|
|
22815
24168
|
}
|
|
22816
24169
|
function loadPortRegistry(path2) {
|
|
22817
|
-
if (!(0,
|
|
22818
|
-
const raw = JSON.parse((0,
|
|
24170
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return {};
|
|
24171
|
+
const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
|
|
22819
24172
|
const out = {};
|
|
22820
24173
|
for (const [key, value] of Object.entries(raw)) {
|
|
22821
24174
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -22829,9 +24182,9 @@ function ensurePortRange(repo, path2) {
|
|
|
22829
24182
|
const existing = registry2[repo];
|
|
22830
24183
|
if (existing) return existing;
|
|
22831
24184
|
const range = nextPortBlock(registry2);
|
|
22832
|
-
const raw = (0,
|
|
24185
|
+
const raw = (0, import_node_fs23.existsSync)(path2) ? JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")) : {};
|
|
22833
24186
|
raw[repo] = range;
|
|
22834
|
-
(0,
|
|
24187
|
+
(0, import_node_fs23.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
22835
24188
|
return range;
|
|
22836
24189
|
}
|
|
22837
24190
|
function portCursorSeed(registry2) {
|
|
@@ -22853,22 +24206,22 @@ function existingPortRange(repo, registry2) {
|
|
|
22853
24206
|
return registry2[repo] ?? null;
|
|
22854
24207
|
}
|
|
22855
24208
|
function portRangeInfraAt(root, source) {
|
|
22856
|
-
const registryPath = (0,
|
|
22857
|
-
const ddbScriptPath = (0,
|
|
22858
|
-
if (!(0,
|
|
24209
|
+
const registryPath = (0, import_node_path22.join)(root, "infra", "port-ranges.json");
|
|
24210
|
+
const ddbScriptPath = (0, import_node_path22.join)(root, "infra", "port-ddb.mjs");
|
|
24211
|
+
if (!(0, import_node_fs23.existsSync)(registryPath) || !(0, import_node_fs23.existsSync)(ddbScriptPath)) return null;
|
|
22859
24212
|
return { root, source, registryPath, ddbScriptPath };
|
|
22860
24213
|
}
|
|
22861
24214
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
22862
24215
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
22863
24216
|
if (direct) return direct;
|
|
22864
|
-
for (let dir = cwd; ; dir = (0,
|
|
22865
|
-
const sibling = portRangeInfraAt((0,
|
|
24217
|
+
for (let dir = cwd; ; dir = (0, import_node_path22.dirname)(dir)) {
|
|
24218
|
+
const sibling = portRangeInfraAt((0, import_node_path22.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
22866
24219
|
if (sibling) return sibling;
|
|
22867
|
-
const parent = (0,
|
|
24220
|
+
const parent = (0, import_node_path22.dirname)(dir);
|
|
22868
24221
|
if (parent === dir) break;
|
|
22869
24222
|
}
|
|
22870
24223
|
if (packageDir) {
|
|
22871
|
-
const pkgRoot = (0,
|
|
24224
|
+
const pkgRoot = (0, import_node_path22.join)(packageDir, "..", "..");
|
|
22872
24225
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
22873
24226
|
if (pkgFrom) return pkgFrom;
|
|
22874
24227
|
}
|
|
@@ -22930,7 +24283,7 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
22930
24283
|
}
|
|
22931
24284
|
|
|
22932
24285
|
// src/bootstrap-drift.ts
|
|
22933
|
-
var
|
|
24286
|
+
var import_node_crypto7 = require("node:crypto");
|
|
22934
24287
|
function byteComparableSeeds(manifest, cls) {
|
|
22935
24288
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
22936
24289
|
}
|
|
@@ -22950,7 +24303,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
22950
24303
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
22951
24304
|
}
|
|
22952
24305
|
function seedContentHash(content) {
|
|
22953
|
-
return (0,
|
|
24306
|
+
return (0, import_node_crypto7.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
22954
24307
|
}
|
|
22955
24308
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
22956
24309
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -23112,7 +24465,7 @@ function renderPropagationReport(plan) {
|
|
|
23112
24465
|
}
|
|
23113
24466
|
|
|
23114
24467
|
// src/bootstrap-propagation-identity.ts
|
|
23115
|
-
var
|
|
24468
|
+
var import_node_crypto8 = require("node:crypto");
|
|
23116
24469
|
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
23117
24470
|
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
23118
24471
|
function safeBranchPart(value, maxLength, fallback) {
|
|
@@ -23125,7 +24478,7 @@ function repoSlug2(repo) {
|
|
|
23125
24478
|
function propagationBranch(repo, target) {
|
|
23126
24479
|
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
23127
24480
|
const targetPart = safeBranchPart(target, 48, "target");
|
|
23128
|
-
const hash = (0,
|
|
24481
|
+
const hash = (0, import_node_crypto8.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
23129
24482
|
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
23130
24483
|
}
|
|
23131
24484
|
function legacyPropagationBranch(repo) {
|
|
@@ -23932,13 +25285,13 @@ function registerBootstrapCommands(program3) {
|
|
|
23932
25285
|
client: defaultGitHubClient(),
|
|
23933
25286
|
projectMeta: meta,
|
|
23934
25287
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
23935
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
25288
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs24.existsSync)(path2) ? (0, import_node_fs24.readFileSync)(path2, "utf8") : null,
|
|
23936
25289
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
23937
25290
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
23938
25291
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
23939
25292
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
23940
25293
|
// sanction, which is the pre-#3664 behaviour.
|
|
23941
|
-
sanctionedAdmins: (0,
|
|
25294
|
+
sanctionedAdmins: (0, import_node_fs24.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs24.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
23942
25295
|
requiredGcpApis: (() => {
|
|
23943
25296
|
const v = meta?.requiredGcpApis;
|
|
23944
25297
|
if (Array.isArray(v)) return v;
|
|
@@ -23991,14 +25344,14 @@ function registerBootstrapCommands(program3) {
|
|
|
23991
25344
|
bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
|
|
23992
25345
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
23993
25346
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23994
|
-
if (!(0,
|
|
25347
|
+
if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
|
|
23995
25348
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
23996
25349
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
23997
|
-
const manifest = loadBootstrapSeeds((0,
|
|
25350
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
|
|
23998
25351
|
const hubContents = /* @__PURE__ */ new Map();
|
|
23999
25352
|
for (const s of manifest.seeds) {
|
|
24000
25353
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
24001
|
-
hubContents.set(s.target, (0,
|
|
25354
|
+
hubContents.set(s.target, (0, import_node_fs24.existsSync)(s.target) ? (0, import_node_fs24.readFileSync)(s.target, "utf8") : null);
|
|
24002
25355
|
}
|
|
24003
25356
|
let targets;
|
|
24004
25357
|
let classOf = (_repo) => "deployable";
|
|
@@ -24116,10 +25469,10 @@ function registerBootstrapCommands(program3) {
|
|
|
24116
25469
|
return;
|
|
24117
25470
|
}
|
|
24118
25471
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
24119
|
-
if (!(0,
|
|
25472
|
+
if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
|
|
24120
25473
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
24121
25474
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
24122
|
-
const manifest = loadBootstrapSeeds((0,
|
|
25475
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
|
|
24123
25476
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
24124
25477
|
const slug = parsedRepo.slug;
|
|
24125
25478
|
const onlyTarget = o.only.trim();
|
|
@@ -24131,16 +25484,16 @@ function registerBootstrapCommands(program3) {
|
|
|
24131
25484
|
}
|
|
24132
25485
|
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
24133
25486
|
const gh = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
24134
|
-
const readFile8 = (p) => (0,
|
|
25487
|
+
const readFile8 = (p) => (0, import_node_fs24.existsSync)(p) ? (0, import_node_fs24.readFileSync)(p, "utf8") : null;
|
|
24135
25488
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
24136
25489
|
const putSeed = async (target, content, ref, sha) => {
|
|
24137
|
-
const tmp = (0,
|
|
24138
|
-
(0,
|
|
25490
|
+
const tmp = (0, import_node_path23.join)((0, import_node_os12.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
25491
|
+
(0, import_node_fs24.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
24139
25492
|
try {
|
|
24140
25493
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
24141
25494
|
} finally {
|
|
24142
25495
|
try {
|
|
24143
|
-
(0,
|
|
25496
|
+
(0, import_node_fs24.unlinkSync)(tmp);
|
|
24144
25497
|
} catch {
|
|
24145
25498
|
}
|
|
24146
25499
|
}
|
|
@@ -24455,10 +25808,10 @@ LIVE apply to ${repo}:
|
|
|
24455
25808
|
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
|
|
24456
25809
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
24457
25810
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
24458
|
-
if (!(0,
|
|
25811
|
+
if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
|
|
24459
25812
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
24460
25813
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
24461
|
-
const manifest = loadBootstrapSeeds((0,
|
|
25814
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
|
|
24462
25815
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
24463
25816
|
if (!o.target) {
|
|
24464
25817
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -24467,9 +25820,9 @@ LIVE apply to ${repo}:
|
|
|
24467
25820
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
24468
25821
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
24469
25822
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
24470
|
-
if (!seed.managedBlock && !(0,
|
|
24471
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
24472
|
-
const readSeedFile = (path2) => (0,
|
|
25823
|
+
if (!seed.managedBlock && !(0, import_node_fs24.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
25824
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs24.readFileSync)(seed.target, "utf8");
|
|
25825
|
+
const readSeedFile = (path2) => (0, import_node_fs24.existsSync)(path2) ? (0, import_node_fs24.readFileSync)(path2, "utf8") : null;
|
|
24473
25826
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
24474
25827
|
const cfg = await loadConfig();
|
|
24475
25828
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
@@ -24478,9 +25831,9 @@ LIVE apply to ${repo}:
|
|
|
24478
25831
|
}
|
|
24479
25832
|
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
24480
25833
|
let independentCount = rosterRepos.length;
|
|
24481
|
-
if ((0,
|
|
25834
|
+
if ((0, import_node_fs24.existsSync)("projects.json")) {
|
|
24482
25835
|
try {
|
|
24483
|
-
const local = JSON.parse((0,
|
|
25836
|
+
const local = JSON.parse((0, import_node_fs24.readFileSync)("projects.json", "utf8"));
|
|
24484
25837
|
const localRepos = /* @__PURE__ */ new Set();
|
|
24485
25838
|
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
24486
25839
|
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
@@ -24640,15 +25993,15 @@ LIVE apply to ${repo}:
|
|
|
24640
25993
|
} catch {
|
|
24641
25994
|
existingSha = void 0;
|
|
24642
25995
|
}
|
|
24643
|
-
const tmp = (0,
|
|
25996
|
+
const tmp = (0, import_node_path23.join)((0, import_node_os12.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
24644
25997
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
24645
25998
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
24646
|
-
(0,
|
|
25999
|
+
(0, import_node_fs24.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
|
|
24647
26000
|
try {
|
|
24648
26001
|
await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
24649
26002
|
} finally {
|
|
24650
26003
|
try {
|
|
24651
|
-
(0,
|
|
26004
|
+
(0, import_node_fs24.unlinkSync)(tmp);
|
|
24652
26005
|
} catch {
|
|
24653
26006
|
}
|
|
24654
26007
|
}
|
|
@@ -24715,10 +26068,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
24715
26068
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
24716
26069
|
}
|
|
24717
26070
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
24718
|
-
if (!(0,
|
|
26071
|
+
if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
|
|
24719
26072
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
24720
26073
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
24721
|
-
const manifest = loadBootstrapSeeds((0,
|
|
26074
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
|
|
24722
26075
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
24723
26076
|
if (!o.target) {
|
|
24724
26077
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -24735,10 +26088,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
24735
26088
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
24736
26089
|
let candidates;
|
|
24737
26090
|
if (o.record) {
|
|
24738
|
-
if (!(0,
|
|
26091
|
+
if (!(0, import_node_fs24.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
24739
26092
|
let parsed;
|
|
24740
26093
|
try {
|
|
24741
|
-
parsed = JSON.parse((0,
|
|
26094
|
+
parsed = JSON.parse((0, import_node_fs24.readFileSync)(o.record, "utf8"));
|
|
24742
26095
|
} catch (e) {
|
|
24743
26096
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
24744
26097
|
}
|
|
@@ -24815,13 +26168,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
24815
26168
|
} catch {
|
|
24816
26169
|
existingSha = void 0;
|
|
24817
26170
|
}
|
|
24818
|
-
const tmp = (0,
|
|
24819
|
-
(0,
|
|
26171
|
+
const tmp = (0, import_node_path23.join)((0, import_node_os12.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
26172
|
+
(0, import_node_fs24.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
24820
26173
|
try {
|
|
24821
26174
|
await gh(contentPutInputArgs(repo, seed.target, tmp));
|
|
24822
26175
|
} finally {
|
|
24823
26176
|
try {
|
|
24824
|
-
(0,
|
|
26177
|
+
(0, import_node_fs24.unlinkSync)(tmp);
|
|
24825
26178
|
} catch {
|
|
24826
26179
|
}
|
|
24827
26180
|
}
|
|
@@ -25814,7 +27167,7 @@ function renderDeployPortDoctor(report) {
|
|
|
25814
27167
|
}
|
|
25815
27168
|
|
|
25816
27169
|
// src/wave-status.ts
|
|
25817
|
-
var
|
|
27170
|
+
var import_node_fs27 = require("node:fs");
|
|
25818
27171
|
|
|
25819
27172
|
// src/git-worktree-observation.ts
|
|
25820
27173
|
function parseGitWorktreePorcelain(text) {
|
|
@@ -25845,8 +27198,8 @@ function deriveComposeProjectName(worktreePath) {
|
|
|
25845
27198
|
|
|
25846
27199
|
// src/stage-runner.ts
|
|
25847
27200
|
var import_node_child_process8 = require("node:child_process");
|
|
25848
|
-
var
|
|
25849
|
-
var
|
|
27201
|
+
var import_node_fs26 = require("node:fs");
|
|
27202
|
+
var import_node_path25 = require("node:path");
|
|
25850
27203
|
var import_node_net = require("node:net");
|
|
25851
27204
|
var import_node_util4 = require("node:util");
|
|
25852
27205
|
|
|
@@ -25856,8 +27209,8 @@ function normalizeEol2(s) {
|
|
|
25856
27209
|
}
|
|
25857
27210
|
|
|
25858
27211
|
// src/env-detection.ts
|
|
25859
|
-
var
|
|
25860
|
-
var
|
|
27212
|
+
var import_node_fs25 = require("node:fs");
|
|
27213
|
+
var import_node_path24 = require("node:path");
|
|
25861
27214
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
25862
27215
|
function isEnvBaseName(name) {
|
|
25863
27216
|
return name.toLowerCase() === ".env";
|
|
@@ -25867,7 +27220,7 @@ function findEnvFiles(root) {
|
|
|
25867
27220
|
const walk2 = (dir, rel) => {
|
|
25868
27221
|
let entries;
|
|
25869
27222
|
try {
|
|
25870
|
-
entries = (0,
|
|
27223
|
+
entries = (0, import_node_fs25.readdirSync)(dir, { withFileTypes: true });
|
|
25871
27224
|
} catch {
|
|
25872
27225
|
return;
|
|
25873
27226
|
}
|
|
@@ -25875,13 +27228,13 @@ function findEnvFiles(root) {
|
|
|
25875
27228
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
25876
27229
|
if (entry.isDirectory()) {
|
|
25877
27230
|
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
25878
|
-
walk2((0,
|
|
27231
|
+
walk2((0, import_node_path24.join)(dir, entry.name), relPath);
|
|
25879
27232
|
continue;
|
|
25880
27233
|
}
|
|
25881
27234
|
if (isEnvBaseName(entry.name)) {
|
|
25882
27235
|
let isDir = false;
|
|
25883
27236
|
try {
|
|
25884
|
-
isDir = (0,
|
|
27237
|
+
isDir = (0, import_node_fs25.lstatSync)((0, import_node_path24.join)(dir, entry.name)).isDirectory();
|
|
25885
27238
|
} catch {
|
|
25886
27239
|
}
|
|
25887
27240
|
if (!isDir) found.push(relPath);
|
|
@@ -25912,7 +27265,7 @@ function healthPollIntervalMs() {
|
|
|
25912
27265
|
return HEALTH_POLL_INTERVAL_MS;
|
|
25913
27266
|
}
|
|
25914
27267
|
function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
|
|
25915
|
-
return new Promise((
|
|
27268
|
+
return new Promise((resolve7, reject) => {
|
|
25916
27269
|
let settled = false;
|
|
25917
27270
|
const finish = (fn) => {
|
|
25918
27271
|
if (settled) return;
|
|
@@ -25922,7 +27275,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
|
|
|
25922
27275
|
child2.removeAllListeners("exit");
|
|
25923
27276
|
fn();
|
|
25924
27277
|
};
|
|
25925
|
-
const timer = setTimeout(() => finish(
|
|
27278
|
+
const timer = setTimeout(() => finish(resolve7), graceMs);
|
|
25926
27279
|
child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
|
|
25927
27280
|
child2.on("exit", (code, signal) => {
|
|
25928
27281
|
const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
|
|
@@ -26022,7 +27375,7 @@ function appendForceRecreate(up) {
|
|
|
26022
27375
|
return `${up.trimEnd()} --force-recreate`;
|
|
26023
27376
|
}
|
|
26024
27377
|
function stageStatePath(cwd = process.cwd()) {
|
|
26025
|
-
return (0,
|
|
27378
|
+
return (0, import_node_path25.join)(cwd, "tmp", "stage", "state.json");
|
|
26026
27379
|
}
|
|
26027
27380
|
function normPath(path2) {
|
|
26028
27381
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -26128,10 +27481,10 @@ function pickStagePort(range, isFree) {
|
|
|
26128
27481
|
throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
|
|
26129
27482
|
}
|
|
26130
27483
|
function isPortFree(port) {
|
|
26131
|
-
return new Promise((
|
|
27484
|
+
return new Promise((resolve7) => {
|
|
26132
27485
|
const srv = (0, import_node_net.createServer)();
|
|
26133
|
-
srv.once("error", () =>
|
|
26134
|
-
srv.once("listening", () => srv.close(() =>
|
|
27486
|
+
srv.once("error", () => resolve7(false));
|
|
27487
|
+
srv.once("listening", () => srv.close(() => resolve7(true)));
|
|
26135
27488
|
srv.listen(port, "127.0.0.1");
|
|
26136
27489
|
});
|
|
26137
27490
|
}
|
|
@@ -26246,14 +27599,14 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
26246
27599
|
}
|
|
26247
27600
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
26248
27601
|
if (!config.ensureEnv) return;
|
|
26249
|
-
const target = (0,
|
|
26250
|
-
const example = (0,
|
|
26251
|
-
if (!(0,
|
|
26252
|
-
(0,
|
|
26253
|
-
} else if ((0,
|
|
26254
|
-
const stale = detectStaleEnvFile((0,
|
|
26255
|
-
exampleMtimeMs: (0,
|
|
26256
|
-
targetMtimeMs: (0,
|
|
27602
|
+
const target = (0, import_node_path25.join)(cwd, config.ensureEnv.target);
|
|
27603
|
+
const example = (0, import_node_path25.join)(cwd, config.ensureEnv.example);
|
|
27604
|
+
if (!(0, import_node_fs26.existsSync)(target) && (0, import_node_fs26.existsSync)(example)) {
|
|
27605
|
+
(0, import_node_fs26.copyFileSync)(example, target);
|
|
27606
|
+
} else if ((0, import_node_fs26.existsSync)(target) && (0, import_node_fs26.existsSync)(example)) {
|
|
27607
|
+
const stale = detectStaleEnvFile((0, import_node_fs26.readFileSync)(example, "utf8"), (0, import_node_fs26.readFileSync)(target, "utf8"), {
|
|
27608
|
+
exampleMtimeMs: (0, import_node_fs26.statSync)(example).mtimeMs,
|
|
27609
|
+
targetMtimeMs: (0, import_node_fs26.statSync)(target).mtimeMs
|
|
26257
27610
|
});
|
|
26258
27611
|
if (stale) {
|
|
26259
27612
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -26261,8 +27614,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
26261
27614
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
26262
27615
|
}
|
|
26263
27616
|
}
|
|
26264
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
26265
|
-
(0,
|
|
27617
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs26.existsSync)(target)) {
|
|
27618
|
+
(0, import_node_fs26.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs26.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
26266
27619
|
}
|
|
26267
27620
|
}
|
|
26268
27621
|
async function gitText(cwd, args) {
|
|
@@ -26292,20 +27645,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
26292
27645
|
return void 0;
|
|
26293
27646
|
}
|
|
26294
27647
|
function readState(path2) {
|
|
26295
|
-
if (!(0,
|
|
27648
|
+
if (!(0, import_node_fs26.existsSync)(path2)) return null;
|
|
26296
27649
|
try {
|
|
26297
|
-
return JSON.parse((0,
|
|
27650
|
+
return JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
|
|
26298
27651
|
} catch {
|
|
26299
27652
|
return null;
|
|
26300
27653
|
}
|
|
26301
27654
|
}
|
|
26302
27655
|
function mkdirFor(path2) {
|
|
26303
27656
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
26304
|
-
(0,
|
|
27657
|
+
(0, import_node_fs26.mkdirSync)(dir, { recursive: true });
|
|
26305
27658
|
}
|
|
26306
27659
|
function writeState(path2, state) {
|
|
26307
27660
|
mkdirFor(path2);
|
|
26308
|
-
(0,
|
|
27661
|
+
(0, import_node_fs26.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
26309
27662
|
}
|
|
26310
27663
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
26311
27664
|
const reservation = {
|
|
@@ -26325,7 +27678,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
|
|
|
26325
27678
|
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, Math.max(timeoutMs, 1e4));
|
|
26326
27679
|
}
|
|
26327
27680
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
26328
|
-
(0,
|
|
27681
|
+
(0, import_node_fs26.rmSync)(path2, { force: true });
|
|
26329
27682
|
}
|
|
26330
27683
|
}
|
|
26331
27684
|
async function killTree(pid) {
|
|
@@ -26342,7 +27695,7 @@ async function killTree(pid) {
|
|
|
26342
27695
|
} catch {
|
|
26343
27696
|
}
|
|
26344
27697
|
}
|
|
26345
|
-
await new Promise((
|
|
27698
|
+
await new Promise((resolve7) => setTimeout(resolve7, 500));
|
|
26346
27699
|
try {
|
|
26347
27700
|
process.kill(-pid, "SIGKILL");
|
|
26348
27701
|
} catch {
|
|
@@ -26363,7 +27716,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
|
|
|
26363
27716
|
} catch (e) {
|
|
26364
27717
|
last = e.message;
|
|
26365
27718
|
}
|
|
26366
|
-
await new Promise((
|
|
27719
|
+
await new Promise((resolve7) => setTimeout(resolve7, healthPollIntervalMs()));
|
|
26367
27720
|
}
|
|
26368
27721
|
throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
|
|
26369
27722
|
}
|
|
@@ -26495,8 +27848,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
26495
27848
|
});
|
|
26496
27849
|
}
|
|
26497
27850
|
} catch (e) {
|
|
26498
|
-
(0,
|
|
26499
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
27851
|
+
(0, import_node_fs26.rmSync)(statePath, { force: true });
|
|
27852
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs26.rmSync)(globalStatePath, { force: true });
|
|
26500
27853
|
throw e;
|
|
26501
27854
|
}
|
|
26502
27855
|
const started = await startStage(config, {
|
|
@@ -26513,9 +27866,9 @@ async function runStage(config = {}, opts = {}) {
|
|
|
26513
27866
|
// src/wave-status.ts
|
|
26514
27867
|
function readStageSummary(worktreePath) {
|
|
26515
27868
|
const statePath = stageStatePath(worktreePath);
|
|
26516
|
-
if (!(0,
|
|
27869
|
+
if (!(0, import_node_fs27.existsSync)(statePath)) return void 0;
|
|
26517
27870
|
try {
|
|
26518
|
-
const state = JSON.parse((0,
|
|
27871
|
+
const state = JSON.parse((0, import_node_fs27.readFileSync)(statePath, "utf8"));
|
|
26519
27872
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
26520
27873
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
26521
27874
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -26570,9 +27923,9 @@ async function collectWaveStatus(deps) {
|
|
|
26570
27923
|
}
|
|
26571
27924
|
|
|
26572
27925
|
// src/discovery-commands.ts
|
|
26573
|
-
var
|
|
27926
|
+
var import_node_fs28 = require("node:fs");
|
|
26574
27927
|
var import_node_os13 = require("node:os");
|
|
26575
|
-
var
|
|
27928
|
+
var import_node_path26 = require("node:path");
|
|
26576
27929
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
26577
27930
|
async function collectStatus() {
|
|
26578
27931
|
const repo = await resolveRepo();
|
|
@@ -26762,8 +28115,8 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
26762
28115
|
}
|
|
26763
28116
|
const home = (0, import_node_os13.homedir)();
|
|
26764
28117
|
const plugin = onboardPluginGate({
|
|
26765
|
-
readKnown: () => readFileSyncSafe((0,
|
|
26766
|
-
readSettings: () => readFileSyncSafe((0,
|
|
28118
|
+
readKnown: () => readFileSyncSafe((0, import_node_path26.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs28.readFileSync),
|
|
28119
|
+
readSettings: () => readFileSyncSafe((0, import_node_path26.join)(home, ".claude", "settings.json"), import_node_fs28.readFileSync)
|
|
26767
28120
|
});
|
|
26768
28121
|
return { track, board, registry: registry2, secrets, plugin, doors: opts.doors ?? [], nextCommand };
|
|
26769
28122
|
}
|
|
@@ -28794,9 +30147,9 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28794
30147
|
}
|
|
28795
30148
|
|
|
28796
30149
|
// src/doctor-io.ts
|
|
28797
|
-
var
|
|
30150
|
+
var import_node_fs29 = require("node:fs");
|
|
28798
30151
|
var import_node_os14 = require("node:os");
|
|
28799
|
-
var
|
|
30152
|
+
var import_node_path27 = require("node:path");
|
|
28800
30153
|
var import_node_child_process9 = require("node:child_process");
|
|
28801
30154
|
var import_node_util5 = require("node:util");
|
|
28802
30155
|
|
|
@@ -28809,8 +30162,8 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
|
|
|
28809
30162
|
var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process9.execFile);
|
|
28810
30163
|
function execFileCapture(file, args, opts = {}) {
|
|
28811
30164
|
const sink = nodeDiscardSinkPath();
|
|
28812
|
-
const inFd = (0,
|
|
28813
|
-
const errFd = (0,
|
|
30165
|
+
const inFd = (0, import_node_fs29.openSync)(sink, "r");
|
|
30166
|
+
const errFd = (0, import_node_fs29.openSync)(sink, "w");
|
|
28814
30167
|
try {
|
|
28815
30168
|
return (0, import_node_child_process9.execFileSync)(file, args, {
|
|
28816
30169
|
...opts,
|
|
@@ -28818,17 +30171,17 @@ function execFileCapture(file, args, opts = {}) {
|
|
|
28818
30171
|
stdio: [inFd, "pipe", errFd]
|
|
28819
30172
|
});
|
|
28820
30173
|
} finally {
|
|
28821
|
-
(0,
|
|
28822
|
-
(0,
|
|
30174
|
+
(0, import_node_fs29.closeSync)(inFd);
|
|
30175
|
+
(0, import_node_fs29.closeSync)(errFd);
|
|
28823
30176
|
}
|
|
28824
30177
|
}
|
|
28825
30178
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
28826
30179
|
function stagedClaudePluginVersion() {
|
|
28827
30180
|
try {
|
|
28828
|
-
const stagingRoot = (0,
|
|
28829
|
-
const record = JSON.parse((0,
|
|
30181
|
+
const stagingRoot = (0, import_node_path27.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "staging");
|
|
30182
|
+
const record = JSON.parse((0, import_node_fs29.readFileSync)((0, import_node_path27.join)(stagingRoot, "launch-target.json"), "utf8"));
|
|
28830
30183
|
if (typeof record.target !== "string" || typeof record.incoming !== "string") return void 0;
|
|
28831
|
-
const manifest = JSON.parse((0,
|
|
30184
|
+
const manifest = JSON.parse((0, import_node_fs29.readFileSync)((0, import_node_path27.join)(record.incoming, ".claude-plugin", "plugin.json"), "utf8"));
|
|
28832
30185
|
if (typeof manifest.version !== "string") return void 0;
|
|
28833
30186
|
return manifest.version === record.target ? record.target : void 0;
|
|
28834
30187
|
} catch {
|
|
@@ -28838,7 +30191,7 @@ function stagedClaudePluginVersion() {
|
|
|
28838
30191
|
function installedClaudePluginVersion() {
|
|
28839
30192
|
try {
|
|
28840
30193
|
const file = JSON.parse(
|
|
28841
|
-
(0,
|
|
30194
|
+
(0, import_node_fs29.readFileSync)((0, import_node_path27.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
28842
30195
|
);
|
|
28843
30196
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
28844
30197
|
if (versions.length === 0) return void 0;
|
|
@@ -28849,7 +30202,7 @@ function installedClaudePluginVersion() {
|
|
|
28849
30202
|
}
|
|
28850
30203
|
function manifestVersion(path2) {
|
|
28851
30204
|
try {
|
|
28852
|
-
const manifest = JSON.parse((0,
|
|
30205
|
+
const manifest = JSON.parse((0, import_node_fs29.readFileSync)(path2, "utf8"));
|
|
28853
30206
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
28854
30207
|
} catch {
|
|
28855
30208
|
return void 0;
|
|
@@ -28858,12 +30211,12 @@ function manifestVersion(path2) {
|
|
|
28858
30211
|
function readHermesPluginEvidence(env = process.env) {
|
|
28859
30212
|
const host = hermesConfigRoot(env);
|
|
28860
30213
|
const root = hermesPluginRoot(env);
|
|
28861
|
-
const installRecordPresent = (0,
|
|
28862
|
-
const manifestPath = (0,
|
|
30214
|
+
const installRecordPresent = (0, import_node_fs29.existsSync)(root);
|
|
30215
|
+
const manifestPath = (0, import_node_path27.join)(root, "plugin.yaml");
|
|
28863
30216
|
let installedVersion;
|
|
28864
30217
|
let manifest = "missing";
|
|
28865
30218
|
try {
|
|
28866
|
-
const text = (0,
|
|
30219
|
+
const text = (0, import_node_fs29.readFileSync)(manifestPath, "utf8");
|
|
28867
30220
|
let version;
|
|
28868
30221
|
try {
|
|
28869
30222
|
const parsed = JSON.parse(text).version;
|
|
@@ -28877,20 +30230,20 @@ function readHermesPluginEvidence(env = process.env) {
|
|
|
28877
30230
|
if (version) {
|
|
28878
30231
|
installedVersion = version;
|
|
28879
30232
|
manifest = "valid";
|
|
28880
|
-
} else if ((0,
|
|
30233
|
+
} else if ((0, import_node_fs29.existsSync)(manifestPath)) manifest = "invalid";
|
|
28881
30234
|
} catch {
|
|
28882
|
-
if ((0,
|
|
30235
|
+
if ((0, import_node_fs29.existsSync)(manifestPath)) manifest = "invalid";
|
|
28883
30236
|
}
|
|
28884
30237
|
let skills = false;
|
|
28885
30238
|
try {
|
|
28886
|
-
skills = (0,
|
|
30239
|
+
skills = (0, import_node_fs29.existsSync)((0, import_node_path27.join)(root, "skills")) && (0, import_node_fs29.statSync)((0, import_node_path27.join)(root, "skills")).isDirectory();
|
|
28887
30240
|
} catch {
|
|
28888
30241
|
}
|
|
28889
30242
|
return {
|
|
28890
|
-
hostPresent: (0,
|
|
30243
|
+
hostPresent: (0, import_node_fs29.existsSync)(host),
|
|
28891
30244
|
installRecordPresent,
|
|
28892
30245
|
manifest,
|
|
28893
|
-
payloadPresent: (0,
|
|
30246
|
+
payloadPresent: (0, import_node_fs29.existsSync)((0, import_node_path27.join)(root, "__init__.py")) && skills && manifest === "valid",
|
|
28894
30247
|
...installedVersion ? { installedVersion } : {}
|
|
28895
30248
|
};
|
|
28896
30249
|
}
|
|
@@ -28898,7 +30251,7 @@ function installedSurfacePluginVersion(surface) {
|
|
|
28898
30251
|
const token = surfaceToken(surface);
|
|
28899
30252
|
if (token === "hermes") return readHermesPluginEvidence().installedVersion;
|
|
28900
30253
|
if (token === "cursor") {
|
|
28901
|
-
return manifestVersion((0,
|
|
30254
|
+
return manifestVersion((0, import_node_path27.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
28902
30255
|
}
|
|
28903
30256
|
if (token === "jervcode") {
|
|
28904
30257
|
return installedJervCodePackageVersion();
|
|
@@ -28935,13 +30288,13 @@ function worktreeRootSync() {
|
|
|
28935
30288
|
}
|
|
28936
30289
|
var gitignorePath = () => {
|
|
28937
30290
|
const root = worktreeRootSync();
|
|
28938
|
-
return root === null ? null : (0,
|
|
30291
|
+
return root === null ? null : (0, import_node_path27.join)(root, ".gitignore");
|
|
28939
30292
|
};
|
|
28940
30293
|
function readGitignore() {
|
|
28941
30294
|
const path2 = gitignorePath();
|
|
28942
30295
|
if (path2 === null) return null;
|
|
28943
30296
|
try {
|
|
28944
|
-
return (0,
|
|
30297
|
+
return (0, import_node_fs29.readFileSync)(path2, "utf8");
|
|
28945
30298
|
} catch {
|
|
28946
30299
|
return null;
|
|
28947
30300
|
}
|
|
@@ -28950,14 +30303,14 @@ function writeGitignore(content) {
|
|
|
28950
30303
|
const path2 = gitignorePath();
|
|
28951
30304
|
if (path2 === null) return false;
|
|
28952
30305
|
try {
|
|
28953
|
-
(0,
|
|
30306
|
+
(0, import_node_fs29.writeFileSync)(path2, content, "utf8");
|
|
28954
30307
|
return true;
|
|
28955
30308
|
} catch {
|
|
28956
30309
|
return false;
|
|
28957
30310
|
}
|
|
28958
30311
|
}
|
|
28959
30312
|
function lineEndingState(root) {
|
|
28960
|
-
const attributesPresent = (0,
|
|
30313
|
+
const attributesPresent = (0, import_node_fs29.existsSync)((0, import_node_path27.join)(root, ".gitattributes"));
|
|
28961
30314
|
try {
|
|
28962
30315
|
const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
|
|
28963
30316
|
windowsHide: true
|
|
@@ -29277,102 +30630,6 @@ function registerExplainCommand(program3) {
|
|
|
29277
30630
|
});
|
|
29278
30631
|
}
|
|
29279
30632
|
|
|
29280
|
-
// src/file-lock.ts
|
|
29281
|
-
var import_promises3 = require("node:fs/promises");
|
|
29282
|
-
var import_node_path26 = require("node:path");
|
|
29283
|
-
var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
29284
|
-
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
29285
|
-
var FileLockBusyError = class extends Error {
|
|
29286
|
-
lockPath;
|
|
29287
|
-
constructor(label, lockPath, maxWaitMs) {
|
|
29288
|
-
super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
|
|
29289
|
-
this.name = "FileLockBusyError";
|
|
29290
|
-
this.lockPath = lockPath;
|
|
29291
|
-
}
|
|
29292
|
-
};
|
|
29293
|
-
function resolveFileLockOpts(opts = {}) {
|
|
29294
|
-
return {
|
|
29295
|
-
staleMs: opts.staleMs ?? 3e4,
|
|
29296
|
-
retryMs: opts.retryMs ?? 50,
|
|
29297
|
-
maxWaitMs: opts.maxWaitMs ?? 5e3,
|
|
29298
|
-
label: opts.label ?? "file lock"
|
|
29299
|
-
};
|
|
29300
|
-
}
|
|
29301
|
-
async function acquireFileLock(lockPath, opts, deadline) {
|
|
29302
|
-
let immediateRetries = 0;
|
|
29303
|
-
for (; ; ) {
|
|
29304
|
-
let handle;
|
|
29305
|
-
try {
|
|
29306
|
-
handle = await (0, import_promises3.open)(lockPath, "wx");
|
|
29307
|
-
} catch (e) {
|
|
29308
|
-
const code = e.code;
|
|
29309
|
-
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
29310
|
-
const retryNow = async () => {
|
|
29311
|
-
if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
29312
|
-
if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
|
|
29313
|
-
};
|
|
29314
|
-
try {
|
|
29315
|
-
const age = Date.now() - (await (0, import_promises3.stat)(lockPath)).mtimeMs;
|
|
29316
|
-
if (age > opts.staleMs) {
|
|
29317
|
-
try {
|
|
29318
|
-
await (0, import_promises3.unlink)(lockPath);
|
|
29319
|
-
await retryNow();
|
|
29320
|
-
continue;
|
|
29321
|
-
} catch (unlinkError) {
|
|
29322
|
-
if (unlinkError instanceof FileLockBusyError) throw unlinkError;
|
|
29323
|
-
const unlinkCode = unlinkError.code;
|
|
29324
|
-
if (unlinkCode === "ENOENT") {
|
|
29325
|
-
await retryNow();
|
|
29326
|
-
continue;
|
|
29327
|
-
}
|
|
29328
|
-
}
|
|
29329
|
-
}
|
|
29330
|
-
} catch (statError) {
|
|
29331
|
-
if (statError instanceof FileLockBusyError) throw statError;
|
|
29332
|
-
if (statError.code !== "ENOENT") throw statError;
|
|
29333
|
-
await retryNow();
|
|
29334
|
-
continue;
|
|
29335
|
-
}
|
|
29336
|
-
if (Date.now() >= deadline) {
|
|
29337
|
-
throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
29338
|
-
}
|
|
29339
|
-
await sleep(opts.retryMs);
|
|
29340
|
-
continue;
|
|
29341
|
-
}
|
|
29342
|
-
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
29343
|
-
try {
|
|
29344
|
-
await handle.writeFile(token);
|
|
29345
|
-
} catch (e) {
|
|
29346
|
-
await handle.close().catch(() => void 0);
|
|
29347
|
-
throw e;
|
|
29348
|
-
}
|
|
29349
|
-
return { token, handle };
|
|
29350
|
-
}
|
|
29351
|
-
}
|
|
29352
|
-
async function fileLockHeldBy(lockPath, token) {
|
|
29353
|
-
try {
|
|
29354
|
-
return await (0, import_promises3.readFile)(lockPath, "utf8") === token;
|
|
29355
|
-
} catch {
|
|
29356
|
-
return false;
|
|
29357
|
-
}
|
|
29358
|
-
}
|
|
29359
|
-
async function releaseFileLock(lockPath, guard) {
|
|
29360
|
-
await guard.handle.close().catch(() => void 0);
|
|
29361
|
-
if (await fileLockHeldBy(lockPath, guard.token)) {
|
|
29362
|
-
await (0, import_promises3.unlink)(lockPath).catch(() => void 0);
|
|
29363
|
-
}
|
|
29364
|
-
}
|
|
29365
|
-
async function withFileLock(lockPath, opts, fn) {
|
|
29366
|
-
const resolved = resolveFileLockOpts(opts);
|
|
29367
|
-
await (0, import_promises3.mkdir)((0, import_node_path26.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
29368
|
-
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
29369
|
-
try {
|
|
29370
|
-
return await fn();
|
|
29371
|
-
} finally {
|
|
29372
|
-
await releaseFileLock(lockPath, guard);
|
|
29373
|
-
}
|
|
29374
|
-
}
|
|
29375
|
-
|
|
29376
30633
|
// src/gh-account-visibility.ts
|
|
29377
30634
|
function parseGhAuthAccounts(stdout) {
|
|
29378
30635
|
const accounts = [];
|
|
@@ -29392,8 +30649,8 @@ function parseOriginRepo(remoteUrl) {
|
|
|
29392
30649
|
}
|
|
29393
30650
|
|
|
29394
30651
|
// src/issue-commands.ts
|
|
29395
|
-
var
|
|
29396
|
-
var
|
|
30652
|
+
var import_node_fs30 = require("node:fs");
|
|
30653
|
+
var import_node_crypto9 = require("node:crypto");
|
|
29397
30654
|
|
|
29398
30655
|
// src/issue-body.ts
|
|
29399
30656
|
var import_node_os15 = require("node:os");
|
|
@@ -29642,7 +30899,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
29642
30899
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
29643
30900
|
const patch = {};
|
|
29644
30901
|
let bodyChanged = false;
|
|
29645
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
30902
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs30.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
29646
30903
|
if (options.titleFile !== void 0) {
|
|
29647
30904
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
29648
30905
|
} else if (options.title !== void 0) {
|
|
@@ -29923,7 +31180,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
29923
31180
|
const identity = `${spec.type}
|
|
29924
31181
|
${spec.title.trim()}
|
|
29925
31182
|
${spec.body ?? ""}`;
|
|
29926
|
-
const hash = (0,
|
|
31183
|
+
const hash = (0, import_node_crypto9.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
29927
31184
|
return `${batchKey}:${hash}`;
|
|
29928
31185
|
}
|
|
29929
31186
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -30243,8 +31500,8 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
30243
31500
|
(_opts, args) => ({ command: "issue unlink-child", parent: args[0], child: args[1] })
|
|
30244
31501
|
).action(async (parentRef, childRef, o) => {
|
|
30245
31502
|
try {
|
|
30246
|
-
parseIssueRef(parentRef
|
|
30247
|
-
parseIssueRef(childRef
|
|
31503
|
+
parseIssueRef(parentRef);
|
|
31504
|
+
parseIssueRef(childRef);
|
|
30248
31505
|
const defaultRepo = await resolveRepo(o.repo);
|
|
30249
31506
|
const result = await unlinkSubIssue(ghRunner, parentRef, childRef, defaultRepo);
|
|
30250
31507
|
console.log(JSON.stringify(result));
|
|
@@ -30267,7 +31524,7 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
30267
31524
|
if (opts.batch) {
|
|
30268
31525
|
let specs;
|
|
30269
31526
|
try {
|
|
30270
|
-
const raw = (0,
|
|
31527
|
+
const raw = (0, import_node_fs30.readFileSync)(opts.batch, "utf8");
|
|
30271
31528
|
specs = JSON.parse(raw);
|
|
30272
31529
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
30273
31530
|
} catch (e) {
|
|
@@ -30739,7 +31996,7 @@ async function readRestPrSnapshotWithRetry(prNumber, repo, gh = defaultGhApi, op
|
|
|
30739
31996
|
const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
|
|
30740
31997
|
const untilMs = options?.retryTransientUntilMs;
|
|
30741
31998
|
const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
|
|
30742
|
-
const sleep2 = options?.sleep ?? ((ms) => new Promise((
|
|
31999
|
+
const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
30743
32000
|
const now = options?.now ?? (() => Date.now());
|
|
30744
32001
|
let lastError = "no attempt completed";
|
|
30745
32002
|
let attempt = 0;
|
|
@@ -31394,8 +32651,8 @@ function registerPrLifecycleCommands(program3) {
|
|
|
31394
32651
|
}
|
|
31395
32652
|
|
|
31396
32653
|
// src/project-info-sync.ts
|
|
31397
|
-
var
|
|
31398
|
-
var
|
|
32654
|
+
var import_node_fs31 = require("node:fs");
|
|
32655
|
+
var import_node_path28 = require("node:path");
|
|
31399
32656
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
31400
32657
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
31401
32658
|
projectV2 { id }
|
|
@@ -31440,14 +32697,14 @@ function sharedName(entries, fallback) {
|
|
|
31440
32697
|
}
|
|
31441
32698
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
31442
32699
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
31443
|
-
const readmePath = (0,
|
|
31444
|
-
if (!(0,
|
|
32700
|
+
const readmePath = (0, import_node_path28.join)(repoRoot2, "README.md");
|
|
32701
|
+
if (!(0, import_node_fs31.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
31445
32702
|
const entries = entriesFor(project2, projects);
|
|
31446
32703
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
31447
32704
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
31448
32705
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
31449
32706
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
31450
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
32707
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs31.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
31451
32708
|
const lines = [
|
|
31452
32709
|
`# ${projectName}`,
|
|
31453
32710
|
"",
|
|
@@ -31466,8 +32723,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
31466
32723
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
31467
32724
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
31468
32725
|
const orgDocs = [
|
|
31469
|
-
(0,
|
|
31470
|
-
(0,
|
|
32726
|
+
(0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
32727
|
+
(0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
|
|
31471
32728
|
].filter(Boolean);
|
|
31472
32729
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
31473
32730
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -31682,7 +32939,7 @@ async function awsJson(args) {
|
|
|
31682
32939
|
try {
|
|
31683
32940
|
return await run();
|
|
31684
32941
|
} catch {
|
|
31685
|
-
await new Promise((
|
|
32942
|
+
await new Promise((resolve7) => setTimeout(resolve7, AWS_RETRY_DELAY_MS));
|
|
31686
32943
|
return run();
|
|
31687
32944
|
}
|
|
31688
32945
|
}
|
|
@@ -31885,8 +33142,8 @@ function registerSchedulesCommands(program3) {
|
|
|
31885
33142
|
}
|
|
31886
33143
|
|
|
31887
33144
|
// src/secrets-commands.ts
|
|
31888
|
-
var
|
|
31889
|
-
var
|
|
33145
|
+
var import_node_fs32 = require("node:fs");
|
|
33146
|
+
var import_node_path29 = require("node:path");
|
|
31890
33147
|
var import_node_os16 = require("node:os");
|
|
31891
33148
|
|
|
31892
33149
|
// src/secrets-diff.ts
|
|
@@ -31989,18 +33246,18 @@ function collectMap(value, previous = []) {
|
|
|
31989
33246
|
return [...previous, value];
|
|
31990
33247
|
}
|
|
31991
33248
|
async function decryptRailsCredentials(input) {
|
|
31992
|
-
const appDir = (0,
|
|
33249
|
+
const appDir = (0, import_node_path29.resolve)(input.appDir ?? process.cwd());
|
|
31993
33250
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
31994
33251
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
31995
|
-
const credentialsPath = (0,
|
|
31996
|
-
const masterKeyPath = (0,
|
|
33252
|
+
const credentialsPath = (0, import_node_path29.resolve)(appDir, credentialsFile);
|
|
33253
|
+
const masterKeyPath = (0, import_node_path29.resolve)(appDir, masterKeyFile);
|
|
31997
33254
|
const env = {
|
|
31998
33255
|
...process.env,
|
|
31999
33256
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
32000
33257
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
32001
33258
|
};
|
|
32002
|
-
if ((0,
|
|
32003
|
-
env.RAILS_MASTER_KEY = (0,
|
|
33259
|
+
if ((0, import_node_fs32.existsSync)(masterKeyPath)) {
|
|
33260
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs32.readFileSync)(masterKeyPath, "utf8").trim();
|
|
32004
33261
|
}
|
|
32005
33262
|
const script = [
|
|
32006
33263
|
'require "json"',
|
|
@@ -32010,9 +33267,9 @@ async function decryptRailsCredentials(input) {
|
|
|
32010
33267
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
32011
33268
|
"puts JSON.generate(config.config)"
|
|
32012
33269
|
].join("\n");
|
|
32013
|
-
const scriptDir = (0,
|
|
32014
|
-
const scriptPath = (0,
|
|
32015
|
-
(0,
|
|
33270
|
+
const scriptDir = (0, import_node_fs32.mkdtempSync)((0, import_node_path29.join)((0, import_node_os16.tmpdir)(), "mmi-rails-decrypt-"));
|
|
33271
|
+
const scriptPath = (0, import_node_path29.join)(scriptDir, "decrypt.rb");
|
|
33272
|
+
(0, import_node_fs32.writeFileSync)(scriptPath, script, "utf8");
|
|
32016
33273
|
try {
|
|
32017
33274
|
const args = ["exec", "ruby", scriptPath];
|
|
32018
33275
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -32024,7 +33281,7 @@ async function decryptRailsCredentials(input) {
|
|
|
32024
33281
|
});
|
|
32025
33282
|
return JSON.parse(stdout);
|
|
32026
33283
|
} finally {
|
|
32027
|
-
(0,
|
|
33284
|
+
(0, import_node_fs32.rmSync)(scriptDir, { recursive: true, force: true });
|
|
32028
33285
|
}
|
|
32029
33286
|
}
|
|
32030
33287
|
async function readSecretStdin() {
|
|
@@ -32114,7 +33371,7 @@ function registerSecretsCommands(program3) {
|
|
|
32114
33371
|
let body;
|
|
32115
33372
|
if (o.file) {
|
|
32116
33373
|
try {
|
|
32117
|
-
body = (0,
|
|
33374
|
+
body = (0, import_node_fs32.readFileSync)((0, import_node_path29.resolve)(o.file), "utf8");
|
|
32118
33375
|
} catch (e) {
|
|
32119
33376
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
32120
33377
|
}
|
|
@@ -32219,7 +33476,7 @@ function registerSecretsCommands(program3) {
|
|
|
32219
33476
|
{
|
|
32220
33477
|
...d,
|
|
32221
33478
|
decryptRailsCredentials,
|
|
32222
|
-
removeFile: (path2) => (0,
|
|
33479
|
+
removeFile: (path2) => (0, import_node_fs32.unlinkSync)((0, import_node_path29.resolve)(o.appDir ?? process.cwd(), path2))
|
|
32223
33480
|
},
|
|
32224
33481
|
{
|
|
32225
33482
|
repo: o.repo,
|
|
@@ -32380,8 +33637,8 @@ function registerSessionReport(program3) {
|
|
|
32380
33637
|
}
|
|
32381
33638
|
|
|
32382
33639
|
// src/stage-commands.ts
|
|
32383
|
-
var
|
|
32384
|
-
var
|
|
33640
|
+
var import_node_fs33 = require("node:fs");
|
|
33641
|
+
var import_node_path30 = require("node:path");
|
|
32385
33642
|
|
|
32386
33643
|
// src/stage-default.ts
|
|
32387
33644
|
function shellFor(platform2 = process.platform) {
|
|
@@ -32665,8 +33922,8 @@ function registerStageCommands(program3) {
|
|
|
32665
33922
|
return {
|
|
32666
33923
|
resolution: decideStage({
|
|
32667
33924
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
32668
|
-
hasCompose: (0,
|
|
32669
|
-
hasEnvExample: (0,
|
|
33925
|
+
hasCompose: (0, import_node_fs33.existsSync)((0, import_node_path30.join)(process.cwd(), "docker-compose.yml")),
|
|
33926
|
+
hasEnvExample: (0, import_node_fs33.existsSync)((0, import_node_path30.join)(process.cwd(), ".env.example")),
|
|
32670
33927
|
repo
|
|
32671
33928
|
}),
|
|
32672
33929
|
project: project2,
|
|
@@ -32916,20 +34173,20 @@ function registerStageCommands(program3) {
|
|
|
32916
34173
|
}
|
|
32917
34174
|
|
|
32918
34175
|
// src/tenant-artifact.ts
|
|
32919
|
-
var
|
|
32920
|
-
var
|
|
34176
|
+
var import_node_crypto10 = require("node:crypto");
|
|
34177
|
+
var import_node_fs34 = require("node:fs");
|
|
32921
34178
|
var import_promises6 = require("node:fs/promises");
|
|
32922
|
-
var
|
|
34179
|
+
var import_node_path31 = require("node:path");
|
|
32923
34180
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
32924
34181
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
32925
34182
|
async function sha256File(path2) {
|
|
32926
|
-
const hash = (0,
|
|
32927
|
-
for await (const chunk of (0,
|
|
34183
|
+
const hash = (0, import_node_crypto10.createHash)("sha256");
|
|
34184
|
+
for await (const chunk of (0, import_node_fs34.createReadStream)(path2)) hash.update(chunk);
|
|
32928
34185
|
return hash.digest("hex");
|
|
32929
34186
|
}
|
|
32930
34187
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
32931
34188
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
32932
|
-
const path2 = (0,
|
|
34189
|
+
const path2 = (0, import_node_path31.resolve)(inputPath);
|
|
32933
34190
|
const info = await (0, import_promises6.stat)(path2);
|
|
32934
34191
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
32935
34192
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
|
|
@@ -32947,7 +34204,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
32947
34204
|
return [key, value];
|
|
32948
34205
|
}));
|
|
32949
34206
|
headers["content-length"] = String(info.size);
|
|
32950
|
-
const stream = (0,
|
|
34207
|
+
const stream = (0, import_node_fs34.createReadStream)(path2);
|
|
32951
34208
|
let uploaded;
|
|
32952
34209
|
try {
|
|
32953
34210
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -33087,7 +34344,7 @@ function renderVerifySecrets(body) {
|
|
|
33087
34344
|
|
|
33088
34345
|
// src/command-register-collaboration.ts
|
|
33089
34346
|
var import_node_child_process14 = require("node:child_process");
|
|
33090
|
-
var
|
|
34347
|
+
var import_node_fs41 = require("node:fs");
|
|
33091
34348
|
var import_promises7 = require("node:fs/promises");
|
|
33092
34349
|
|
|
33093
34350
|
// src/session-runtime.ts
|
|
@@ -33107,7 +34364,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
33107
34364
|
}
|
|
33108
34365
|
|
|
33109
34366
|
// src/command-register-collaboration.ts
|
|
33110
|
-
var
|
|
34367
|
+
var import_node_path38 = require("node:path");
|
|
33111
34368
|
|
|
33112
34369
|
// src/attach-to-project.ts
|
|
33113
34370
|
function boardAttachRateLimitedReceipt(resetEpochSeconds) {
|
|
@@ -33194,7 +34451,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
|
|
|
33194
34451
|
if (first.mergeStatus !== "failed") return first;
|
|
33195
34452
|
const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
|
|
33196
34453
|
if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
|
|
33197
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((
|
|
34454
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
33198
34455
|
await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
|
|
33199
34456
|
const retried = await deps.mergeAuto(prNumber, repo);
|
|
33200
34457
|
if (retried.mergeStatus !== "failed") return retried;
|
|
@@ -33205,7 +34462,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 3e3;
|
|
|
33205
34462
|
async function confirmAutoMergeEnqueued(deps, options) {
|
|
33206
34463
|
const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
|
|
33207
34464
|
const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
|
|
33208
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((
|
|
34465
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
33209
34466
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
33210
34467
|
if (await deps.readMerged().catch(() => false)) return "merged";
|
|
33211
34468
|
const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
|
|
@@ -33222,7 +34479,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
|
|
|
33222
34479
|
async function readGhPrStateWithRetry(fetchState, options) {
|
|
33223
34480
|
const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
|
|
33224
34481
|
const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
|
|
33225
|
-
const sleep2 = options?.sleep ?? ((ms) => new Promise((
|
|
34482
|
+
const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
33226
34483
|
let lastError = "empty state";
|
|
33227
34484
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
33228
34485
|
try {
|
|
@@ -33295,8 +34552,8 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
33295
34552
|
}
|
|
33296
34553
|
|
|
33297
34554
|
// src/merge-cleanup.ts
|
|
33298
|
-
var
|
|
33299
|
-
var
|
|
34555
|
+
var import_node_fs36 = require("node:fs");
|
|
34556
|
+
var import_node_path33 = require("node:path");
|
|
33300
34557
|
var import_node_os17 = require("node:os");
|
|
33301
34558
|
|
|
33302
34559
|
// src/board-advance.ts
|
|
@@ -33399,8 +34656,8 @@ function boardAdvanceFailureMessage(result) {
|
|
|
33399
34656
|
|
|
33400
34657
|
// src/test-policy-core.ts
|
|
33401
34658
|
var import_node_child_process12 = require("node:child_process");
|
|
33402
|
-
var
|
|
33403
|
-
var
|
|
34659
|
+
var import_node_fs35 = require("node:fs");
|
|
34660
|
+
var import_node_path32 = require("node:path");
|
|
33404
34661
|
|
|
33405
34662
|
// src/test-command-policy-shared.mjs
|
|
33406
34663
|
var import_node_child_process11 = require("node:child_process");
|
|
@@ -33855,7 +35112,7 @@ function isMeaningfulRow(file) {
|
|
|
33855
35112
|
return file.meaningful !== false;
|
|
33856
35113
|
}
|
|
33857
35114
|
function loadPolicy(root, readFile8 = readFileOrNull2) {
|
|
33858
|
-
const raw = readFile8((0,
|
|
35115
|
+
const raw = readFile8((0, import_node_path32.join)(root, POLICY_FILE));
|
|
33859
35116
|
if (raw == null) return { mandatory: [], declared: false };
|
|
33860
35117
|
try {
|
|
33861
35118
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -33865,7 +35122,7 @@ function loadPolicy(root, readFile8 = readFileOrNull2) {
|
|
|
33865
35122
|
}
|
|
33866
35123
|
function readFileOrNull2(path2) {
|
|
33867
35124
|
try {
|
|
33868
|
-
return (0,
|
|
35125
|
+
return (0, import_node_fs35.readFileSync)(path2, "utf8");
|
|
33869
35126
|
} catch {
|
|
33870
35127
|
return null;
|
|
33871
35128
|
}
|
|
@@ -33893,12 +35150,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
33893
35150
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
33894
35151
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
33895
35152
|
}
|
|
33896
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
33897
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
35153
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs35.existsSync)(path2)) {
|
|
35154
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path32.join)(root, p)));
|
|
33898
35155
|
}
|
|
33899
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
35156
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs35.existsSync)(path2)) {
|
|
33900
35157
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
33901
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
35158
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path32.join)(root, p)));
|
|
33902
35159
|
}
|
|
33903
35160
|
function evaluate(changed, policy, present = () => false) {
|
|
33904
35161
|
const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -34026,17 +35283,17 @@ function blobAt(base, path2, cwd) {
|
|
|
34026
35283
|
}
|
|
34027
35284
|
function runTestPolicy(root, deps = {}) {
|
|
34028
35285
|
const policy = deps.policy ?? loadPolicy(root);
|
|
34029
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
35286
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs35.existsSync)(path2));
|
|
34030
35287
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
34031
35288
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
34032
35289
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
34033
35290
|
const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
34034
35291
|
const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
|
|
34035
35292
|
before: blobAt(base, path2, root),
|
|
34036
|
-
after: readFileOrNull2((0,
|
|
35293
|
+
after: readFileOrNull2((0, import_node_path32.join)(root, path2))
|
|
34037
35294
|
}));
|
|
34038
35295
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
|
|
34039
|
-
const present = (path2) => exists((0,
|
|
35296
|
+
const present = (path2) => exists((0, import_node_path32.join)(root, path2));
|
|
34040
35297
|
const removedByThisDiff = removedPaths(changed);
|
|
34041
35298
|
const staleFindings = [];
|
|
34042
35299
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -34236,13 +35493,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
34236
35493
|
return rewritten === base ? null : rewritten;
|
|
34237
35494
|
}
|
|
34238
35495
|
function writeSquashBodyFile(body) {
|
|
34239
|
-
const dir = (0,
|
|
34240
|
-
const path2 = (0,
|
|
34241
|
-
(0,
|
|
35496
|
+
const dir = (0, import_node_fs36.mkdtempSync)((0, import_node_path33.join)((0, import_node_os17.tmpdir)(), "mmi-squash-body-"));
|
|
35497
|
+
const path2 = (0, import_node_path33.join)(dir, "body.txt");
|
|
35498
|
+
(0, import_node_fs36.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
34242
35499
|
`, "utf8");
|
|
34243
35500
|
return { path: path2, cleanup: () => {
|
|
34244
35501
|
try {
|
|
34245
|
-
(0,
|
|
35502
|
+
(0, import_node_fs36.rmSync)(dir, { recursive: true, force: true });
|
|
34246
35503
|
} catch {
|
|
34247
35504
|
}
|
|
34248
35505
|
} };
|
|
@@ -34444,8 +35701,8 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
34444
35701
|
}
|
|
34445
35702
|
|
|
34446
35703
|
// src/post-merge-recon.ts
|
|
34447
|
-
var
|
|
34448
|
-
var
|
|
35704
|
+
var import_node_fs37 = require("node:fs");
|
|
35705
|
+
var import_node_path34 = require("node:path");
|
|
34449
35706
|
|
|
34450
35707
|
// src/cross-repo-filing-issue.ts
|
|
34451
35708
|
function crossRepoFilingRetryCommand(prRepo, prNumber) {
|
|
@@ -34611,16 +35868,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
34611
35868
|
}
|
|
34612
35869
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
34613
35870
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
34614
|
-
(0,
|
|
34615
|
-
(0,
|
|
35871
|
+
(0, import_node_fs37.mkdirSync)((0, import_node_path34.dirname)(path2), { recursive: true });
|
|
35872
|
+
(0, import_node_fs37.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
34616
35873
|
`, "utf8");
|
|
34617
35874
|
return path2;
|
|
34618
35875
|
}
|
|
34619
35876
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
34620
35877
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
34621
|
-
if (!(0,
|
|
35878
|
+
if (!(0, import_node_fs37.existsSync)(path2)) return;
|
|
34622
35879
|
try {
|
|
34623
|
-
(0,
|
|
35880
|
+
(0, import_node_fs37.unlinkSync)(path2);
|
|
34624
35881
|
} catch {
|
|
34625
35882
|
}
|
|
34626
35883
|
}
|
|
@@ -34655,7 +35912,7 @@ var import_node_child_process13 = require("node:child_process");
|
|
|
34655
35912
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
34656
35913
|
function catFileBatch(root, ref, paths) {
|
|
34657
35914
|
if (paths.length === 0) return Promise.resolve([]);
|
|
34658
|
-
return new Promise((
|
|
35915
|
+
return new Promise((resolve7) => {
|
|
34659
35916
|
const chunks = [];
|
|
34660
35917
|
let settled = false;
|
|
34661
35918
|
const child2 = (0, import_node_child_process13.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
|
|
@@ -34663,7 +35920,7 @@ function catFileBatch(root, ref, paths) {
|
|
|
34663
35920
|
if (settled) return;
|
|
34664
35921
|
settled = true;
|
|
34665
35922
|
clearTimeout(timer);
|
|
34666
|
-
|
|
35923
|
+
resolve7(parseCatFileBatch(Buffer.concat(chunks), paths.length));
|
|
34667
35924
|
};
|
|
34668
35925
|
const timer = setTimeout(() => {
|
|
34669
35926
|
child2.kill("SIGKILL");
|
|
@@ -34772,12 +36029,69 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
34772
36029
|
}
|
|
34773
36030
|
|
|
34774
36031
|
// src/worktree-merge-cleanup.ts
|
|
36032
|
+
var import_node_fs40 = require("node:fs");
|
|
36033
|
+
var import_node_path37 = require("node:path");
|
|
36034
|
+
|
|
36035
|
+
// src/jervcode-node-modules-cleanup.ts
|
|
34775
36036
|
var import_node_fs38 = require("node:fs");
|
|
36037
|
+
var import_node_os18 = require("node:os");
|
|
34776
36038
|
var import_node_path35 = require("node:path");
|
|
36039
|
+
var JERVCODE_PACKAGE_ENTRY = (0, import_node_path35.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
|
|
36040
|
+
var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
|
|
36041
|
+
var POSIX_NAMES2 = ["jervcode"];
|
|
36042
|
+
var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
|
|
36043
|
+
function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os18.homedir)(), platform2 = process.platform) {
|
|
36044
|
+
const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
|
|
36045
|
+
const out = [];
|
|
36046
|
+
for (const dir of jervCliCandidateDirs(env, home, platform2)) {
|
|
36047
|
+
for (const name of names) out.push((0, import_node_path35.join)(dir, name));
|
|
36048
|
+
}
|
|
36049
|
+
return out;
|
|
36050
|
+
}
|
|
36051
|
+
function resolveJervcodePath(env = process.env, home = (0, import_node_os18.homedir)(), platform2 = process.platform, exists = import_node_fs38.existsSync) {
|
|
36052
|
+
for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
|
|
36053
|
+
if (exists(candidate)) return candidate;
|
|
36054
|
+
}
|
|
36055
|
+
return void 0;
|
|
36056
|
+
}
|
|
36057
|
+
function jervcodeExecFileArgs(args, opts = {}) {
|
|
36058
|
+
const platform2 = opts.platform ?? process.platform;
|
|
36059
|
+
const exists = opts.exists ?? import_node_fs38.existsSync;
|
|
36060
|
+
const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os18.homedir)(), platform2, exists);
|
|
36061
|
+
if (resolved) {
|
|
36062
|
+
const entry = (0, import_node_path35.join)((0, import_node_path35.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
|
|
36063
|
+
if (exists(entry)) {
|
|
36064
|
+
return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
|
|
36065
|
+
}
|
|
36066
|
+
}
|
|
36067
|
+
const bin = resolved ?? "jervcode";
|
|
36068
|
+
if (platform2 === "win32") {
|
|
36069
|
+
return { file: "cmd.exe", args: ["/c", bin, ...args], via: resolved ? "cmd-shim" : "bare" };
|
|
36070
|
+
}
|
|
36071
|
+
return { file: bin, args: [...args], via: resolved ? "posix" : "bare" };
|
|
36072
|
+
}
|
|
36073
|
+
async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
|
|
36074
|
+
const { cwd, timeoutMs = NODE_MODULES_CLEANUP_TIMEOUT_MS } = opts;
|
|
36075
|
+
const env = opts.env ?? process.env;
|
|
36076
|
+
const exec = opts.exec ?? execFileP;
|
|
36077
|
+
const candidates = jervcodeCandidatePaths(env, opts.home, opts.platform ?? process.platform);
|
|
36078
|
+
const plan = jervcodeExecFileArgs(["worktree-node-modules-cleanup", "--worktree", wtPath], opts);
|
|
36079
|
+
const execOptions = { timeout: timeoutMs, ...cwd ? { cwd } : {} };
|
|
36080
|
+
try {
|
|
36081
|
+
await exec(plan.file, plan.args, execOptions);
|
|
36082
|
+
return { ok: true };
|
|
36083
|
+
} catch (e) {
|
|
36084
|
+
const err = e;
|
|
36085
|
+
const base = formatJervCliSpawnFailure(err, plan, candidates);
|
|
36086
|
+
const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
|
|
36087
|
+
const detail = stderr.split("\n")[0] || base;
|
|
36088
|
+
return { ok: false, error: `jervcode worktree-node-modules-cleanup failed: ${detail}` };
|
|
36089
|
+
}
|
|
36090
|
+
}
|
|
34777
36091
|
|
|
34778
36092
|
// src/worktree-evidence-archive.ts
|
|
34779
|
-
var
|
|
34780
|
-
var
|
|
36093
|
+
var import_node_fs39 = require("node:fs");
|
|
36094
|
+
var import_node_path36 = require("node:path");
|
|
34781
36095
|
var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
|
|
34782
36096
|
function sanitizeArchiveSegment(value, max = 80) {
|
|
34783
36097
|
const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -34792,24 +36106,24 @@ function resolveArtifactRunScope(env = process.env) {
|
|
|
34792
36106
|
}
|
|
34793
36107
|
function defaultIsDirectory(path2) {
|
|
34794
36108
|
try {
|
|
34795
|
-
return (0,
|
|
36109
|
+
return (0, import_node_fs39.lstatSync)(path2).isDirectory();
|
|
34796
36110
|
} catch {
|
|
34797
36111
|
return false;
|
|
34798
36112
|
}
|
|
34799
36113
|
}
|
|
34800
36114
|
function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
34801
|
-
const exists = deps.exists ??
|
|
36115
|
+
const exists = deps.exists ?? import_node_fs39.existsSync;
|
|
34802
36116
|
const isDirectory = deps.isDirectory ?? defaultIsDirectory;
|
|
34803
|
-
const copyDir = deps.copyDir ?? ((from, to) => (0,
|
|
36117
|
+
const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs39.cpSync)(from, to, { recursive: true, force: true }));
|
|
34804
36118
|
const mkdirp = deps.mkdirp ?? ((path2) => {
|
|
34805
|
-
(0,
|
|
36119
|
+
(0, import_node_fs39.mkdirSync)(path2, { recursive: true });
|
|
34806
36120
|
});
|
|
34807
36121
|
const env = deps.env ?? process.env;
|
|
34808
36122
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
34809
36123
|
const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
|
|
34810
36124
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
34811
36125
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
34812
|
-
const source = (0,
|
|
36126
|
+
const source = (0, import_node_path36.join)(args.worktreePath, ".jerv");
|
|
34813
36127
|
if (!exists(source)) return { status: "absent" };
|
|
34814
36128
|
if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
|
|
34815
36129
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -34817,7 +36131,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
34817
36131
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
34818
36132
|
const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
|
|
34819
36133
|
try {
|
|
34820
|
-
mkdirp((0,
|
|
36134
|
+
mkdirp((0, import_node_path36.dirname)(dest));
|
|
34821
36135
|
copyDir(source, dest);
|
|
34822
36136
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
34823
36137
|
return { status: "archived", path: dest, runScope };
|
|
@@ -34826,14 +36140,14 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
34826
36140
|
}
|
|
34827
36141
|
}
|
|
34828
36142
|
function defaultStat(path2) {
|
|
34829
|
-
const st = (0,
|
|
36143
|
+
const st = (0, import_node_fs39.statSync)(path2);
|
|
34830
36144
|
return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory() };
|
|
34831
36145
|
}
|
|
34832
36146
|
function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
34833
|
-
const exists = deps.exists ??
|
|
36147
|
+
const exists = deps.exists ?? import_node_fs39.existsSync;
|
|
34834
36148
|
const stat4 = deps.stat ?? defaultStat;
|
|
34835
|
-
const readdir2 = deps.readdir ??
|
|
34836
|
-
const tmpRoot = (0,
|
|
36149
|
+
const readdir2 = deps.readdir ?? import_node_fs39.readdirSync;
|
|
36150
|
+
const tmpRoot = (0, import_node_path36.join)(worktreePath, "tmp");
|
|
34837
36151
|
if (!exists(tmpRoot)) return [];
|
|
34838
36152
|
const entries = [];
|
|
34839
36153
|
const walk2 = (dir) => {
|
|
@@ -34844,14 +36158,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
34844
36158
|
return;
|
|
34845
36159
|
}
|
|
34846
36160
|
for (const name of names) {
|
|
34847
|
-
const full = (0,
|
|
36161
|
+
const full = (0, import_node_path36.join)(dir, name);
|
|
34848
36162
|
let st;
|
|
34849
36163
|
try {
|
|
34850
36164
|
st = stat4(full);
|
|
34851
36165
|
} catch {
|
|
34852
36166
|
continue;
|
|
34853
36167
|
}
|
|
34854
|
-
const relPath = (0,
|
|
36168
|
+
const relPath = (0, import_node_path36.relative)(worktreePath, full).replace(/\\/g, "/");
|
|
34855
36169
|
if (st.isDirectory()) {
|
|
34856
36170
|
if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
|
|
34857
36171
|
walk2(full);
|
|
@@ -34865,10 +36179,10 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
34865
36179
|
return entries;
|
|
34866
36180
|
}
|
|
34867
36181
|
function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
34868
|
-
const exists = deps.exists ??
|
|
34869
|
-
const copyDir = deps.copyDir ?? ((from, to) => (0,
|
|
36182
|
+
const exists = deps.exists ?? import_node_fs39.existsSync;
|
|
36183
|
+
const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs39.cpSync)(from, to, { recursive: true, force: true }));
|
|
34870
36184
|
const mkdirp = deps.mkdirp ?? ((path2) => {
|
|
34871
|
-
(0,
|
|
36185
|
+
(0, import_node_fs39.mkdirSync)(path2, { recursive: true });
|
|
34872
36186
|
});
|
|
34873
36187
|
const env = deps.env ?? process.env;
|
|
34874
36188
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -34876,7 +36190,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
34876
36190
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
34877
36191
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
34878
36192
|
const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
|
|
34879
|
-
const source = (0,
|
|
36193
|
+
const source = (0, import_node_path36.join)(args.worktreePath, "tmp");
|
|
34880
36194
|
if (!scanned.length) return { status: "absent" };
|
|
34881
36195
|
if (!exists(source)) return { status: "absent" };
|
|
34882
36196
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -34884,7 +36198,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
34884
36198
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
34885
36199
|
const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
|
|
34886
36200
|
try {
|
|
34887
|
-
mkdirp((0,
|
|
36201
|
+
mkdirp((0, import_node_path36.dirname)(dest));
|
|
34888
36202
|
copyDir(source, dest);
|
|
34889
36203
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
34890
36204
|
const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
|
|
@@ -34953,9 +36267,9 @@ function normPath2(p) {
|
|
|
34953
36267
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
34954
36268
|
}
|
|
34955
36269
|
function unlinkNodeModulesJunction(wtPath) {
|
|
34956
|
-
const nm = (0,
|
|
36270
|
+
const nm = (0, import_node_path37.join)(wtPath, "node_modules");
|
|
34957
36271
|
try {
|
|
34958
|
-
if ((0,
|
|
36272
|
+
if ((0, import_node_fs40.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs40.rmdirSync)(nm);
|
|
34959
36273
|
return { ok: true };
|
|
34960
36274
|
} catch (e) {
|
|
34961
36275
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -34963,11 +36277,11 @@ function unlinkNodeModulesJunction(wtPath) {
|
|
|
34963
36277
|
}
|
|
34964
36278
|
}
|
|
34965
36279
|
function defaultSleep2(ms) {
|
|
34966
|
-
return new Promise((
|
|
36280
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
34967
36281
|
}
|
|
34968
36282
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
34969
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
34970
|
-
const removeRecursive = options.removeRecursive ?? ((path2) => (0,
|
|
36283
|
+
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs40.rmdirSync, exists = import_node_fs40.existsSync } = options;
|
|
36284
|
+
const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs40.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
34971
36285
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
34972
36286
|
if (!junction.ok) return junction;
|
|
34973
36287
|
try {
|
|
@@ -35181,7 +36495,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
35181
36495
|
return report;
|
|
35182
36496
|
}
|
|
35183
36497
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
35184
|
-
const pathExists = options.pathExists ??
|
|
36498
|
+
const pathExists = options.pathExists ?? import_node_fs40.existsSync;
|
|
35185
36499
|
let afterWorktrees = [];
|
|
35186
36500
|
try {
|
|
35187
36501
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -35273,6 +36587,23 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
35273
36587
|
report.localBranch = { name: branch, status: "not-attempted", reason: "archive-failed" };
|
|
35274
36588
|
return report;
|
|
35275
36589
|
}
|
|
36590
|
+
if (pathExists((0, import_node_path37.join)(wtPath, "node_modules"))) {
|
|
36591
|
+
const nmRemoved = await (options.removeRealNodeModules ?? ((p) => removeWorktreeNodeModulesViaHelper(p, { cwd: mainWorktreePath })))(wtPath);
|
|
36592
|
+
if (!nmRemoved.ok) {
|
|
36593
|
+
report.worktree = {
|
|
36594
|
+
path: wtPath,
|
|
36595
|
+
status: "failed",
|
|
36596
|
+
reason: "pre-clean-failed",
|
|
36597
|
+
error: `real-node_modules cleanup failed before worktree removal: ${nmRemoved.error}`,
|
|
36598
|
+
artifactsArchive,
|
|
36599
|
+
stageTeardown,
|
|
36600
|
+
tmpEvidenceCount: tmpEvidence.length,
|
|
36601
|
+
remediation: `jervcode worktree-node-modules-cleanup --worktree '${wtPath.replace(/'/g, "''")}'`
|
|
36602
|
+
};
|
|
36603
|
+
report.localBranch = { name: branch, status: "not-attempted", reason: "worktree-pre-clean-failed" };
|
|
36604
|
+
return report;
|
|
36605
|
+
}
|
|
36606
|
+
}
|
|
35276
36607
|
const preClean = await preCleanWorktreeForRemoval(wtPath, execGit);
|
|
35277
36608
|
if (!preClean.ok) {
|
|
35278
36609
|
report.worktree = {
|
|
@@ -35399,10 +36730,10 @@ function argvWantsJson2() {
|
|
|
35399
36730
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
35400
36731
|
}
|
|
35401
36732
|
function hubRoot() {
|
|
35402
|
-
const fromPkg = (0,
|
|
36733
|
+
const fromPkg = (0, import_node_path38.join)(__dirname, "..", "..");
|
|
35403
36734
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
35404
|
-
if ((0,
|
|
35405
|
-
if ((0,
|
|
36735
|
+
if ((0, import_node_fs41.existsSync)((0, import_node_path38.join)(fromPkg, marker))) return fromPkg;
|
|
36736
|
+
if ((0, import_node_fs41.existsSync)((0, import_node_path38.join)(process.cwd(), marker))) return process.cwd();
|
|
35406
36737
|
return null;
|
|
35407
36738
|
}
|
|
35408
36739
|
function ciAuditDeps() {
|
|
@@ -35414,8 +36745,8 @@ function ciAuditDeps() {
|
|
|
35414
36745
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
35415
36746
|
readSeedFile: (path2) => {
|
|
35416
36747
|
if (!root) return null;
|
|
35417
|
-
const fullPath = (0,
|
|
35418
|
-
return (0,
|
|
36748
|
+
const fullPath = (0, import_node_path38.join)(root, path2);
|
|
36749
|
+
return (0, import_node_fs41.existsSync)(fullPath) ? (0, import_node_fs41.readFileSync)(fullPath, "utf8") : null;
|
|
35419
36750
|
}
|
|
35420
36751
|
};
|
|
35421
36752
|
}
|
|
@@ -35631,7 +36962,7 @@ function registerCollaborationCommands(program3) {
|
|
|
35631
36962
|
repo: targetRepo2,
|
|
35632
36963
|
labels: extraLabels.length ? extraLabels : void 0
|
|
35633
36964
|
});
|
|
35634
|
-
if (o.parent !== void 0) parseIssueRef(o.parent
|
|
36965
|
+
if (o.parent !== void 0) parseIssueRef(o.parent);
|
|
35635
36966
|
} catch (e) {
|
|
35636
36967
|
return fail(`issue create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
35637
36968
|
}
|
|
@@ -35788,14 +37119,14 @@ function registerCollaborationCommands(program3) {
|
|
|
35788
37119
|
jsonParity(issue.command("link-child <parent> <child>").description("link an existing issue as a native sub-issue of a parent and print {parentNumber,subIssueNumber,totalCount} JSON").option("--repo <owner/repo>", "repo for bare refs on either side (defaults to the current repo)")).action(async (parentRef, childRef, o) => {
|
|
35789
37120
|
const defaultRepo = await resolveRepo(o.repo);
|
|
35790
37121
|
try {
|
|
35791
|
-
parseIssueRef(parentRef
|
|
35792
|
-
parseIssueRef(childRef
|
|
37122
|
+
parseIssueRef(parentRef);
|
|
37123
|
+
parseIssueRef(childRef);
|
|
35793
37124
|
const result = await linkSubIssue(ghRunner2, parentRef, childRef, defaultRepo);
|
|
35794
37125
|
console.log(JSON.stringify(result));
|
|
35795
37126
|
} catch (e) {
|
|
35796
37127
|
let conflict;
|
|
35797
37128
|
try {
|
|
35798
|
-
const child2 = parseIssueRef(childRef
|
|
37129
|
+
const child2 = parseIssueRef(childRef);
|
|
35799
37130
|
const childRepo = child2.repo ?? defaultRepo;
|
|
35800
37131
|
if (childRepo) conflict = await classifyReparentFailure(e, ghRunner2, childRepo, child2.number, parentRef);
|
|
35801
37132
|
} catch {
|
|
@@ -36046,11 +37377,11 @@ ${list}`);
|
|
|
36046
37377
|
}
|
|
36047
37378
|
});
|
|
36048
37379
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
36049
|
-
const wfDir = (0,
|
|
36050
|
-
if (!(0,
|
|
36051
|
-
return (0,
|
|
37380
|
+
const wfDir = (0, import_node_path38.join)(cwd, ".github", "workflows");
|
|
37381
|
+
if (!(0, import_node_fs41.existsSync)(wfDir)) return [];
|
|
37382
|
+
return (0, import_node_fs41.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
36052
37383
|
try {
|
|
36053
|
-
return workflowReportsPrChecks((0,
|
|
37384
|
+
return workflowReportsPrChecks((0, import_node_fs41.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
|
|
36054
37385
|
} catch {
|
|
36055
37386
|
return true;
|
|
36056
37387
|
}
|
|
@@ -36208,7 +37539,7 @@ ${list}`);
|
|
|
36208
37539
|
// #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
|
|
36209
37540
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
|
|
36210
37541
|
baseBranch,
|
|
36211
|
-
sleep: (ms) => new Promise((
|
|
37542
|
+
sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
|
|
36212
37543
|
log: (message) => console.warn(message),
|
|
36213
37544
|
timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
|
|
36214
37545
|
// Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
|
|
@@ -36312,7 +37643,7 @@ ${list}`);
|
|
|
36312
37643
|
// #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
|
|
36313
37644
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
|
|
36314
37645
|
baseBranch: "development",
|
|
36315
|
-
sleep: (ms) => new Promise((
|
|
37646
|
+
sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
|
|
36316
37647
|
log: (message) => console.warn(message),
|
|
36317
37648
|
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
|
|
36318
37649
|
// 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
|
|
@@ -36358,7 +37689,7 @@ ${list}`);
|
|
|
36358
37689
|
} else {
|
|
36359
37690
|
lastFailure = void 0;
|
|
36360
37691
|
}
|
|
36361
|
-
await new Promise((
|
|
37692
|
+
await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
|
|
36362
37693
|
}
|
|
36363
37694
|
if (lastFailure) {
|
|
36364
37695
|
throw new Error(
|
|
@@ -36445,7 +37776,7 @@ ${list}`);
|
|
|
36445
37776
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
36446
37777
|
closingGuardInput,
|
|
36447
37778
|
method === "--squash",
|
|
36448
|
-
o.squashBodyFile ? (0,
|
|
37779
|
+
o.squashBodyFile ? (0, import_node_fs41.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
36449
37780
|
);
|
|
36450
37781
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
36451
37782
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -36495,7 +37826,7 @@ ${list}`);
|
|
|
36495
37826
|
checksDiagnosticCommand: `gh pr checks ${number} --repo ${repo}`,
|
|
36496
37827
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
|
|
36497
37828
|
baseBranch,
|
|
36498
|
-
sleep: (ms) => new Promise((
|
|
37829
|
+
sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
|
|
36499
37830
|
log: (message) => console.warn(message),
|
|
36500
37831
|
timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
|
|
36501
37832
|
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
@@ -36549,7 +37880,7 @@ ${list}`);
|
|
|
36549
37880
|
}
|
|
36550
37881
|
if (!repoForPostCleanup) throw e;
|
|
36551
37882
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
36552
|
-
const commitMessage = bodyFile ? (0,
|
|
37883
|
+
const commitMessage = bodyFile ? (0, import_node_fs41.readFileSync)(bodyFile, "utf8") : void 0;
|
|
36553
37884
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
36554
37885
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
36555
37886
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -36637,7 +37968,7 @@ ${list}`);
|
|
|
36637
37968
|
preserveWorktree: o.preserveWorktree,
|
|
36638
37969
|
gcAcknowledged: o.gc,
|
|
36639
37970
|
expectedHeadOid: headRefOid,
|
|
36640
|
-
pathExists: (p) => (0,
|
|
37971
|
+
pathExists: (p) => (0, import_node_fs41.existsSync)(p),
|
|
36641
37972
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
36642
37973
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
36643
37974
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -36752,7 +38083,7 @@ ${list}`);
|
|
|
36752
38083
|
}
|
|
36753
38084
|
|
|
36754
38085
|
// src/command-register-developer.ts
|
|
36755
|
-
var
|
|
38086
|
+
var import_node_fs46 = require("node:fs");
|
|
36756
38087
|
|
|
36757
38088
|
// src/whoami.ts
|
|
36758
38089
|
async function resolveWhoami(deps) {
|
|
@@ -36782,7 +38113,7 @@ async function resolveWhoami(deps) {
|
|
|
36782
38113
|
}
|
|
36783
38114
|
|
|
36784
38115
|
// src/command-register-developer.ts
|
|
36785
|
-
var
|
|
38116
|
+
var import_node_path43 = require("node:path");
|
|
36786
38117
|
|
|
36787
38118
|
// src/wave-land.ts
|
|
36788
38119
|
function planWaveLand(prs) {
|
|
@@ -36813,7 +38144,7 @@ async function executeWaveLand(plan, deps) {
|
|
|
36813
38144
|
}
|
|
36814
38145
|
|
|
36815
38146
|
// src/box-commands.ts
|
|
36816
|
-
var
|
|
38147
|
+
var import_node_fs42 = require("node:fs");
|
|
36817
38148
|
|
|
36818
38149
|
// src/box.ts
|
|
36819
38150
|
var BOX_KEYS = {
|
|
@@ -37016,7 +38347,7 @@ function registerBoxCommands(program3) {
|
|
|
37016
38347
|
}
|
|
37017
38348
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
37018
38349
|
else if (o.ssh && o.script) {
|
|
37019
|
-
(0,
|
|
38350
|
+
(0, import_node_fs42.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
37020
38351
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
37021
38352
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
37022
38353
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -37031,28 +38362,28 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
37031
38362
|
|
|
37032
38363
|
// src/dist-drift.ts
|
|
37033
38364
|
var import_node_child_process15 = require("node:child_process");
|
|
37034
|
-
var
|
|
37035
|
-
var
|
|
37036
|
-
var
|
|
37037
|
-
var
|
|
38365
|
+
var import_node_crypto12 = require("node:crypto");
|
|
38366
|
+
var import_node_fs44 = require("node:fs");
|
|
38367
|
+
var import_node_os19 = require("node:os");
|
|
38368
|
+
var import_node_path40 = require("node:path");
|
|
37038
38369
|
|
|
37039
38370
|
// ../scripts/distribution-digest.mjs
|
|
37040
|
-
var
|
|
37041
|
-
var
|
|
37042
|
-
var
|
|
38371
|
+
var import_node_crypto11 = require("node:crypto");
|
|
38372
|
+
var import_node_fs43 = require("node:fs");
|
|
38373
|
+
var import_node_path39 = require("node:path");
|
|
37043
38374
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
37044
38375
|
function repoPath(root, declaredPath, label) {
|
|
37045
|
-
const absoluteRoot = (0,
|
|
37046
|
-
const target = (0,
|
|
37047
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
38376
|
+
const absoluteRoot = (0, import_node_path39.resolve)(root);
|
|
38377
|
+
const target = (0, import_node_path39.resolve)(root, declaredPath);
|
|
38378
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path39.sep}`)) {
|
|
37048
38379
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
37049
38380
|
}
|
|
37050
38381
|
return target;
|
|
37051
38382
|
}
|
|
37052
38383
|
function digestFiles(files) {
|
|
37053
|
-
const hash = (0,
|
|
38384
|
+
const hash = (0, import_node_crypto11.createHash)("sha256");
|
|
37054
38385
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
37055
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
38386
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs43.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs43.readFileSync)(file.absolute);
|
|
37056
38387
|
hash.update(file.relative, "utf8");
|
|
37057
38388
|
hash.update("\0");
|
|
37058
38389
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -37067,8 +38398,8 @@ function digestFiles(files) {
|
|
|
37067
38398
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
37068
38399
|
return digestFiles(packedFiles.map((path2) => {
|
|
37069
38400
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
37070
|
-
if (!(0,
|
|
37071
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
38401
|
+
if (!(0, import_node_fs43.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
38402
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs43.lstatSync)(absolute) };
|
|
37072
38403
|
}));
|
|
37073
38404
|
}
|
|
37074
38405
|
|
|
@@ -37080,7 +38411,7 @@ var DIST_ARTIFACTS = [
|
|
|
37080
38411
|
];
|
|
37081
38412
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
37082
38413
|
var ABSENT = "absent";
|
|
37083
|
-
var sha256 = (bytes) => `sha256:${(0,
|
|
38414
|
+
var sha256 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
37084
38415
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
37085
38416
|
const committed = committedBytes ? sha256(committedBytes) : ABSENT;
|
|
37086
38417
|
const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
|
|
@@ -37152,13 +38483,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
37152
38483
|
return lines;
|
|
37153
38484
|
}
|
|
37154
38485
|
function readOrNull(path2) {
|
|
37155
|
-
return (0,
|
|
38486
|
+
return (0, import_node_fs44.existsSync)(path2) ? (0, import_node_fs44.readFileSync)(path2) : null;
|
|
37156
38487
|
}
|
|
37157
38488
|
function walkFiles(root) {
|
|
37158
38489
|
const files = [];
|
|
37159
38490
|
const walk2 = (directory) => {
|
|
37160
|
-
for (const entry of (0,
|
|
37161
|
-
const child2 = (0,
|
|
38491
|
+
for (const entry of (0, import_node_fs44.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
38492
|
+
const child2 = (0, import_node_path40.join)(directory, entry.name);
|
|
37162
38493
|
if (entry.isDirectory()) walk2(child2);
|
|
37163
38494
|
else files.push(child2);
|
|
37164
38495
|
}
|
|
@@ -37168,10 +38499,10 @@ function walkFiles(root) {
|
|
|
37168
38499
|
}
|
|
37169
38500
|
function bomPathFor(root) {
|
|
37170
38501
|
try {
|
|
37171
|
-
const registry2 = JSON.parse((0,
|
|
37172
|
-
return (0,
|
|
38502
|
+
const registry2 = JSON.parse((0, import_node_fs44.readFileSync)((0, import_node_path40.join)(root, "surfaces.json"), "utf8"));
|
|
38503
|
+
return (0, import_node_path40.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
37173
38504
|
} catch {
|
|
37174
|
-
return (0,
|
|
38505
|
+
return (0, import_node_path40.join)(root, "distribution-bom.json");
|
|
37175
38506
|
}
|
|
37176
38507
|
}
|
|
37177
38508
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -37184,35 +38515,35 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
37184
38515
|
});
|
|
37185
38516
|
}
|
|
37186
38517
|
function runDistStatus(root) {
|
|
37187
|
-
const stage = (0,
|
|
38518
|
+
const stage = (0, import_node_fs44.mkdtempSync)((0, import_node_path40.join)((0, import_node_os19.tmpdir)(), "mmi-dist-drift-"));
|
|
37188
38519
|
let overlayCount = 0;
|
|
37189
38520
|
try {
|
|
37190
|
-
const cliOut = (0,
|
|
37191
|
-
const hubOut = (0,
|
|
37192
|
-
rebuildTo((0,
|
|
37193
|
-
rebuildTo((0,
|
|
38521
|
+
const cliOut = (0, import_node_path40.join)(stage, "cli-dist");
|
|
38522
|
+
const hubOut = (0, import_node_path40.join)(stage, "hub-dist");
|
|
38523
|
+
rebuildTo((0, import_node_path40.join)(root, "cli"), cliOut);
|
|
38524
|
+
rebuildTo((0, import_node_path40.join)(root, "updater"), hubOut);
|
|
37194
38525
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
37195
38526
|
const rebuilt = (path2) => {
|
|
37196
38527
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
37197
|
-
return spec ? readOrNull((0,
|
|
38528
|
+
return spec ? readOrNull((0, import_node_path40.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
37198
38529
|
};
|
|
37199
|
-
const committed = (path2) => readOrNull((0,
|
|
37200
|
-
const tree = (path2) => readOrNull((0,
|
|
37201
|
-
const distRoot = (0,
|
|
37202
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
37203
|
-
const bom = JSON.parse((0,
|
|
38530
|
+
const committed = (path2) => readOrNull((0, import_node_path40.join)(root, path2));
|
|
38531
|
+
const tree = (path2) => readOrNull((0, import_node_path40.join)(root, path2));
|
|
38532
|
+
const distRoot = (0, import_node_path40.join)(root, "cli", "dist");
|
|
38533
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path40.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
38534
|
+
const bom = JSON.parse((0, import_node_fs44.readFileSync)(bomPathFor(root), "utf8"));
|
|
37204
38535
|
const digest = (entries) => {
|
|
37205
|
-
const overlay = (0,
|
|
38536
|
+
const overlay = (0, import_node_path40.join)(stage, `overlay-${overlayCount++}`);
|
|
37206
38537
|
for (const entry of entries) {
|
|
37207
|
-
const target = (0,
|
|
37208
|
-
(0,
|
|
37209
|
-
(0,
|
|
38538
|
+
const target = (0, import_node_path40.join)(overlay, entry.path);
|
|
38539
|
+
(0, import_node_fs44.mkdirSync)((0, import_node_path40.dirname)(target), { recursive: true });
|
|
38540
|
+
(0, import_node_fs44.writeFileSync)(target, entry.bytes);
|
|
37210
38541
|
}
|
|
37211
38542
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
37212
38543
|
};
|
|
37213
38544
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
37214
38545
|
} finally {
|
|
37215
|
-
(0,
|
|
38546
|
+
(0, import_node_fs44.rmSync)(stage, { recursive: true, force: true });
|
|
37216
38547
|
}
|
|
37217
38548
|
}
|
|
37218
38549
|
|
|
@@ -37284,7 +38615,7 @@ function registerEdgeCommands(program3) {
|
|
|
37284
38615
|
|
|
37285
38616
|
// src/schedules-lift-command.ts
|
|
37286
38617
|
var import_promises8 = require("node:fs/promises");
|
|
37287
|
-
var
|
|
38618
|
+
var import_node_path41 = require("node:path");
|
|
37288
38619
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
37289
38620
|
var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
|
|
37290
38621
|
var SchedulesLiftUsageError = class extends Error {
|
|
@@ -37311,7 +38642,7 @@ async function readWorkflowFiles(dir) {
|
|
|
37311
38642
|
const files = [];
|
|
37312
38643
|
for (const name of names.sort()) {
|
|
37313
38644
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
37314
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0,
|
|
38645
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path41.join)(dir, name), "utf8") });
|
|
37315
38646
|
}
|
|
37316
38647
|
return files;
|
|
37317
38648
|
}
|
|
@@ -37394,8 +38725,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
37394
38725
|
|
|
37395
38726
|
// src/spawn-policy-core.ts
|
|
37396
38727
|
var import_node_child_process16 = require("node:child_process");
|
|
37397
|
-
var
|
|
37398
|
-
var
|
|
38728
|
+
var import_node_fs45 = require("node:fs");
|
|
38729
|
+
var import_node_path42 = require("node:path");
|
|
37399
38730
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
37400
38731
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
37401
38732
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -37481,7 +38812,7 @@ function runSpawnPolicy(root) {
|
|
|
37481
38812
|
for (const file of files) {
|
|
37482
38813
|
let raw;
|
|
37483
38814
|
try {
|
|
37484
|
-
raw = (0,
|
|
38815
|
+
raw = (0, import_node_fs45.readFileSync)((0, import_node_path42.join)(root, file), "utf8");
|
|
37485
38816
|
} catch {
|
|
37486
38817
|
continue;
|
|
37487
38818
|
}
|
|
@@ -37501,19 +38832,19 @@ function runSpawnPolicy(root) {
|
|
|
37501
38832
|
function registerDeveloperCommands(program3) {
|
|
37502
38833
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
37503
38834
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
37504
|
-
const path2 = (0,
|
|
37505
|
-
const current = (0,
|
|
38835
|
+
const path2 = (0, import_node_path43.join)(process.cwd(), ".gitignore");
|
|
38836
|
+
const current = (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2, "utf8") : null;
|
|
37506
38837
|
const plan = planManagedGitignore(current);
|
|
37507
38838
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
37508
38839
|
if (opts.json) {
|
|
37509
|
-
if (opts.write && plan.changed) (0,
|
|
38840
|
+
if (opts.write && plan.changed) (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
|
|
37510
38841
|
console.log(JSON.stringify(plan, null, 2));
|
|
37511
38842
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
37512
38843
|
return;
|
|
37513
38844
|
}
|
|
37514
38845
|
if (opts.write) {
|
|
37515
38846
|
if (plan.changed) {
|
|
37516
|
-
(0,
|
|
38847
|
+
(0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
|
|
37517
38848
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
37518
38849
|
} else {
|
|
37519
38850
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -37771,11 +39102,11 @@ var defaultDeps = {
|
|
|
37771
39102
|
// #5813: the caller supplies that command line. `process.argv` is NOT it — the house shim splices
|
|
37772
39103
|
// the house root out in place, so re-reading it here re-ran the removed flat alias (`release …`
|
|
37773
39104
|
// instead of `devops release …`) and the converged child refused with exit 2.
|
|
37774
|
-
reexec: (argv) => new Promise((
|
|
39105
|
+
reexec: (argv) => new Promise((resolve7) => {
|
|
37775
39106
|
const args = [...argv];
|
|
37776
39107
|
const child2 = isWin2 ? (0, import_node_child_process17.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process17.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
|
|
37777
|
-
child2.on("exit", (code) =>
|
|
37778
|
-
child2.on("error", () =>
|
|
39108
|
+
child2.on("exit", (code) => resolve7(code ?? 1));
|
|
39109
|
+
child2.on("error", () => resolve7(1));
|
|
37779
39110
|
}),
|
|
37780
39111
|
// stderr only — stdout may be carrying --json output.
|
|
37781
39112
|
warn: (line) => console.error(line)
|
|
@@ -37817,7 +39148,7 @@ function clean3(out) {
|
|
|
37817
39148
|
return out.trim();
|
|
37818
39149
|
}
|
|
37819
39150
|
function sleeper(deps) {
|
|
37820
|
-
return deps.sleep ?? ((ms) => new Promise((
|
|
39151
|
+
return deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
37821
39152
|
}
|
|
37822
39153
|
function normalizeHotfixVersion(input) {
|
|
37823
39154
|
const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
|
|
@@ -38846,8 +40177,8 @@ async function announceRelease(deps, args) {
|
|
|
38846
40177
|
}
|
|
38847
40178
|
|
|
38848
40179
|
// src/train-commands.ts
|
|
38849
|
-
var
|
|
38850
|
-
var
|
|
40180
|
+
var import_node_fs47 = require("node:fs");
|
|
40181
|
+
var import_node_path44 = require("node:path");
|
|
38851
40182
|
var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
|
|
38852
40183
|
function resolveReleaseBumpIntent(raw) {
|
|
38853
40184
|
const intent = typeof raw === "string" ? raw.trim() : "";
|
|
@@ -38858,7 +40189,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
38858
40189
|
}
|
|
38859
40190
|
function readRepoVersion() {
|
|
38860
40191
|
try {
|
|
38861
|
-
return JSON.parse((0,
|
|
40192
|
+
return JSON.parse((0, import_node_fs47.readFileSync)((0, import_node_path44.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
38862
40193
|
} catch {
|
|
38863
40194
|
return void 0;
|
|
38864
40195
|
}
|
|
@@ -39041,6 +40372,7 @@ function renderReleaseResume(r) {
|
|
|
39041
40372
|
if (r.devRollForward) lines.push(` development: ${r.devRollForward.note}`);
|
|
39042
40373
|
if (r.rcAlignment) lines.push(` rc: ${r.rcAlignment.note}`);
|
|
39043
40374
|
if (r.checkout) lines.push(` checkout: ${r.checkout.note}`);
|
|
40375
|
+
if (r.ledger) lines.push(...formatReleaseLedgerReport(r.ledger).map((l, i) => i === 0 ? ` ${l}` : ` ${l}`));
|
|
39044
40376
|
return lines.join("\n");
|
|
39045
40377
|
}
|
|
39046
40378
|
function renderReleaseAbort(r) {
|
|
@@ -39120,6 +40452,27 @@ async function runPostReleaseJervDoctor(repo) {
|
|
|
39120
40452
|
};
|
|
39121
40453
|
}
|
|
39122
40454
|
}
|
|
40455
|
+
function buildReleaseVerdict(commandName, result, projectInfoSync) {
|
|
40456
|
+
const alignmentPending = result.devRollForward?.status === "pr-pending" || result.rcAlignment?.status === "pr-pending";
|
|
40457
|
+
const followUpStatus = deriveTrainFollowUpStatus({
|
|
40458
|
+
projectInfo: projectInfoSync && "error" in projectInfoSync ? "failure" : "ok",
|
|
40459
|
+
deploy: deployFollowUpOutcome(result.deployStatus),
|
|
40460
|
+
rcRetirement: result.rcRetirement === "failed" ? result.rcRetirementCategory === "wait-timeout" ? "unresolved" : "failure" : "ok",
|
|
40461
|
+
alignment: alignmentPending ? "unresolved" : "ok",
|
|
40462
|
+
foldPort: "ok"
|
|
40463
|
+
});
|
|
40464
|
+
return {
|
|
40465
|
+
followUpStatus,
|
|
40466
|
+
releaseVerdict: commandName === "release" ? {
|
|
40467
|
+
releaseStatus: "succeeded",
|
|
40468
|
+
followUpStatus,
|
|
40469
|
+
promoted: result.promoted,
|
|
40470
|
+
tag: result.tag,
|
|
40471
|
+
releaseUrl: result.release?.url ?? null,
|
|
40472
|
+
legs: releaseFollowUpLegs(result, projectInfoSync)
|
|
40473
|
+
} : void 0
|
|
40474
|
+
};
|
|
40475
|
+
}
|
|
39123
40476
|
function renderTrainApply(commandName, r) {
|
|
39124
40477
|
let base = `mmi-cli ${commandName}: promoted ${r.repo} \u2014 ${r.stage} at ${r.tag} [${r.deployModel}]; ${renderDeployLine(r)}`;
|
|
39125
40478
|
if (r.versionFold) base = `${base}; ${r.versionFold}`;
|
|
@@ -39148,9 +40501,14 @@ function renderTrainApply(commandName, r) {
|
|
|
39148
40501
|
const failedLegs = v.legs.filter((leg) => leg.status === "failed");
|
|
39149
40502
|
return [
|
|
39150
40503
|
`${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}`,
|
|
39151
|
-
...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`)
|
|
40504
|
+
...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`),
|
|
40505
|
+
// #5987: the durable ledger's phase legs + derived typed verdict ride the same receipt.
|
|
40506
|
+
...r.ledger ? formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`) : []
|
|
39152
40507
|
].join("\n");
|
|
39153
40508
|
}
|
|
40509
|
+
if (r.ledger) {
|
|
40510
|
+
return [base, ...formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`)].join("\n");
|
|
40511
|
+
}
|
|
39154
40512
|
return base;
|
|
39155
40513
|
}
|
|
39156
40514
|
function applyTrainFollowUpExit(status) {
|
|
@@ -39175,7 +40533,7 @@ function registerTrainCommandsAndInventory(program3) {
|
|
|
39175
40533
|
}
|
|
39176
40534
|
function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
39177
40535
|
for (const commandName of ["rcand", "release"]) {
|
|
39178
|
-
const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)");
|
|
40536
|
+
const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "with --apply, write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983)");
|
|
39179
40537
|
const RELEASE_ONLY_FLAGS = [
|
|
39180
40538
|
{ flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line Hub Slack summary; required for a new MMI-Hub --apply (#883/#3901)" },
|
|
39181
40539
|
{ flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
|
|
@@ -39211,10 +40569,13 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
39211
40569
|
if (o.retryPublish && commandName !== "release") {
|
|
39212
40570
|
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
|
|
39213
40571
|
}
|
|
40572
|
+
if (o.out && !o.apply) {
|
|
40573
|
+
return fail(`${commandName}: --out writes the --apply result receipt only \u2014 the dry-run plan and --resume keep their stdout/JSON output. Drop --out${o.resume ? "" : " or rerun with --apply"}`);
|
|
40574
|
+
}
|
|
39214
40575
|
if (o.retryPublish) {
|
|
39215
40576
|
if (o.resume || o.abort) return fail("release: --retry-publish cannot be combined with --resume or --abort");
|
|
39216
40577
|
if (!o.apply) return fail("release: --retry-publish requires --apply after explicit approval; nothing was written");
|
|
39217
|
-
if (o.announceSummaryFile || o.ack || o.dev) return fail("release: --retry-publish accepts only --apply, --watch, --repo and --json");
|
|
40578
|
+
if (o.announceSummaryFile || o.ack || o.dev || o.out) return fail("release: --retry-publish accepts only --apply, --watch, --repo and --json");
|
|
39218
40579
|
if (o.repo) {
|
|
39219
40580
|
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops release --retry-publish ${o.retryPublish} --apply${o.watch ? " --watch" : ""}`);
|
|
39220
40581
|
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
@@ -39238,7 +40599,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
39238
40599
|
if (o.abort) {
|
|
39239
40600
|
if (o.resume) return fail("release: --abort and --resume are mutually exclusive \u2014 abort removes an unpublished tag while resume preserves and promotes it");
|
|
39240
40601
|
if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
|
|
39241
|
-
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
40602
|
+
if (o.watch || o.announceSummaryFile || o.ack || o.dev || o.out) {
|
|
39242
40603
|
return fail("release: --abort accepts only --apply, --repo and --json; promotion flags cannot be combined with rollback");
|
|
39243
40604
|
}
|
|
39244
40605
|
if (o.repo) {
|
|
@@ -39313,29 +40674,16 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
39313
40674
|
projectInfoSync = { applied: false, note: `FAILED \u2014 ${error}`, error };
|
|
39314
40675
|
}
|
|
39315
40676
|
}
|
|
39316
|
-
const
|
|
39317
|
-
const followUpStatus = deriveTrainFollowUpStatus({
|
|
39318
|
-
projectInfo: projectInfoSync && "error" in projectInfoSync ? "failure" : "ok",
|
|
39319
|
-
deploy: deployFollowUpOutcome(result.deployStatus),
|
|
39320
|
-
rcRetirement: result.rcRetirement === "failed" ? result.rcRetirementCategory === "wait-timeout" ? "unresolved" : "failure" : "ok",
|
|
39321
|
-
alignment: alignmentPending ? "unresolved" : "ok",
|
|
39322
|
-
foldPort: "ok"
|
|
39323
|
-
});
|
|
39324
|
-
const releaseVerdict = commandName === "release" ? {
|
|
39325
|
-
releaseStatus: "succeeded",
|
|
39326
|
-
followUpStatus,
|
|
39327
|
-
promoted: result.promoted,
|
|
39328
|
-
tag: result.tag,
|
|
39329
|
-
releaseUrl: result.release?.url ?? null,
|
|
39330
|
-
legs: releaseFollowUpLegs(result, projectInfoSync)
|
|
39331
|
-
} : void 0;
|
|
40677
|
+
const { followUpStatus, releaseVerdict } = buildReleaseVerdict(commandName, result, projectInfoSync);
|
|
39332
40678
|
const reported = {
|
|
39333
40679
|
...result,
|
|
39334
40680
|
...projectInfoSync ? { projectInfoSync } : {},
|
|
39335
40681
|
...postReleaseJervDoctor ? { postReleaseJervDoctor } : {},
|
|
39336
40682
|
...releaseVerdict ? { releaseVerdict } : {}
|
|
39337
40683
|
};
|
|
39338
|
-
|
|
40684
|
+
const output = o.json ? JSON.stringify(reported, null, 2) : renderTrainApply(commandName, reported);
|
|
40685
|
+
if (!o.out) printLine(output);
|
|
40686
|
+
else printLine(`Wrote ${commandName} apply result to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
|
|
39339
40687
|
applyTrainFollowUpExit(followUpStatus);
|
|
39340
40688
|
return;
|
|
39341
40689
|
} catch (e) {
|
|
@@ -39477,12 +40825,12 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
39477
40825
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
39478
40826
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
39479
40827
|
function envHealLockPath(home) {
|
|
39480
|
-
return (0,
|
|
40828
|
+
return (0, import_node_path45.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
39481
40829
|
}
|
|
39482
40830
|
async function withEnvHealLock(what, run) {
|
|
39483
40831
|
try {
|
|
39484
40832
|
return await withFileLock(
|
|
39485
|
-
envHealLockPath((0,
|
|
40833
|
+
envHealLockPath((0, import_node_os20.homedir)()),
|
|
39486
40834
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
39487
40835
|
run
|
|
39488
40836
|
);
|
|
@@ -39578,7 +40926,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39578
40926
|
const configRoot = surfaceConfigRoot(surface);
|
|
39579
40927
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
39580
40928
|
const plan = buildPluginCachePlan(
|
|
39581
|
-
(0,
|
|
40929
|
+
(0, import_node_os20.homedir)(),
|
|
39582
40930
|
running,
|
|
39583
40931
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
39584
40932
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -39606,14 +40954,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39606
40954
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
39607
40955
|
const installed = installedActivePluginVersion(surface);
|
|
39608
40956
|
const plan = buildPluginCachePlan(
|
|
39609
|
-
(0,
|
|
40957
|
+
(0, import_node_os20.homedir)(),
|
|
39610
40958
|
running,
|
|
39611
40959
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
39612
40960
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
39613
40961
|
);
|
|
39614
40962
|
const result = applyPluginCachePlan(
|
|
39615
40963
|
plan,
|
|
39616
|
-
(p) => (0,
|
|
40964
|
+
(p) => (0, import_node_fs48.rmSync)(p, { recursive: true }),
|
|
39617
40965
|
stagingApplyFsGuard(configRoot)
|
|
39618
40966
|
);
|
|
39619
40967
|
return {
|
|
@@ -39648,7 +40996,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39648
40996
|
// has no generated routing index would
|
|
39649
40997
|
// get a permanent — demanding an artifact it never asked for.
|
|
39650
40998
|
docsIndexState: (root) => {
|
|
39651
|
-
if (!(0,
|
|
40999
|
+
if (!(0, import_node_fs48.existsSync)((0, import_node_path45.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
39652
41000
|
const real = createDocsIndexDeps(root);
|
|
39653
41001
|
let docs;
|
|
39654
41002
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -39657,7 +41005,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39657
41005
|
},
|
|
39658
41006
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
39659
41007
|
healDocsIndex: (root) => {
|
|
39660
|
-
if (!(0,
|
|
41008
|
+
if (!(0, import_node_fs48.existsSync)((0, import_node_path45.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
39661
41009
|
const real = createDocsIndexDeps(root);
|
|
39662
41010
|
let docs;
|
|
39663
41011
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -39678,8 +41026,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39678
41026
|
});
|
|
39679
41027
|
const raced = await Promise.race([
|
|
39680
41028
|
work.then((r) => ({ ...r, timedOut: false })),
|
|
39681
|
-
new Promise((
|
|
39682
|
-
ceiling = setTimeout(() =>
|
|
41029
|
+
new Promise((resolve7) => {
|
|
41030
|
+
ceiling = setTimeout(() => resolve7({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
|
|
39683
41031
|
})
|
|
39684
41032
|
]);
|
|
39685
41033
|
if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
|
|
@@ -39712,8 +41060,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39712
41060
|
incomplete: nb.incomplete,
|
|
39713
41061
|
timedOut: false
|
|
39714
41062
|
})),
|
|
39715
|
-
new Promise((
|
|
39716
|
-
ceiling = setTimeout(() =>
|
|
41063
|
+
new Promise((resolve7) => {
|
|
41064
|
+
ceiling = setTimeout(() => resolve7({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
|
|
39717
41065
|
})
|
|
39718
41066
|
]);
|
|
39719
41067
|
if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
|
|
@@ -40228,7 +41576,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
40228
41576
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
40229
41577
|
if (o.secretsFile) {
|
|
40230
41578
|
try {
|
|
40231
|
-
vars.push(`secrets=${(0,
|
|
41579
|
+
vars.push(`secrets=${(0, import_node_fs48.readFileSync)(o.secretsFile, "utf8")}`);
|
|
40232
41580
|
} catch (e) {
|
|
40233
41581
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
40234
41582
|
}
|
|
@@ -40541,16 +41889,16 @@ function ciAuditDeps2() {
|
|
|
40541
41889
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
40542
41890
|
readSeedFile: (path2) => {
|
|
40543
41891
|
if (!root) return null;
|
|
40544
|
-
const fullPath = (0,
|
|
40545
|
-
return (0,
|
|
41892
|
+
const fullPath = (0, import_node_path45.join)(root, path2);
|
|
41893
|
+
return (0, import_node_fs48.existsSync)(fullPath) ? (0, import_node_fs48.readFileSync)(fullPath, "utf8") : null;
|
|
40546
41894
|
}
|
|
40547
41895
|
};
|
|
40548
41896
|
}
|
|
40549
41897
|
function hubRoot2() {
|
|
40550
|
-
const fromPkg = (0,
|
|
41898
|
+
const fromPkg = (0, import_node_path45.join)(__dirname, "..", "..");
|
|
40551
41899
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
40552
|
-
if ((0,
|
|
40553
|
-
if ((0,
|
|
41900
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(fromPkg, marker))) return fromPkg;
|
|
41901
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(process.cwd(), marker))) return process.cwd();
|
|
40554
41902
|
return null;
|
|
40555
41903
|
}
|
|
40556
41904
|
registerQueryCommands(program2);
|
|
@@ -40650,10 +41998,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
40650
41998
|
targets = resolution.targets;
|
|
40651
41999
|
}
|
|
40652
42000
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
40653
|
-
const fileMatrix = (0,
|
|
42001
|
+
const fileMatrix = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
40654
42002
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
40655
42003
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
40656
|
-
const sanctioned = (0,
|
|
42004
|
+
const sanctioned = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
40657
42005
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
40658
42006
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
40659
42007
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -40684,16 +42032,16 @@ function directoryBytes(path2) {
|
|
|
40684
42032
|
let total = 0;
|
|
40685
42033
|
let entries;
|
|
40686
42034
|
try {
|
|
40687
|
-
entries = (0,
|
|
42035
|
+
entries = (0, import_node_fs48.readdirSync)(path2, { withFileTypes: true });
|
|
40688
42036
|
} catch {
|
|
40689
42037
|
return 0;
|
|
40690
42038
|
}
|
|
40691
42039
|
for (const entry of entries) {
|
|
40692
|
-
const child2 = (0,
|
|
42040
|
+
const child2 = (0, import_node_path45.join)(path2, entry.name);
|
|
40693
42041
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
40694
42042
|
else {
|
|
40695
42043
|
try {
|
|
40696
|
-
total += (0,
|
|
42044
|
+
total += (0, import_node_fs48.statSync)(child2).size;
|
|
40697
42045
|
} catch {
|
|
40698
42046
|
}
|
|
40699
42047
|
}
|
|
@@ -40701,25 +42049,25 @@ function directoryBytes(path2) {
|
|
|
40701
42049
|
return total;
|
|
40702
42050
|
}
|
|
40703
42051
|
function listDirEntries(dir) {
|
|
40704
|
-
return (0,
|
|
42052
|
+
return (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
40705
42053
|
}
|
|
40706
42054
|
function readInstalledPluginRefs(configRoot) {
|
|
40707
42055
|
const p = installedPluginsPathForConfig(configRoot);
|
|
40708
|
-
if (!(0,
|
|
42056
|
+
if (!(0, import_node_fs48.existsSync)(p)) return [];
|
|
40709
42057
|
try {
|
|
40710
|
-
return installedPluginPaths((0,
|
|
42058
|
+
return installedPluginPaths((0, import_node_fs48.readFileSync)(p, "utf8"));
|
|
40711
42059
|
} catch {
|
|
40712
42060
|
return null;
|
|
40713
42061
|
}
|
|
40714
42062
|
}
|
|
40715
42063
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
40716
42064
|
return {
|
|
40717
|
-
exists: (p) => (0,
|
|
40718
|
-
listVersionDirs: (root) => (0,
|
|
42065
|
+
exists: (p) => (0, import_node_fs48.existsSync)(p),
|
|
42066
|
+
listVersionDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
40719
42067
|
dirBytes,
|
|
40720
|
-
listStagingDirs: (root) => (0,
|
|
42068
|
+
listStagingDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
40721
42069
|
try {
|
|
40722
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
42070
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path45.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs48.statSync)(p).mtimeMs) };
|
|
40723
42071
|
} catch {
|
|
40724
42072
|
return { name: d.name, mtimeMs: Date.now() };
|
|
40725
42073
|
}
|
|
@@ -40733,10 +42081,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
40733
42081
|
return {
|
|
40734
42082
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
40735
42083
|
mtimeMs: (name) => {
|
|
40736
|
-
const p = (0,
|
|
40737
|
-
if (!(0,
|
|
42084
|
+
const p = (0, import_node_path45.join)(stagingRoot, name);
|
|
42085
|
+
if (!(0, import_node_fs48.existsSync)(p)) return null;
|
|
40738
42086
|
try {
|
|
40739
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
42087
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs48.statSync)(q).mtimeMs);
|
|
40740
42088
|
} catch {
|
|
40741
42089
|
return null;
|
|
40742
42090
|
}
|
|
@@ -40756,13 +42104,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
40756
42104
|
return;
|
|
40757
42105
|
}
|
|
40758
42106
|
const plan = buildPluginCachePlan(
|
|
40759
|
-
(0,
|
|
42107
|
+
(0, import_node_os20.homedir)(),
|
|
40760
42108
|
running,
|
|
40761
42109
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
40762
42110
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
40763
42111
|
);
|
|
40764
42112
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
40765
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
42113
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs48.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
40766
42114
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
40767
42115
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
40768
42116
|
else console.log(renderPluginCachePlan(plan, result));
|