@mutmutco/cli 4.5.103 → 4.5.104
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 +85 -51
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -14908,7 +14908,7 @@ async function readDefaultBranchGate(repo, client, baseBranch, now = Date.now(),
|
|
|
14908
14908
|
async function gateIsProvenGreen(repo, client, baseBranch, gateFiles = DEFAULT_GATE_FILES) {
|
|
14909
14909
|
return (await readDefaultBranchGate(repo, client, baseBranch, Date.now(), gateFiles)).state === "green";
|
|
14910
14910
|
}
|
|
14911
|
-
async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active") {
|
|
14911
|
+
async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active", dryRun = false) {
|
|
14912
14912
|
const want = new Set(rulesetRequiredContexts({ rules: rulesetBody.rules }));
|
|
14913
14913
|
const wantStrict = rulesetStrictPolicy({ rules: rulesetBody.rules });
|
|
14914
14914
|
const wantBranches = rulesetBranchIncludes(rulesetBody);
|
|
@@ -14927,10 +14927,10 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
|
|
|
14927
14927
|
if (detail.enforcement === effective && branchesMatch && strictMatch && have.size === want.size && [...want].every((c) => have.has(c))) {
|
|
14928
14928
|
return { action: "skipped", enforcement: effective, detail: `${effective} ruleset already matches required contexts, branches and up-to-date policy${kept}` };
|
|
14929
14929
|
}
|
|
14930
|
-
await client.rest("PUT", `repos/${repo}/rulesets/${existing.id}`, { body: { ...rulesetBody, enforcement: effective }, timeoutMs: 2e4 });
|
|
14930
|
+
if (!dryRun) await client.rest("PUT", `repos/${repo}/rulesets/${existing.id}`, { body: { ...rulesetBody, enforcement: effective }, timeoutMs: 2e4 });
|
|
14931
14931
|
return { action: "updated", enforcement: effective, detail: `ruleset ${existing.id}${kept}` };
|
|
14932
14932
|
}
|
|
14933
|
-
await client.rest("POST", `repos/${repo}/rulesets`, { body: { ...rulesetBody, enforcement }, timeoutMs: 2e4 });
|
|
14933
|
+
if (!dryRun) await client.rest("POST", `repos/${repo}/rulesets`, { body: { ...rulesetBody, enforcement }, timeoutMs: 2e4 });
|
|
14934
14934
|
return { action: "created", enforcement };
|
|
14935
14935
|
}
|
|
14936
14936
|
async function gateContextEnforced(repo, rest) {
|
|
@@ -16383,7 +16383,7 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
16383
16383
|
suite,
|
|
16384
16384
|
...suite === "mm-work" && deployModel === "tenant-container" ? {
|
|
16385
16385
|
requiredBuildSecrets: ["NODE_AUTH_TOKEN=@github-packages-token"],
|
|
16386
|
-
requiredChecks: ["gate"
|
|
16386
|
+
requiredChecks: options.existing?.requiredChecks ?? ["gate"]
|
|
16387
16387
|
} : {},
|
|
16388
16388
|
repos: [`${parsedRepo.owner}/${slug}`],
|
|
16389
16389
|
branch: vars.BRANCH || (cls === "content" ? "main" : "development"),
|
|
@@ -17699,10 +17699,10 @@ var rollout_plan_default = {
|
|
|
17699
17699
|
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)."
|
|
17700
17700
|
},
|
|
17701
17701
|
baseline: {
|
|
17702
|
-
version: "4.5.
|
|
17703
|
-
tag: "v4.5.
|
|
17704
|
-
commit: "
|
|
17705
|
-
npm: "@mutmutco/cli@4.5.
|
|
17702
|
+
version: "4.5.104",
|
|
17703
|
+
tag: "v4.5.104",
|
|
17704
|
+
commit: "6bf765dc986c",
|
|
17705
|
+
npm: "@mutmutco/cli@4.5.104"
|
|
17706
17706
|
},
|
|
17707
17707
|
exitCriterion: "fleet-n-of-n",
|
|
17708
17708
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17719,14 +17719,14 @@ var rollout_plan_default = {
|
|
|
17719
17719
|
repo: "mutmutco/mmi-hub",
|
|
17720
17720
|
role: "canary",
|
|
17721
17721
|
schedule: "train",
|
|
17722
|
-
v3Target: "v4.5.
|
|
17722
|
+
v3Target: "v4.5.104"
|
|
17723
17723
|
}
|
|
17724
17724
|
],
|
|
17725
17725
|
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.",
|
|
17726
17726
|
rollback: {
|
|
17727
17727
|
independent: true,
|
|
17728
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17729
|
-
v3Target: "v4.5.
|
|
17728
|
+
mechanism: "npm dist-tag latest -> 4.5.104 and redeploy the Hub Lambda from tag v4.5.104 (6bf765dc986c); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17729
|
+
v3Target: "v4.5.104 (@mutmutco/cli@4.5.104, tag commit 6bf765dc986c \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17730
17730
|
}
|
|
17731
17731
|
},
|
|
17732
17732
|
{
|
|
@@ -28344,7 +28344,7 @@ function planBranchRequiredContexts(input) {
|
|
|
28344
28344
|
})).filter((entry) => entry.contexts.length > 0);
|
|
28345
28345
|
return { common, pending, waiting };
|
|
28346
28346
|
}
|
|
28347
|
-
async function branchContextPlan(deps, repo, authority, meta) {
|
|
28347
|
+
async function branchContextPlan(deps, repo, authority, meta, emittedBranchesOnly = false) {
|
|
28348
28348
|
const branches = resolveRequiredCheckBranches(meta, repo);
|
|
28349
28349
|
const emittedByBranch = {};
|
|
28350
28350
|
const unreadable = [];
|
|
@@ -28354,7 +28354,10 @@ async function branchContextPlan(deps, repo, authority, meta) {
|
|
|
28354
28354
|
emittedByBranch[branch] = await resolveEmittedPrContexts(deps, repo, branch);
|
|
28355
28355
|
if (presence === void 0 || emittedByBranch[branch] === void 0) unreadable.push(branch);
|
|
28356
28356
|
}
|
|
28357
|
-
|
|
28357
|
+
const bindingBranches = emittedBranchesOnly ? branches.filter((branch) => authority.contexts.some((context) => emittedByBranch[branch]?.includes(context))) : branches;
|
|
28358
|
+
const plan = planBranchRequiredContexts({ contexts: authority.contexts, branches: bindingBranches, emittedByBranch });
|
|
28359
|
+
const waiting = planBranchRequiredContexts({ contexts: authority.contexts, branches, emittedByBranch }).waiting;
|
|
28360
|
+
return { ...plan, waiting, unreadable, bindingBranches };
|
|
28358
28361
|
}
|
|
28359
28362
|
function projectCommonAuthority(authority, common) {
|
|
28360
28363
|
const apiPayload = patchRulesetRequiredContexts(authority.apiPayload, common);
|
|
@@ -28608,8 +28611,8 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28608
28611
|
checks.push({
|
|
28609
28612
|
ok: pendingAligned,
|
|
28610
28613
|
label: "branch-specific required checks match workflow emitters",
|
|
28611
|
-
detail: pendingAligned ? void 0 :
|
|
28612
|
-
remediation: pendingAligned ? void 0 : `mmi-cli devops
|
|
28614
|
+
detail: pendingAligned ? void 0 : `pending ruleset contexts or branch conditions have not converged to the emitted registry requirements \u2014 run mmi-cli devops bootstrap apply ${repo} --only product-ruleset --execute`,
|
|
28615
|
+
remediation: pendingAligned ? void 0 : `mmi-cli devops bootstrap apply ${repo} --only product-ruleset --execute`
|
|
28613
28616
|
});
|
|
28614
28617
|
}
|
|
28615
28618
|
}
|
|
@@ -28641,7 +28644,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28641
28644
|
checks.push({
|
|
28642
28645
|
ok: aligned,
|
|
28643
28646
|
label: RULESET_REFERENCE_MATCH_LABEL,
|
|
28644
|
-
detail: aligned ? `contexts [${authoritativeRuleset.contexts.join(", ")}], branches [${authoritativeRuleset.branchIncludes.join(", ")}] and the up-to-date policy match declared authority` : `${authoritativeRuleset.source} requires contexts [${authoritativeRuleset.contexts.join(", ")}]; ${authoritativeRuleset.branchSource} requires branches [${authoritativeRuleset.branchIncludes.join(", ")}]; ${PRODUCT_RULESET_REF} declares contexts [${authoritativeRuleset.committedContexts.join(", ")}] / branches [${authoritativeRuleset.committedBranchIncludes.join(", ")}] and live ${PRODUCT_RULESET_NAME} has contexts [${liveContexts.join(", ")}] / branches [${liveBranchIncludes.join(", ")}]` + (strictAligned ? "" : `; strict_required_status_checks_policy differs from committed policy \u2014 ${PRODUCT_RULESET_REF} declares ${authoritativeRuleset.committedStrict}, live ${PRODUCT_RULESET_NAME} has ${liveStrict}`)
|
|
28647
|
+
detail: aligned ? `contexts [${authoritativeRuleset.contexts.join(", ")}], branches [${authoritativeRuleset.branchIncludes.join(", ")}] and the up-to-date policy match declared authority` : `${authoritativeRuleset.source} requires contexts [${authoritativeRuleset.contexts.join(", ")}]; ${authoritativeRuleset.branchSource} requires branches [${authoritativeRuleset.branchIncludes.join(", ")}]; ${PRODUCT_RULESET_REF} declares contexts [${authoritativeRuleset.committedContexts.join(", ")}] / branches [${authoritativeRuleset.committedBranchIncludes.join(", ")}] and live ${PRODUCT_RULESET_NAME} has contexts [${liveContexts.join(", ")}] / branches [${liveBranchIncludes.join(", ")}]` + (strictAligned ? "" : `; strict_required_status_checks_policy differs from committed policy \u2014 ${PRODUCT_RULESET_REF} declares ${authoritativeRuleset.committedStrict}, live ${PRODUCT_RULESET_NAME} has ${liveStrict}`) + ` \u2014 live repair: mmi-cli devops bootstrap apply ${repo} --only product-ruleset --execute`,
|
|
28645
28648
|
remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
|
|
28646
28649
|
});
|
|
28647
28650
|
}
|
|
@@ -29184,9 +29187,14 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta)
|
|
|
29184
29187
|
}
|
|
29185
29188
|
return null;
|
|
29186
29189
|
}
|
|
29187
|
-
async function applyCiReconcileRepo(repo, deps) {
|
|
29190
|
+
async function applyCiReconcileRepo(repo, deps, options = {}) {
|
|
29191
|
+
if (options.dryRun && !options.productRulesetOnly) throw new Error("dry-run requires productRulesetOnly");
|
|
29188
29192
|
const meta = await deps.getProjectMeta(slugFromRepo(repo));
|
|
29193
|
+
if (options.productRulesetOnly && !meta?.requiredChecks?.length) {
|
|
29194
|
+
return { repo, applied: [], skipped: [], errors: ["product-ruleset requires registry META requiredChecks"] };
|
|
29195
|
+
}
|
|
29189
29196
|
const report = await auditRepoCi(repo, deps);
|
|
29197
|
+
const finish = (result2, pendingReason) => options.productRulesetOnly ? Promise.resolve(result2) : finalizeCiReconcile(repo, deps, result2, report, pendingReason);
|
|
29190
29198
|
const baseBranch = "development";
|
|
29191
29199
|
let raw = report.class === "deployable" && !report.explicitNoCi ? await fetchRulesetSeedBody(deps, repo) : null;
|
|
29192
29200
|
let authority = null;
|
|
@@ -29196,12 +29204,12 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29196
29204
|
authority = parseAuthoritativeRuleset(raw, meta, repo);
|
|
29197
29205
|
} catch (e) {
|
|
29198
29206
|
empty.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
29199
|
-
return
|
|
29207
|
+
return finish(empty);
|
|
29200
29208
|
}
|
|
29201
29209
|
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
29202
29210
|
if (hold) {
|
|
29203
29211
|
empty.skipped.push(hold);
|
|
29204
|
-
return
|
|
29212
|
+
return finish(empty, hold);
|
|
29205
29213
|
}
|
|
29206
29214
|
} else if (report.class === "deployable" && !report.explicitNoCi) {
|
|
29207
29215
|
const registryAuthority = registryAuthorityWithoutReference(meta, repo);
|
|
@@ -29209,59 +29217,64 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29209
29217
|
const hold = await authoritativeSafetyHold(registryAuthority, deps, repo, baseBranch, meta);
|
|
29210
29218
|
if (hold) {
|
|
29211
29219
|
empty.skipped.push(hold);
|
|
29212
|
-
return
|
|
29220
|
+
return finish(empty, hold);
|
|
29213
29221
|
}
|
|
29214
29222
|
}
|
|
29215
29223
|
}
|
|
29216
|
-
const result = await applyCiReconcileMergeSettingsFromReport(repo, deps, report);
|
|
29224
|
+
const result = options.productRulesetOnly ? empty : await applyCiReconcileMergeSettingsFromReport(repo, deps, report);
|
|
29217
29225
|
if (report.class !== "deployable" || report.explicitNoCi) {
|
|
29218
|
-
return
|
|
29226
|
+
return finish(result);
|
|
29219
29227
|
}
|
|
29220
|
-
const refDelivery = await seedGateYml(repo, deps, meta, result);
|
|
29228
|
+
const refDelivery = options.productRulesetOnly ? void 0 : await seedGateYml(repo, deps, meta, result);
|
|
29221
29229
|
if (refDelivery === "pending") {
|
|
29222
29230
|
const reason = `${PRODUCT_RULESET_REF} is pending on its protected-base source PR; live ruleset convergence waits for that source to merge`;
|
|
29223
29231
|
result.skipped.push(reason);
|
|
29224
|
-
return
|
|
29232
|
+
return finish(result, reason);
|
|
29225
29233
|
}
|
|
29226
29234
|
raw ??= await fetchRulesetSeedBody(deps, repo);
|
|
29227
29235
|
if (!raw) {
|
|
29228
29236
|
result.errors.push(`missing ${PRODUCT_RULESET_REF} on development \u2014 run bootstrap apply first`);
|
|
29229
|
-
return
|
|
29237
|
+
return finish(result);
|
|
29230
29238
|
}
|
|
29231
29239
|
if (authority == null) {
|
|
29232
29240
|
try {
|
|
29233
29241
|
authority = parseAuthoritativeRuleset(raw, meta, repo);
|
|
29234
29242
|
} catch (e) {
|
|
29235
29243
|
result.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
29236
|
-
return
|
|
29244
|
+
return finish(result);
|
|
29237
29245
|
}
|
|
29238
29246
|
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
29239
29247
|
if (hold) {
|
|
29240
29248
|
result.skipped.push(hold);
|
|
29241
|
-
return
|
|
29249
|
+
return finish(result, hold);
|
|
29242
29250
|
}
|
|
29243
29251
|
}
|
|
29244
29252
|
if (authority.contexts.length === 0) {
|
|
29245
29253
|
result.errors.push(`${authority.source} declares no required contexts`);
|
|
29246
|
-
return
|
|
29254
|
+
return finish(result);
|
|
29247
29255
|
}
|
|
29248
29256
|
let pendingPlan = null;
|
|
29249
29257
|
const declaredContexts = authority.contexts;
|
|
29250
29258
|
if (authority.source === "registry META requiredChecks") {
|
|
29251
|
-
pendingPlan = await branchContextPlan(deps, repo, authority, meta);
|
|
29259
|
+
pendingPlan = await branchContextPlan(deps, repo, authority, meta, options.productRulesetOnly);
|
|
29252
29260
|
if (pendingPlan.unreadable.length) {
|
|
29253
29261
|
const reason = `required branch workflow emitters unreadable on [${pendingPlan.unreadable.join(", ")}] \u2014 ruleset convergence deferred`;
|
|
29254
29262
|
result.skipped.push(reason);
|
|
29255
|
-
return
|
|
29263
|
+
return finish(result, reason);
|
|
29256
29264
|
}
|
|
29257
29265
|
if (pendingPlan.common.length === 0) {
|
|
29258
29266
|
result.errors.push("no common required context is emitted by every required branch \u2014 ruleset convergence deferred");
|
|
29259
|
-
return
|
|
29267
|
+
return finish(result);
|
|
29260
29268
|
}
|
|
29261
29269
|
authority = projectCommonAuthority(authority, pendingPlan.common);
|
|
29270
|
+
if (options.productRulesetOnly) {
|
|
29271
|
+
const apiPayload = patchRulesetBranchIncludes(authority.apiPayload, pendingPlan.bindingBranches);
|
|
29272
|
+
authority = { ...authority, apiPayload, branchIncludes: rulesetBranchIncludes(apiPayload) };
|
|
29273
|
+
for (const entry of pendingPlan.waiting) result.skipped.push(`${entry.branch} waits for workflow emitter(s) [${entry.contexts.join(", ")}]`);
|
|
29274
|
+
}
|
|
29262
29275
|
}
|
|
29263
29276
|
const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes) || !authority.committedStrict;
|
|
29264
|
-
if (sourceNeedsConvergence) {
|
|
29277
|
+
if (sourceNeedsConvergence && !options.productRulesetOnly) {
|
|
29265
29278
|
try {
|
|
29266
29279
|
const delivery = await deliverSeedFile(
|
|
29267
29280
|
deps,
|
|
@@ -29278,15 +29291,15 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29278
29291
|
`${PRODUCT_RULESET_REF} authority \u2192 contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}] delivered on ${delivery.plan.branch} (${delivery.plan.reason})${delivery.prUrl ? `: ${delivery.prUrl}` : ""}`
|
|
29279
29292
|
);
|
|
29280
29293
|
const reason = `${PRODUCT_RULESET_REF} authority is pending on its protected-base source PR; live ruleset convergence waits for that source to merge`;
|
|
29281
|
-
return
|
|
29294
|
+
return finish(result, reason);
|
|
29282
29295
|
} else {
|
|
29283
29296
|
result.applied.push(`reconciled ${PRODUCT_RULESET_REF} authority \u2192 contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}]`);
|
|
29284
29297
|
}
|
|
29285
29298
|
} catch (e) {
|
|
29286
29299
|
result.errors.push(`ruleset reference delivery failed: ${e.message}`);
|
|
29287
|
-
return
|
|
29300
|
+
return finish(result);
|
|
29288
29301
|
}
|
|
29289
|
-
} else {
|
|
29302
|
+
} else if (!options.productRulesetOnly) {
|
|
29290
29303
|
result.skipped.push(`${PRODUCT_RULESET_REF} already matches declared context and branch authority`);
|
|
29291
29304
|
}
|
|
29292
29305
|
let live;
|
|
@@ -29294,7 +29307,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29294
29307
|
live = await readLiveProductRuleset(deps, repo);
|
|
29295
29308
|
} catch (e) {
|
|
29296
29309
|
result.errors.push(`cannot read the live ${PRODUCT_RULESET_NAME} ruleset: ${e.message}`);
|
|
29297
|
-
return
|
|
29310
|
+
return finish(result);
|
|
29298
29311
|
}
|
|
29299
29312
|
const liveContexts = live == null ? [] : sortedUnique(rulesetRequiredContexts(live));
|
|
29300
29313
|
const liveBranchIncludes = live == null ? [] : rulesetBranchIncludes(live);
|
|
@@ -29312,7 +29325,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29312
29325
|
if (pendingPlan?.pending.some((entry) => entry.contexts.some((context) => unsafe.includes(context)))) {
|
|
29313
29326
|
const reason = `pending required-check rulesets left non-enforcing \u2014 [${unsafe.join(", ")}] emitted only by path-filtered workflows`;
|
|
29314
29327
|
result.skipped.push(reason);
|
|
29315
|
-
return
|
|
29328
|
+
return finish(result, reason);
|
|
29316
29329
|
}
|
|
29317
29330
|
const gateProvenGreen = await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles);
|
|
29318
29331
|
const plan = resolveProductRulesetReconcilePlan({
|
|
@@ -29329,25 +29342,25 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29329
29342
|
if (!plan.shouldActivate) {
|
|
29330
29343
|
if (plan.holdReason) {
|
|
29331
29344
|
result.skipped.push(plan.holdReason);
|
|
29332
|
-
return
|
|
29345
|
+
return finish(result, plan.holdReason);
|
|
29333
29346
|
}
|
|
29334
29347
|
result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts, branches and up-to-date policy already match declared authority`);
|
|
29335
29348
|
}
|
|
29336
29349
|
if (plan.shouldActivate) {
|
|
29337
29350
|
try {
|
|
29338
|
-
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
|
|
29351
|
+
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement, options.dryRun);
|
|
29339
29352
|
if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
|
|
29340
|
-
else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
29353
|
+
else result.applied.push(`product ruleset ${activation.action} (${activation.enforcement}; contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}])${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
29341
29354
|
} catch (e) {
|
|
29342
29355
|
result.errors.push(e.message);
|
|
29343
|
-
return
|
|
29356
|
+
return finish(result);
|
|
29344
29357
|
}
|
|
29345
29358
|
}
|
|
29346
29359
|
if (pendingPlan) {
|
|
29347
29360
|
if (pendingPlan.pending.length > 0 && !gateProvenGreen) {
|
|
29348
29361
|
const reason = "pending required-check rulesets left non-enforcing \u2014 the gate is not green on development";
|
|
29349
29362
|
result.skipped.push(reason);
|
|
29350
|
-
return
|
|
29363
|
+
return finish(result, reason);
|
|
29351
29364
|
}
|
|
29352
29365
|
try {
|
|
29353
29366
|
const desired = /* @__PURE__ */ new Set();
|
|
@@ -29363,20 +29376,20 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29363
29376
|
patchRulesetRequiredContexts(statusOnly, entry.contexts),
|
|
29364
29377
|
[entry.branch]
|
|
29365
29378
|
);
|
|
29366
|
-
const activation = await activateProductRuleset(repo, payload, deps.client);
|
|
29367
|
-
(activation.action === "skipped" ? result.skipped : result.applied).push(`${name}: ${activation.action}`);
|
|
29379
|
+
const activation = await activateProductRuleset(repo, payload, deps.client, "active", options.dryRun);
|
|
29380
|
+
(activation.action === "skipped" ? result.skipped : result.applied).push(`${name}: ${activation.action} \u2014 contexts [${entry.contexts.join(", ")}]`);
|
|
29368
29381
|
}
|
|
29369
29382
|
const list = await deps.client.rest("GET", `repos/${repo}/rulesets`);
|
|
29370
29383
|
for (const stale of list ?? []) {
|
|
29371
29384
|
if (!stale.name?.startsWith(PRODUCT_PENDING_RULESET_PREFIX) || desired.has(stale.name) || stale.id == null) continue;
|
|
29372
|
-
await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
|
|
29385
|
+
if (!options.dryRun) await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
|
|
29373
29386
|
result.applied.push(`deleted converged ${stale.name}`);
|
|
29374
29387
|
}
|
|
29375
29388
|
} catch (e) {
|
|
29376
29389
|
result.errors.push(`pending ruleset convergence failed: ${e.message}`);
|
|
29377
29390
|
}
|
|
29378
29391
|
}
|
|
29379
|
-
return
|
|
29392
|
+
return finish(result);
|
|
29380
29393
|
}
|
|
29381
29394
|
|
|
29382
29395
|
// src/command-composition.ts
|
|
@@ -34433,8 +34446,8 @@ function suiteModulePackageAccessCheck(suite, deployModel) {
|
|
|
34433
34446
|
if (suite !== "mm-work" || deployModel !== "tenant-container") return void 0;
|
|
34434
34447
|
return {
|
|
34435
34448
|
ok: true,
|
|
34436
|
-
label: "mmwork-ds package access (manual org-admin step; unverified)",
|
|
34437
|
-
detail:
|
|
34449
|
+
label: "mmwork-ds + mmwork-screens package access (manual org-admin step; unverified)",
|
|
34450
|
+
detail: `An org admin must add this repository with Read under BOTH @mutmutco/mmwork-ds and @mutmutco/mmwork-screens \u2192 Package settings \u2192 Manage Actions access (#7984). No GitHub REST API can verify or grant this; without it the image build or the screens job fails with 403 read_package. Once screens is green, require it: mmi-cli oracle org project set <owner/repo> --var 'requiredChecks=["gate","screens / screens"]'.`
|
|
34438
34451
|
};
|
|
34439
34452
|
}
|
|
34440
34453
|
function attachTrainDoctorVerdicts(report, trainDoctors) {
|
|
@@ -34830,7 +34843,7 @@ function registerBootstrapCommands(program3) {
|
|
|
34830
34843
|
}
|
|
34831
34844
|
if (findings.some((f) => f.state !== "waived")) process.exitCode = 1;
|
|
34832
34845
|
});
|
|
34833
|
-
bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").addOption(new Option("--class <class>", "deployable | content").default("deployable").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--box <name>", "resolve this live box and plan free registry DEPLOY# ports (container deploy models)").option("--domains <stages>", "optional comma-separated dev=host,rc=host,main=host deploy domains (requires --box)").option("--only <target>", "deliver ONLY this manifest target (#3818) \u2014 one file, no labels/ruleset/registry writes; or the special
|
|
34846
|
+
bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").addOption(new Option("--class <class>", "deployable | content").default("deployable").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--box <name>", "resolve this live box and plan free registry DEPLOY# ports (container deploy models)").option("--domains <stages>", "optional comma-separated dev=host,rc=host,main=host deploy domains (requires --box)").option("--only <target>", "deliver ONLY this manifest target (#3818) \u2014 one file, no labels/ruleset/registry writes; or the special targets environments (#5035) / product-ruleset (#7986) to reconcile only that live surface (dry-run classifies, --execute writes)").option("--from-config", "with --only gate.yml: adopt an equal legacy gate with a render stamp, then update from registry config via review PR; refuse hand edits").option("--var <KEY=VALUE...>", "placeholder values for repo-owned templates (repeatable)").option("--json", "machine-readable output").action(async (repo, cmdOpts) => {
|
|
34834
34847
|
const o = {
|
|
34835
34848
|
class: rawValue("--class", "deployable"),
|
|
34836
34849
|
projectType: rawValue("--project-type", ""),
|
|
@@ -34917,6 +34930,26 @@ function registerBootstrapCommands(program3) {
|
|
|
34917
34930
|
}
|
|
34918
34931
|
return;
|
|
34919
34932
|
}
|
|
34933
|
+
if (o.only.trim() === "product-ruleset") {
|
|
34934
|
+
if (cmdOpts.var?.length) return fail("bootstrap apply: --only product-ruleset accepts no --var overrides; set registry META instead");
|
|
34935
|
+
const read = await fetchProjectBySlugChecked(parsedRepo.slug, registryClientDeps(await loadConfig()));
|
|
34936
|
+
if (!read.ok) return failGraceful(`bootstrap apply: registry META unreadable (${read.error}); refusing product ruleset reconciliation`);
|
|
34937
|
+
if (!read.project?.requiredChecks?.length) return failGraceful("bootstrap apply: --only product-ruleset requires registry META requiredChecks");
|
|
34938
|
+
const result = await applyCiReconcileRepo(repo, {
|
|
34939
|
+
client: defaultGitHubClient(),
|
|
34940
|
+
listProjects: async () => [],
|
|
34941
|
+
getProjectMeta: async () => read.project
|
|
34942
|
+
}, { productRulesetOnly: true, dryRun: !o.execute });
|
|
34943
|
+
if (o.json) console.log(JSON.stringify(result, null, 2));
|
|
34944
|
+
else {
|
|
34945
|
+
console.log(`bootstrap apply --only product-ruleset \u2014 ${o.execute ? "LIVE" : "dry-run; no writes"} (${repo}):`);
|
|
34946
|
+
result.applied.forEach((line) => console.log(` ${o.execute ? "APPLY" : "PLAN"} ${line}`));
|
|
34947
|
+
result.skipped.forEach((line) => console.log(` SKIP ${line}`));
|
|
34948
|
+
result.errors.forEach((line) => console.error(` REFUSED ${line}`));
|
|
34949
|
+
}
|
|
34950
|
+
if (result.errors.length) return failGraceful("bootstrap apply: product ruleset reconciliation failed");
|
|
34951
|
+
return;
|
|
34952
|
+
}
|
|
34920
34953
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
34921
34954
|
if (!(0, import_node_fs28.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`);
|
|
34922
34955
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
@@ -35003,9 +35036,10 @@ function registerBootstrapCommands(program3) {
|
|
|
35003
35036
|
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
35004
35037
|
if (vars.SUITE === "mm-work" || registryMeta?.suite === "mm-work") {
|
|
35005
35038
|
if (applyDeployModel === "tenant-container") {
|
|
35006
|
-
const
|
|
35039
|
+
const requiredChecks = registryMeta?.requiredChecks ?? ["gate"];
|
|
35040
|
+
const moduleGate = withDerivedRepoVars({}, parsedRepo, o.class, effectiveTrack, registryRequiredCheckBranches, requiredChecks);
|
|
35007
35041
|
vars.GATE_RULESET_CONTEXTS_JSON = moduleGate.GATE_RULESET_CONTEXTS_JSON;
|
|
35008
|
-
if (registryMeta) registryMeta = { ...registryMeta, requiredChecks
|
|
35042
|
+
if (registryMeta) registryMeta = { ...registryMeta, requiredChecks };
|
|
35009
35043
|
}
|
|
35010
35044
|
}
|
|
35011
35045
|
const controlClient = defaultGitHubClient();
|
package/package.json
CHANGED