@mutmutco/cli 4.5.102 → 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 +201 -61
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -6053,6 +6053,16 @@ function isRetryableGitHubWaitReadError(e) {
|
|
|
6053
6053
|
}
|
|
6054
6054
|
return /Something went wrong while executing your query/.test(text) || /^\s*unexpected end of JSON input\s*$/m.test(text);
|
|
6055
6055
|
}
|
|
6056
|
+
function refusalWaitMs(e, nowMs) {
|
|
6057
|
+
const err = e;
|
|
6058
|
+
const text = [err?.stdout, err?.stderr, e instanceof Error ? e.message : ""].filter((t) => typeof t === "string").join("\n");
|
|
6059
|
+
const header = (name) => new RegExp(`^${name}:\\s*(\\d+)\\s*$`, "im").exec(text)?.[1];
|
|
6060
|
+
const retryAfter = header("retry-after");
|
|
6061
|
+
if (retryAfter !== void 0) return Number(retryAfter) * 1e3;
|
|
6062
|
+
const reset = header("x-ratelimit-remaining") === "0" ? Number(header("x-ratelimit-reset")) : typeof err?.rateLimitReset === "number" ? err.rateLimitReset : NaN;
|
|
6063
|
+
if (reset * 1e3 > nowMs) return reset * 1e3 - nowMs;
|
|
6064
|
+
return /secondary rate|abuse detection/i.test(text) ? 6e4 : void 0;
|
|
6065
|
+
}
|
|
6056
6066
|
function waitReadFailureMessage(e) {
|
|
6057
6067
|
const err = e;
|
|
6058
6068
|
const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
|
|
@@ -6180,9 +6190,21 @@ async function waitForPrChecks(deps) {
|
|
|
6180
6190
|
return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed", waitedMs: now() - started };
|
|
6181
6191
|
}
|
|
6182
6192
|
lastDetail = `github-read-transient (${waitReadFailureMessage(e)})`;
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6193
|
+
const askedMs = refusalWaitMs(e, now());
|
|
6194
|
+
const jitter = deps.jitterMs ?? (() => Math.floor(Math.random() * PR_CHECKS_RATELIMIT_JITTER_MAX_MS));
|
|
6195
|
+
const waitMs = askedMs !== void 0 && askedMs > PR_CHECKS_POLL_MS ? askedMs + jitter() : PR_CHECKS_POLL_MS;
|
|
6196
|
+
if (askedMs !== void 0 && now() + waitMs >= deadline) {
|
|
6197
|
+
return {
|
|
6198
|
+
policy,
|
|
6199
|
+
status: "rate-limited",
|
|
6200
|
+
reason: `GitHub refused the read for rate limit and asked for ${Math.round(askedMs / 1e3)}s, past the wait deadline (${lastDetail})`,
|
|
6201
|
+
detail: "rate-limited",
|
|
6202
|
+
waitedMs: now() - started
|
|
6203
|
+
};
|
|
6204
|
+
}
|
|
6205
|
+
deps.log?.(`transient GitHub read failure \u2014 retrying in ${Math.round(waitMs / 1e3)}s within the wait budget: ${lastDetail}`);
|
|
6206
|
+
report(askedMs !== void 0 ? "rate-limited" : "pending");
|
|
6207
|
+
await deps.sleep(waitMs);
|
|
6186
6208
|
continue;
|
|
6187
6209
|
}
|
|
6188
6210
|
report(state);
|
|
@@ -6300,6 +6322,7 @@ function setPollTokenProvider(provider) {
|
|
|
6300
6322
|
pollTokenOnce = void 0;
|
|
6301
6323
|
resetAppPollGuard();
|
|
6302
6324
|
lastPollSource = void 0;
|
|
6325
|
+
observedPool.clear();
|
|
6303
6326
|
}
|
|
6304
6327
|
async function pollToken() {
|
|
6305
6328
|
if (!pollTokenProvider) return void 0;
|
|
@@ -6351,6 +6374,19 @@ function appPoolDetail() {
|
|
|
6351
6374
|
const counts = guard.remaining !== void 0 && guard.limit !== void 0 ? `App pool at ${guard.remaining}/${guard.limit}` : "App pool headroom unverified";
|
|
6352
6375
|
return guard.resetAtMs !== void 0 ? `${counts}, resets ${new Date(guard.resetAtMs).toISOString()}` : counts;
|
|
6353
6376
|
}
|
|
6377
|
+
var observedPool = /* @__PURE__ */ new Map();
|
|
6378
|
+
var OBSERVED_POOL_MAX_AGE_MS = 3 * 6e4;
|
|
6379
|
+
function notePoolHeaders(source, out, nowMs) {
|
|
6380
|
+
const { remaining, resetSec } = parseRateLimitHeaders(out);
|
|
6381
|
+
const resource = /^x-ratelimit-resource:\s*(\S+)\s*$/im.exec(out)?.[1];
|
|
6382
|
+
if (remaining !== void 0 && resetSec !== void 0 && (resource ?? "core") === "core") {
|
|
6383
|
+
observedPool.set(source, { remaining, reset: resetSec, atMs: nowMs });
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
function observedCorePool(source, nowMs) {
|
|
6387
|
+
const seen = source ? observedPool.get(source) : void 0;
|
|
6388
|
+
return seen && nowMs - seen.atMs <= OBSERVED_POOL_MAX_AGE_MS && seen.reset * 1e3 > nowMs ? { remaining: seen.remaining, reset: seen.reset } : void 0;
|
|
6389
|
+
}
|
|
6354
6390
|
async function defaultGhApi(args) {
|
|
6355
6391
|
const appToken = await pollToken();
|
|
6356
6392
|
let source = appToken && appPollAllowsApp(Date.now()) ? "app" : "user";
|
|
@@ -6372,6 +6408,7 @@ async function defaultGhApi(args) {
|
|
|
6372
6408
|
const out = e.stdout;
|
|
6373
6409
|
const errOut = typeof out === "string" ? out : void 0;
|
|
6374
6410
|
const status = errOut !== void 0 ? parseIncludedOutput(errOut).status : void 0;
|
|
6411
|
+
if (errOut !== void 0) notePoolHeaders(source, errOut, Date.now());
|
|
6375
6412
|
if (cached && status === 304) {
|
|
6376
6413
|
lastPollSource = source;
|
|
6377
6414
|
return cached.body;
|
|
@@ -6386,6 +6423,7 @@ async function defaultGhApi(args) {
|
|
|
6386
6423
|
throw e;
|
|
6387
6424
|
}
|
|
6388
6425
|
const { body, etag, pages } = parseIncludedOutput(stdout);
|
|
6426
|
+
notePoolHeaders(source, stdout, Date.now());
|
|
6389
6427
|
if (source === "app") noteAppPollResponse(parseRateLimitHeaders(stdout), false, Date.now());
|
|
6390
6428
|
lastPollSource = source;
|
|
6391
6429
|
if (etag && pages === 1) etagCache.set(key, { etag, body });
|
|
@@ -6858,7 +6896,9 @@ async function fetchRequiredCheckContexts(repo, branch, gh2 = defaultGhApi) {
|
|
|
6858
6896
|
}
|
|
6859
6897
|
return contexts;
|
|
6860
6898
|
}
|
|
6861
|
-
async function fetchRestCorePool(gh2 = defaultGhApi) {
|
|
6899
|
+
async function fetchRestCorePool(gh2 = defaultGhApi, nowMs = Date.now()) {
|
|
6900
|
+
const seen = gh2 === defaultGhApi ? observedCorePool(await pollToken() && appPollAllowsApp(nowMs) ? "app" : "user", nowMs) : void 0;
|
|
6901
|
+
if (seen) return { state: "ok", ...seen };
|
|
6862
6902
|
let core;
|
|
6863
6903
|
try {
|
|
6864
6904
|
core = JSON.parse(await gh2(["rate_limit"])).resources?.core;
|
|
@@ -14868,7 +14908,7 @@ async function readDefaultBranchGate(repo, client, baseBranch, now = Date.now(),
|
|
|
14868
14908
|
async function gateIsProvenGreen(repo, client, baseBranch, gateFiles = DEFAULT_GATE_FILES) {
|
|
14869
14909
|
return (await readDefaultBranchGate(repo, client, baseBranch, Date.now(), gateFiles)).state === "green";
|
|
14870
14910
|
}
|
|
14871
|
-
async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active") {
|
|
14911
|
+
async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active", dryRun = false) {
|
|
14872
14912
|
const want = new Set(rulesetRequiredContexts({ rules: rulesetBody.rules }));
|
|
14873
14913
|
const wantStrict = rulesetStrictPolicy({ rules: rulesetBody.rules });
|
|
14874
14914
|
const wantBranches = rulesetBranchIncludes(rulesetBody);
|
|
@@ -14887,10 +14927,10 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
|
|
|
14887
14927
|
if (detail.enforcement === effective && branchesMatch && strictMatch && have.size === want.size && [...want].every((c) => have.has(c))) {
|
|
14888
14928
|
return { action: "skipped", enforcement: effective, detail: `${effective} ruleset already matches required contexts, branches and up-to-date policy${kept}` };
|
|
14889
14929
|
}
|
|
14890
|
-
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 });
|
|
14891
14931
|
return { action: "updated", enforcement: effective, detail: `ruleset ${existing.id}${kept}` };
|
|
14892
14932
|
}
|
|
14893
|
-
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 });
|
|
14894
14934
|
return { action: "created", enforcement };
|
|
14895
14935
|
}
|
|
14896
14936
|
async function gateContextEnforced(repo, rest) {
|
|
@@ -16126,6 +16166,39 @@ function gateConfigToVars(gate) {
|
|
|
16126
16166
|
if (extras.steps?.length) out.GATE_EXTRA_STEPS_YAML = gateExtraStepsYaml(extras.steps);
|
|
16127
16167
|
return out;
|
|
16128
16168
|
}
|
|
16169
|
+
function suiteModuleSeeds(seeds, suite, deployModel) {
|
|
16170
|
+
if (suite !== "mm-work" || deployModel !== "tenant-container") return seeds;
|
|
16171
|
+
const replacements = {
|
|
16172
|
+
Dockerfile: "suite-Dockerfile.template",
|
|
16173
|
+
"docker-compose.yml": "suite-compose.template.yml",
|
|
16174
|
+
".github/workflows/gate.yml": "suite-gate.template.yml"
|
|
16175
|
+
};
|
|
16176
|
+
const additions = {
|
|
16177
|
+
"src/index.ts": "suite-server.template.ts",
|
|
16178
|
+
"package.json": "suite-package.template.json",
|
|
16179
|
+
"package-lock.json": "suite-package-lock.template.json",
|
|
16180
|
+
"tsconfig.json": "suite-tsconfig.template.json",
|
|
16181
|
+
"module/package.json": "suite-module-package.template.json",
|
|
16182
|
+
"module/package-lock.json": "suite-module-package-lock.template.json",
|
|
16183
|
+
"module/.npmrc": "suite-npmrc.template",
|
|
16184
|
+
"module/src/module.jsx": "suite-module.template.jsx",
|
|
16185
|
+
"module/vite.config.mjs": "suite-vite.template.mjs",
|
|
16186
|
+
"module/screens/screens.json": "suite-screens.template.json",
|
|
16187
|
+
"module/screens/files.json": "suite-files.template.json",
|
|
16188
|
+
".github/workflows/screens.yml": "suite-screens.template.yml",
|
|
16189
|
+
"docs/Guides/mm-work-module.md": "suite-guide.template.md"
|
|
16190
|
+
};
|
|
16191
|
+
return [
|
|
16192
|
+
...seeds.filter((s) => s.target !== "docs/Guides/google-login.md").map((s) => replacements[s.target] ? { ...s, source: `seed:${replacements[s.target]}` } : s),
|
|
16193
|
+
...Object.entries(additions).map(([target, source]) => ({
|
|
16194
|
+
target,
|
|
16195
|
+
source: `seed:${source}`,
|
|
16196
|
+
ownership: "repo",
|
|
16197
|
+
classes: ["deployable"],
|
|
16198
|
+
deployModels: ["tenant-container"]
|
|
16199
|
+
}))
|
|
16200
|
+
];
|
|
16201
|
+
}
|
|
16129
16202
|
function seedMatchesDeployModel(seed, deployModel) {
|
|
16130
16203
|
if (!seed.deployModels?.length) return true;
|
|
16131
16204
|
return deployModel != null && seed.deployModels.includes(deployModel);
|
|
@@ -16307,7 +16380,11 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
16307
16380
|
// Identity. name/division default off the repo name when the skill didn't pass them.
|
|
16308
16381
|
name: vars.NAME || parsedRepo.name,
|
|
16309
16382
|
division: vars.DIVISION || parsedRepo.name.split("-")[0] || void 0,
|
|
16310
|
-
suite
|
|
16383
|
+
suite,
|
|
16384
|
+
...suite === "mm-work" && deployModel === "tenant-container" ? {
|
|
16385
|
+
requiredBuildSecrets: ["NODE_AUTH_TOKEN=@github-packages-token"],
|
|
16386
|
+
requiredChecks: options.existing?.requiredChecks ?? ["gate"]
|
|
16387
|
+
} : {},
|
|
16311
16388
|
repos: [`${parsedRepo.owner}/${slug}`],
|
|
16312
16389
|
branch: vars.BRANCH || (cls === "content" ? "main" : "development"),
|
|
16313
16390
|
class: cls,
|
|
@@ -17622,10 +17699,10 @@ var rollout_plan_default = {
|
|
|
17622
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)."
|
|
17623
17700
|
},
|
|
17624
17701
|
baseline: {
|
|
17625
|
-
version: "4.5.
|
|
17626
|
-
tag: "v4.5.
|
|
17627
|
-
commit: "
|
|
17628
|
-
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"
|
|
17629
17706
|
},
|
|
17630
17707
|
exitCriterion: "fleet-n-of-n",
|
|
17631
17708
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17642,14 +17719,14 @@ var rollout_plan_default = {
|
|
|
17642
17719
|
repo: "mutmutco/mmi-hub",
|
|
17643
17720
|
role: "canary",
|
|
17644
17721
|
schedule: "train",
|
|
17645
|
-
v3Target: "v4.5.
|
|
17722
|
+
v3Target: "v4.5.104"
|
|
17646
17723
|
}
|
|
17647
17724
|
],
|
|
17648
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.",
|
|
17649
17726
|
rollback: {
|
|
17650
17727
|
independent: true,
|
|
17651
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17652
|
-
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)"
|
|
17653
17730
|
}
|
|
17654
17731
|
},
|
|
17655
17732
|
{
|
|
@@ -28267,7 +28344,7 @@ function planBranchRequiredContexts(input) {
|
|
|
28267
28344
|
})).filter((entry) => entry.contexts.length > 0);
|
|
28268
28345
|
return { common, pending, waiting };
|
|
28269
28346
|
}
|
|
28270
|
-
async function branchContextPlan(deps, repo, authority, meta) {
|
|
28347
|
+
async function branchContextPlan(deps, repo, authority, meta, emittedBranchesOnly = false) {
|
|
28271
28348
|
const branches = resolveRequiredCheckBranches(meta, repo);
|
|
28272
28349
|
const emittedByBranch = {};
|
|
28273
28350
|
const unreadable = [];
|
|
@@ -28277,7 +28354,10 @@ async function branchContextPlan(deps, repo, authority, meta) {
|
|
|
28277
28354
|
emittedByBranch[branch] = await resolveEmittedPrContexts(deps, repo, branch);
|
|
28278
28355
|
if (presence === void 0 || emittedByBranch[branch] === void 0) unreadable.push(branch);
|
|
28279
28356
|
}
|
|
28280
|
-
|
|
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 };
|
|
28281
28361
|
}
|
|
28282
28362
|
function projectCommonAuthority(authority, common) {
|
|
28283
28363
|
const apiPayload = patchRulesetRequiredContexts(authority.apiPayload, common);
|
|
@@ -28531,8 +28611,8 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28531
28611
|
checks.push({
|
|
28532
28612
|
ok: pendingAligned,
|
|
28533
28613
|
label: "branch-specific required checks match workflow emitters",
|
|
28534
|
-
detail: pendingAligned ? void 0 :
|
|
28535
|
-
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`
|
|
28536
28616
|
});
|
|
28537
28617
|
}
|
|
28538
28618
|
}
|
|
@@ -28564,7 +28644,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28564
28644
|
checks.push({
|
|
28565
28645
|
ok: aligned,
|
|
28566
28646
|
label: RULESET_REFERENCE_MATCH_LABEL,
|
|
28567
|
-
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`,
|
|
28568
28648
|
remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
|
|
28569
28649
|
});
|
|
28570
28650
|
}
|
|
@@ -29107,9 +29187,14 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta)
|
|
|
29107
29187
|
}
|
|
29108
29188
|
return null;
|
|
29109
29189
|
}
|
|
29110
|
-
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");
|
|
29111
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
|
+
}
|
|
29112
29196
|
const report = await auditRepoCi(repo, deps);
|
|
29197
|
+
const finish = (result2, pendingReason) => options.productRulesetOnly ? Promise.resolve(result2) : finalizeCiReconcile(repo, deps, result2, report, pendingReason);
|
|
29113
29198
|
const baseBranch = "development";
|
|
29114
29199
|
let raw = report.class === "deployable" && !report.explicitNoCi ? await fetchRulesetSeedBody(deps, repo) : null;
|
|
29115
29200
|
let authority = null;
|
|
@@ -29119,12 +29204,12 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29119
29204
|
authority = parseAuthoritativeRuleset(raw, meta, repo);
|
|
29120
29205
|
} catch (e) {
|
|
29121
29206
|
empty.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
29122
|
-
return
|
|
29207
|
+
return finish(empty);
|
|
29123
29208
|
}
|
|
29124
29209
|
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
29125
29210
|
if (hold) {
|
|
29126
29211
|
empty.skipped.push(hold);
|
|
29127
|
-
return
|
|
29212
|
+
return finish(empty, hold);
|
|
29128
29213
|
}
|
|
29129
29214
|
} else if (report.class === "deployable" && !report.explicitNoCi) {
|
|
29130
29215
|
const registryAuthority = registryAuthorityWithoutReference(meta, repo);
|
|
@@ -29132,59 +29217,64 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29132
29217
|
const hold = await authoritativeSafetyHold(registryAuthority, deps, repo, baseBranch, meta);
|
|
29133
29218
|
if (hold) {
|
|
29134
29219
|
empty.skipped.push(hold);
|
|
29135
|
-
return
|
|
29220
|
+
return finish(empty, hold);
|
|
29136
29221
|
}
|
|
29137
29222
|
}
|
|
29138
29223
|
}
|
|
29139
|
-
const result = await applyCiReconcileMergeSettingsFromReport(repo, deps, report);
|
|
29224
|
+
const result = options.productRulesetOnly ? empty : await applyCiReconcileMergeSettingsFromReport(repo, deps, report);
|
|
29140
29225
|
if (report.class !== "deployable" || report.explicitNoCi) {
|
|
29141
|
-
return
|
|
29226
|
+
return finish(result);
|
|
29142
29227
|
}
|
|
29143
|
-
const refDelivery = await seedGateYml(repo, deps, meta, result);
|
|
29228
|
+
const refDelivery = options.productRulesetOnly ? void 0 : await seedGateYml(repo, deps, meta, result);
|
|
29144
29229
|
if (refDelivery === "pending") {
|
|
29145
29230
|
const reason = `${PRODUCT_RULESET_REF} is pending on its protected-base source PR; live ruleset convergence waits for that source to merge`;
|
|
29146
29231
|
result.skipped.push(reason);
|
|
29147
|
-
return
|
|
29232
|
+
return finish(result, reason);
|
|
29148
29233
|
}
|
|
29149
29234
|
raw ??= await fetchRulesetSeedBody(deps, repo);
|
|
29150
29235
|
if (!raw) {
|
|
29151
29236
|
result.errors.push(`missing ${PRODUCT_RULESET_REF} on development \u2014 run bootstrap apply first`);
|
|
29152
|
-
return
|
|
29237
|
+
return finish(result);
|
|
29153
29238
|
}
|
|
29154
29239
|
if (authority == null) {
|
|
29155
29240
|
try {
|
|
29156
29241
|
authority = parseAuthoritativeRuleset(raw, meta, repo);
|
|
29157
29242
|
} catch (e) {
|
|
29158
29243
|
result.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
29159
|
-
return
|
|
29244
|
+
return finish(result);
|
|
29160
29245
|
}
|
|
29161
29246
|
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
29162
29247
|
if (hold) {
|
|
29163
29248
|
result.skipped.push(hold);
|
|
29164
|
-
return
|
|
29249
|
+
return finish(result, hold);
|
|
29165
29250
|
}
|
|
29166
29251
|
}
|
|
29167
29252
|
if (authority.contexts.length === 0) {
|
|
29168
29253
|
result.errors.push(`${authority.source} declares no required contexts`);
|
|
29169
|
-
return
|
|
29254
|
+
return finish(result);
|
|
29170
29255
|
}
|
|
29171
29256
|
let pendingPlan = null;
|
|
29172
29257
|
const declaredContexts = authority.contexts;
|
|
29173
29258
|
if (authority.source === "registry META requiredChecks") {
|
|
29174
|
-
pendingPlan = await branchContextPlan(deps, repo, authority, meta);
|
|
29259
|
+
pendingPlan = await branchContextPlan(deps, repo, authority, meta, options.productRulesetOnly);
|
|
29175
29260
|
if (pendingPlan.unreadable.length) {
|
|
29176
29261
|
const reason = `required branch workflow emitters unreadable on [${pendingPlan.unreadable.join(", ")}] \u2014 ruleset convergence deferred`;
|
|
29177
29262
|
result.skipped.push(reason);
|
|
29178
|
-
return
|
|
29263
|
+
return finish(result, reason);
|
|
29179
29264
|
}
|
|
29180
29265
|
if (pendingPlan.common.length === 0) {
|
|
29181
29266
|
result.errors.push("no common required context is emitted by every required branch \u2014 ruleset convergence deferred");
|
|
29182
|
-
return
|
|
29267
|
+
return finish(result);
|
|
29183
29268
|
}
|
|
29184
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
|
+
}
|
|
29185
29275
|
}
|
|
29186
29276
|
const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes) || !authority.committedStrict;
|
|
29187
|
-
if (sourceNeedsConvergence) {
|
|
29277
|
+
if (sourceNeedsConvergence && !options.productRulesetOnly) {
|
|
29188
29278
|
try {
|
|
29189
29279
|
const delivery = await deliverSeedFile(
|
|
29190
29280
|
deps,
|
|
@@ -29201,15 +29291,15 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29201
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}` : ""}`
|
|
29202
29292
|
);
|
|
29203
29293
|
const reason = `${PRODUCT_RULESET_REF} authority is pending on its protected-base source PR; live ruleset convergence waits for that source to merge`;
|
|
29204
|
-
return
|
|
29294
|
+
return finish(result, reason);
|
|
29205
29295
|
} else {
|
|
29206
29296
|
result.applied.push(`reconciled ${PRODUCT_RULESET_REF} authority \u2192 contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}]`);
|
|
29207
29297
|
}
|
|
29208
29298
|
} catch (e) {
|
|
29209
29299
|
result.errors.push(`ruleset reference delivery failed: ${e.message}`);
|
|
29210
|
-
return
|
|
29300
|
+
return finish(result);
|
|
29211
29301
|
}
|
|
29212
|
-
} else {
|
|
29302
|
+
} else if (!options.productRulesetOnly) {
|
|
29213
29303
|
result.skipped.push(`${PRODUCT_RULESET_REF} already matches declared context and branch authority`);
|
|
29214
29304
|
}
|
|
29215
29305
|
let live;
|
|
@@ -29217,7 +29307,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29217
29307
|
live = await readLiveProductRuleset(deps, repo);
|
|
29218
29308
|
} catch (e) {
|
|
29219
29309
|
result.errors.push(`cannot read the live ${PRODUCT_RULESET_NAME} ruleset: ${e.message}`);
|
|
29220
|
-
return
|
|
29310
|
+
return finish(result);
|
|
29221
29311
|
}
|
|
29222
29312
|
const liveContexts = live == null ? [] : sortedUnique(rulesetRequiredContexts(live));
|
|
29223
29313
|
const liveBranchIncludes = live == null ? [] : rulesetBranchIncludes(live);
|
|
@@ -29235,7 +29325,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29235
29325
|
if (pendingPlan?.pending.some((entry) => entry.contexts.some((context) => unsafe.includes(context)))) {
|
|
29236
29326
|
const reason = `pending required-check rulesets left non-enforcing \u2014 [${unsafe.join(", ")}] emitted only by path-filtered workflows`;
|
|
29237
29327
|
result.skipped.push(reason);
|
|
29238
|
-
return
|
|
29328
|
+
return finish(result, reason);
|
|
29239
29329
|
}
|
|
29240
29330
|
const gateProvenGreen = await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles);
|
|
29241
29331
|
const plan = resolveProductRulesetReconcilePlan({
|
|
@@ -29252,25 +29342,25 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29252
29342
|
if (!plan.shouldActivate) {
|
|
29253
29343
|
if (plan.holdReason) {
|
|
29254
29344
|
result.skipped.push(plan.holdReason);
|
|
29255
|
-
return
|
|
29345
|
+
return finish(result, plan.holdReason);
|
|
29256
29346
|
}
|
|
29257
29347
|
result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts, branches and up-to-date policy already match declared authority`);
|
|
29258
29348
|
}
|
|
29259
29349
|
if (plan.shouldActivate) {
|
|
29260
29350
|
try {
|
|
29261
|
-
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);
|
|
29262
29352
|
if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
|
|
29263
|
-
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}` : ""}`);
|
|
29264
29354
|
} catch (e) {
|
|
29265
29355
|
result.errors.push(e.message);
|
|
29266
|
-
return
|
|
29356
|
+
return finish(result);
|
|
29267
29357
|
}
|
|
29268
29358
|
}
|
|
29269
29359
|
if (pendingPlan) {
|
|
29270
29360
|
if (pendingPlan.pending.length > 0 && !gateProvenGreen) {
|
|
29271
29361
|
const reason = "pending required-check rulesets left non-enforcing \u2014 the gate is not green on development";
|
|
29272
29362
|
result.skipped.push(reason);
|
|
29273
|
-
return
|
|
29363
|
+
return finish(result, reason);
|
|
29274
29364
|
}
|
|
29275
29365
|
try {
|
|
29276
29366
|
const desired = /* @__PURE__ */ new Set();
|
|
@@ -29286,20 +29376,20 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29286
29376
|
patchRulesetRequiredContexts(statusOnly, entry.contexts),
|
|
29287
29377
|
[entry.branch]
|
|
29288
29378
|
);
|
|
29289
|
-
const activation = await activateProductRuleset(repo, payload, deps.client);
|
|
29290
|
-
(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(", ")}]`);
|
|
29291
29381
|
}
|
|
29292
29382
|
const list = await deps.client.rest("GET", `repos/${repo}/rulesets`);
|
|
29293
29383
|
for (const stale of list ?? []) {
|
|
29294
29384
|
if (!stale.name?.startsWith(PRODUCT_PENDING_RULESET_PREFIX) || desired.has(stale.name) || stale.id == null) continue;
|
|
29295
|
-
await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
|
|
29385
|
+
if (!options.dryRun) await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
|
|
29296
29386
|
result.applied.push(`deleted converged ${stale.name}`);
|
|
29297
29387
|
}
|
|
29298
29388
|
} catch (e) {
|
|
29299
29389
|
result.errors.push(`pending ruleset convergence failed: ${e.message}`);
|
|
29300
29390
|
}
|
|
29301
29391
|
}
|
|
29302
|
-
return
|
|
29392
|
+
return finish(result);
|
|
29303
29393
|
}
|
|
29304
29394
|
|
|
29305
29395
|
// src/command-composition.ts
|
|
@@ -33937,14 +34027,15 @@ function deployRowChecks(repo, deployModel, releaseTrack, facts, factsRead, suit
|
|
|
33937
34027
|
};
|
|
33938
34028
|
});
|
|
33939
34029
|
}
|
|
33940
|
-
function edgeCertificateChecks(results) {
|
|
34030
|
+
function edgeCertificateChecks(results, repo) {
|
|
33941
34031
|
return results.flatMap((result) => {
|
|
33942
34032
|
if (!result) return [];
|
|
33943
34033
|
const { stage, status, error } = result;
|
|
34034
|
+
const fix = status?.missing && !status.pending ? ` \u2014 run: mmi-cli devops runtime edge cert ensure ${repo} ${stage}` : "";
|
|
33944
34035
|
return [{
|
|
33945
34036
|
ok: !error && (status?.required === false || status?.required === true && status.missing === false),
|
|
33946
34037
|
label: `Cloudflare edge certificate: ${stage}`,
|
|
33947
|
-
detail: error ? `certificate coverage unreadable (${error}); cannot verify TLS` : status?.hint
|
|
34038
|
+
detail: error ? `certificate coverage unreadable (${error}); cannot verify TLS` : status?.hint && `${status.hint}${fix}`
|
|
33948
34039
|
}];
|
|
33949
34040
|
});
|
|
33950
34041
|
}
|
|
@@ -34008,6 +34099,8 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
34008
34099
|
detail: exactAllowlist ? void 0 : ownersUnverified ? "org owner baseline is unreadable or empty; the exact restriction is unverified" : deps.pushAllowlistOwnersRead === true ? "restriction must contain exactly the org owners + mmi-github-app and no teams" : "restrictions.users is empty or unset"
|
|
34009
34100
|
});
|
|
34010
34101
|
}
|
|
34102
|
+
const packageAccess = suiteModulePackageAccessCheck(deps.projectMeta?.suite, deps.deployModel);
|
|
34103
|
+
if (packageAccess) checks.push(packageAccess);
|
|
34011
34104
|
let environmentDrift2 = [];
|
|
34012
34105
|
try {
|
|
34013
34106
|
environmentDrift2 = await verifyRepoEnvironments(repo, effectiveTrack, deps.client, deps.projectMeta?.suite);
|
|
@@ -34091,7 +34184,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
34091
34184
|
const check = npmrcGitHubPackagesAuthCheck(path2, await contentText(deps, repo, baseBranch, path2));
|
|
34092
34185
|
if (check) checks.push(check);
|
|
34093
34186
|
}
|
|
34094
|
-
checks.push(...edgeCertificateChecks(deps.edgeCertificates ?? []));
|
|
34187
|
+
checks.push(...edgeCertificateChecks(deps.edgeCertificates ?? [], repo));
|
|
34095
34188
|
checks.push(...edgeDnsChecks(deps.edgeDns ?? [], repo));
|
|
34096
34189
|
const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
|
|
34097
34190
|
if (portRangeCheck) checks.push(portRangeCheck);
|
|
@@ -34349,6 +34442,14 @@ function laneNotEvaluatedFromThisBranch(doctor) {
|
|
|
34349
34442
|
const only = reasons.length === 1 ? reasons[0] : void 0;
|
|
34350
34443
|
return only?.code === "branch-mismatch" ? only.title : void 0;
|
|
34351
34444
|
}
|
|
34445
|
+
function suiteModulePackageAccessCheck(suite, deployModel) {
|
|
34446
|
+
if (suite !== "mm-work" || deployModel !== "tenant-container") return void 0;
|
|
34447
|
+
return {
|
|
34448
|
+
ok: true,
|
|
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"]'.`
|
|
34451
|
+
};
|
|
34452
|
+
}
|
|
34352
34453
|
function attachTrainDoctorVerdicts(report, trainDoctors) {
|
|
34353
34454
|
const marked = trainDoctors.map((doctor) => {
|
|
34354
34455
|
const notEvaluatedFromThisBranch = laneNotEvaluatedFromThisBranch(doctor);
|
|
@@ -34742,7 +34843,7 @@ function registerBootstrapCommands(program3) {
|
|
|
34742
34843
|
}
|
|
34743
34844
|
if (findings.some((f) => f.state !== "waived")) process.exitCode = 1;
|
|
34744
34845
|
});
|
|
34745
|
-
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) => {
|
|
34746
34847
|
const o = {
|
|
34747
34848
|
class: rawValue("--class", "deployable"),
|
|
34748
34849
|
projectType: rawValue("--project-type", ""),
|
|
@@ -34829,6 +34930,26 @@ function registerBootstrapCommands(program3) {
|
|
|
34829
34930
|
}
|
|
34830
34931
|
return;
|
|
34831
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
|
+
}
|
|
34832
34953
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
34833
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`);
|
|
34834
34955
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
@@ -34837,13 +34958,6 @@ function registerBootstrapCommands(program3) {
|
|
|
34837
34958
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
34838
34959
|
const slug = parsedRepo.slug;
|
|
34839
34960
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
34840
|
-
const seedsToApply = onlyTarget ? manifest.seeds.filter((s) => s.target === onlyTarget || s.target.replace("{{REPO_SLUG}}", slug) === onlyTarget) : manifest.seeds;
|
|
34841
|
-
if (onlyTarget && !seedsToApply.length) {
|
|
34842
|
-
const known = manifest.seeds.map((s) => s.target).join("\n ");
|
|
34843
|
-
return fail(`bootstrap apply: --only '${onlyTarget}' names no seed in ${manifestPath}. Declared targets:
|
|
34844
|
-
${known}`);
|
|
34845
|
-
}
|
|
34846
|
-
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
34847
34961
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
34848
34962
|
const readFile10 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
|
|
34849
34963
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
@@ -34913,6 +35027,21 @@ function registerBootstrapCommands(program3) {
|
|
|
34913
35027
|
applyProjectType = o.projectType || registryMeta?.projectType || payload.projectType;
|
|
34914
35028
|
} catch {
|
|
34915
35029
|
}
|
|
35030
|
+
const availableSeeds = suiteModuleSeeds(manifest.seeds, vars.SUITE || registryMeta?.suite, applyDeployModel);
|
|
35031
|
+
const seedsToApply = onlyTarget ? availableSeeds.filter((s) => s.target === onlyTarget || s.target.replace("{{REPO_SLUG}}", slug) === onlyTarget) : availableSeeds;
|
|
35032
|
+
if (onlyTarget && !seedsToApply.length) {
|
|
35033
|
+
return fail(`bootstrap apply: --only '${onlyTarget}' names no seed for this project. Declared targets:
|
|
35034
|
+
${availableSeeds.map((s) => s.target).join("\n ")}`);
|
|
35035
|
+
}
|
|
35036
|
+
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
35037
|
+
if (vars.SUITE === "mm-work" || registryMeta?.suite === "mm-work") {
|
|
35038
|
+
if (applyDeployModel === "tenant-container") {
|
|
35039
|
+
const requiredChecks = registryMeta?.requiredChecks ?? ["gate"];
|
|
35040
|
+
const moduleGate = withDerivedRepoVars({}, parsedRepo, o.class, effectiveTrack, registryRequiredCheckBranches, requiredChecks);
|
|
35041
|
+
vars.GATE_RULESET_CONTEXTS_JSON = moduleGate.GATE_RULESET_CONTEXTS_JSON;
|
|
35042
|
+
if (registryMeta) registryMeta = { ...registryMeta, requiredChecks };
|
|
35043
|
+
}
|
|
35044
|
+
}
|
|
34916
35045
|
const controlClient = defaultGitHubClient();
|
|
34917
35046
|
let controlOwners = [];
|
|
34918
35047
|
let controlDeployFacts = null;
|
|
@@ -53292,6 +53421,17 @@ function registerEdgeCommands(program3) {
|
|
|
53292
53421
|
fail(`runtime edge dns: ${e.message}`);
|
|
53293
53422
|
}
|
|
53294
53423
|
});
|
|
53424
|
+
edge.command("cert <action> <owner/repo> <stage>").description("read (`status`) or order-when-missing (`ensure`, master-only) the Cloudflare advanced edge certificate pack (<x> + *.<x>) a dev/rc host needs").action(async (action, repo, stage) => {
|
|
53425
|
+
if (action !== "status" && action !== "ensure") return fail("runtime edge cert: <action> must be status or ensure");
|
|
53426
|
+
if (stage !== "dev" && stage !== "rc") return fail("runtime edge cert: <stage> must be dev or rc (main is covered by the zone certificate)");
|
|
53427
|
+
try {
|
|
53428
|
+
const result = await edgeCertificateForStage(slugOf(repo), stage, registryClientDeps(await loadConfig()), action === "ensure");
|
|
53429
|
+
console.log(JSON.stringify(result, null, 2));
|
|
53430
|
+
if (result.required && result.missing) process.exitCode = 1;
|
|
53431
|
+
} catch (e) {
|
|
53432
|
+
fail(`runtime edge cert: ${e.message}`);
|
|
53433
|
+
}
|
|
53434
|
+
});
|
|
53295
53435
|
}
|
|
53296
53436
|
|
|
53297
53437
|
// src/command-register-developer.ts
|
package/package.json
CHANGED