@mutmutco/cli 4.5.45 → 4.5.47
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 +132 -44
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -14365,6 +14365,9 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
|
|
|
14365
14365
|
return { action: "created", enforcement };
|
|
14366
14366
|
}
|
|
14367
14367
|
|
|
14368
|
+
// src/bootstrap-apply.ts
|
|
14369
|
+
var import_node_crypto5 = require("node:crypto");
|
|
14370
|
+
|
|
14368
14371
|
// src/bootstrap-seeds.ts
|
|
14369
14372
|
var PLACEHOLDER_RE = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
14370
14373
|
function loadBootstrapSeeds(manifestJson) {
|
|
@@ -15593,6 +15596,32 @@ function planSeedAction(seed, exists) {
|
|
|
15593
15596
|
}
|
|
15594
15597
|
return exists ? { target: seed.target, action: "update", ownership: "org", reason: "org-owned, refresh to current" } : { target: seed.target, action: "create", ownership: "org", reason: "org-owned, missing" };
|
|
15595
15598
|
}
|
|
15599
|
+
var GATE_RENDER_NOTE = "# Rendered from the registry gate config by mmi-cli devops bootstrap apply <repo> --only gate.yml --from-config; edit the config, not this file.\n";
|
|
15600
|
+
var gateHash = (body) => (0, import_node_crypto5.createHash)("sha256").update(body, "utf8").digest("hex");
|
|
15601
|
+
function parseGateRenderStamp(content) {
|
|
15602
|
+
const match = /^# mmi-gate-render: sha256=([a-f0-9]{64})\n/.exec(content);
|
|
15603
|
+
return match ? { hash: match[1], body: content.slice(match[0].length) } : null;
|
|
15604
|
+
}
|
|
15605
|
+
function verifyGateRenderStamp(content) {
|
|
15606
|
+
const stamp = parseGateRenderStamp(content);
|
|
15607
|
+
return stamp !== null && stamp.body.startsWith(GATE_RENDER_NOTE) && gateHash(stamp.body) === stamp.hash;
|
|
15608
|
+
}
|
|
15609
|
+
function stampGateRender(rendered) {
|
|
15610
|
+
const body = GATE_RENDER_NOTE + rendered;
|
|
15611
|
+
return `# mmi-gate-render: sha256=${gateHash(body)}
|
|
15612
|
+
${body}`;
|
|
15613
|
+
}
|
|
15614
|
+
function planGateFromConfig(current, rendered) {
|
|
15615
|
+
const target = ".github/workflows/gate.yml";
|
|
15616
|
+
const base = { target, ownership: "repo" };
|
|
15617
|
+
if (current === null) return { ...base, action: "create", reason: "registry gate config; review via branch + PR" };
|
|
15618
|
+
const stamp = parseGateRenderStamp(current);
|
|
15619
|
+
if (stamp) {
|
|
15620
|
+
if (!verifyGateRenderStamp(current)) return { ...base, action: "skip", refused: true, reason: "REFUSED: stamped gate was hand-edited since its last render (diff below)" };
|
|
15621
|
+
return stamp.body === GATE_RENDER_NOTE + rendered ? { ...base, action: "skip", reason: "already current from registry gate config" } : { ...base, action: "update", reason: "registry gate config changed; update stamped gate via branch + PR" };
|
|
15622
|
+
}
|
|
15623
|
+
return current === rendered ? { ...base, action: "update", reason: "adopt legacy gate: add registry render stamp via branch + PR" } : { ...base, action: "skip", refused: true, reason: "REFUSED: unstamped gate differs from registry render; make the registry config reproduce the file first, then re-run to adopt (diff below)" };
|
|
15624
|
+
}
|
|
15596
15625
|
function reconcileSeedAction(action, content, isManagedBlock, remoteContent = null) {
|
|
15597
15626
|
if (action.action === "skip") return action;
|
|
15598
15627
|
if (content == null) {
|
|
@@ -17014,10 +17043,10 @@ var rollout_plan_default = {
|
|
|
17014
17043
|
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)."
|
|
17015
17044
|
},
|
|
17016
17045
|
baseline: {
|
|
17017
|
-
version: "4.5.
|
|
17018
|
-
tag: "v4.5.
|
|
17019
|
-
commit: "
|
|
17020
|
-
npm: "@mutmutco/cli@4.5.
|
|
17046
|
+
version: "4.5.47",
|
|
17047
|
+
tag: "v4.5.47",
|
|
17048
|
+
commit: "d8722f771c7d",
|
|
17049
|
+
npm: "@mutmutco/cli@4.5.47"
|
|
17021
17050
|
},
|
|
17022
17051
|
exitCriterion: "fleet-n-of-n",
|
|
17023
17052
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17034,14 +17063,14 @@ var rollout_plan_default = {
|
|
|
17034
17063
|
repo: "mutmutco/mmi-hub",
|
|
17035
17064
|
role: "canary",
|
|
17036
17065
|
schedule: "train",
|
|
17037
|
-
v3Target: "v4.5.
|
|
17066
|
+
v3Target: "v4.5.47"
|
|
17038
17067
|
}
|
|
17039
17068
|
],
|
|
17040
17069
|
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.",
|
|
17041
17070
|
rollback: {
|
|
17042
17071
|
independent: true,
|
|
17043
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17044
|
-
v3Target: "v4.5.
|
|
17072
|
+
mechanism: "npm dist-tag latest -> 4.5.47 and redeploy the Hub Lambda from tag v4.5.47 (d8722f771c7d); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17073
|
+
v3Target: "v4.5.47 (@mutmutco/cli@4.5.47, tag commit d8722f771c7d \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17045
17074
|
}
|
|
17046
17075
|
},
|
|
17047
17076
|
{
|
|
@@ -22319,7 +22348,7 @@ function checkLineEndings(probe) {
|
|
|
22319
22348
|
// src/release-ledger.ts
|
|
22320
22349
|
var import_node_fs22 = require("node:fs");
|
|
22321
22350
|
var import_node_path22 = require("node:path");
|
|
22322
|
-
var
|
|
22351
|
+
var import_node_crypto6 = require("node:crypto");
|
|
22323
22352
|
|
|
22324
22353
|
// src/file-lock.ts
|
|
22325
22354
|
var import_promises2 = require("node:fs/promises");
|
|
@@ -22677,7 +22706,7 @@ function releaseLedgerPath(cwd) {
|
|
|
22677
22706
|
}
|
|
22678
22707
|
function atomicWriteJson(path2, ledger) {
|
|
22679
22708
|
(0, import_node_fs22.mkdirSync)((0, import_node_path22.dirname)(path2), { recursive: true });
|
|
22680
|
-
const tmp = `${path2}.tmp-${process.pid}-${(0,
|
|
22709
|
+
const tmp = `${path2}.tmp-${process.pid}-${(0, import_node_crypto6.randomBytes)(4).toString("hex")}`;
|
|
22681
22710
|
(0, import_node_fs22.writeFileSync)(tmp, `${JSON.stringify(ledger, null, 2)}
|
|
22682
22711
|
`, "utf8");
|
|
22683
22712
|
(0, import_node_fs22.renameSync)(tmp, path2);
|
|
@@ -29092,13 +29121,13 @@ function renderPluginCachePlan(plan, applied) {
|
|
|
29092
29121
|
}
|
|
29093
29122
|
|
|
29094
29123
|
// ../shared/github-quota-scope.ts
|
|
29095
|
-
var
|
|
29124
|
+
var import_node_crypto7 = require("node:crypto");
|
|
29096
29125
|
function appQuotaScope(appId, installationId, origin = "https://api.github.com", resource = "graphql") {
|
|
29097
|
-
return `github-app-v1:${(0,
|
|
29126
|
+
return `github-app-v1:${(0, import_node_crypto7.createHash)("sha256").update(JSON.stringify([new URL(origin).origin, appId.trim(), installationId.trim(), resource])).digest("hex")}`;
|
|
29098
29127
|
}
|
|
29099
29128
|
|
|
29100
29129
|
// src/app-actor.ts
|
|
29101
|
-
var
|
|
29130
|
+
var import_node_crypto8 = require("node:crypto");
|
|
29102
29131
|
var APP_ACTOR_ENV = "MMI_ACTOR";
|
|
29103
29132
|
var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
29104
29133
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
@@ -29149,7 +29178,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
|
|
|
29149
29178
|
exp: now + APP_JWT_TTL_S,
|
|
29150
29179
|
iss: appId
|
|
29151
29180
|
}));
|
|
29152
|
-
const signer = (0,
|
|
29181
|
+
const signer = (0, import_node_crypto8.createSign)("RSA-SHA256");
|
|
29153
29182
|
signer.update(`${header}.${payload}`);
|
|
29154
29183
|
return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
|
|
29155
29184
|
}
|
|
@@ -30045,7 +30074,7 @@ var import_node_os16 = require("node:os");
|
|
|
30045
30074
|
var import_node_path27 = require("node:path");
|
|
30046
30075
|
|
|
30047
30076
|
// src/box-control.ts
|
|
30048
|
-
var
|
|
30077
|
+
var import_node_crypto9 = require("node:crypto");
|
|
30049
30078
|
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
30050
30079
|
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
30051
30080
|
function parseManifest(text) {
|
|
@@ -30092,7 +30121,7 @@ function filesForBox(files, box) {
|
|
|
30092
30121
|
return files.flatMap((file) => file.targets.filter((t) => t.box === box).map((t) => ({ file, dest: t.dest })));
|
|
30093
30122
|
}
|
|
30094
30123
|
function md5(content) {
|
|
30095
|
-
return (0,
|
|
30124
|
+
return (0, import_node_crypto9.createHash)("md5").update(content).digest("hex");
|
|
30096
30125
|
}
|
|
30097
30126
|
function planRemoteScript(dests) {
|
|
30098
30127
|
const lines2 = ["set -u"];
|
|
@@ -31958,7 +31987,7 @@ function renderDeployPortDoctor(report) {
|
|
|
31958
31987
|
}
|
|
31959
31988
|
|
|
31960
31989
|
// src/bootstrap-drift.ts
|
|
31961
|
-
var
|
|
31990
|
+
var import_node_crypto10 = require("node:crypto");
|
|
31962
31991
|
function byteComparableSeeds(manifest, cls) {
|
|
31963
31992
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
31964
31993
|
}
|
|
@@ -31978,7 +32007,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
31978
32007
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
31979
32008
|
}
|
|
31980
32009
|
function seedContentHash(content) {
|
|
31981
|
-
return (0,
|
|
32010
|
+
return (0, import_node_crypto10.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
31982
32011
|
}
|
|
31983
32012
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
31984
32013
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -32140,7 +32169,7 @@ function renderPropagationReport(plan) {
|
|
|
32140
32169
|
}
|
|
32141
32170
|
|
|
32142
32171
|
// src/bootstrap-propagation-identity.ts
|
|
32143
|
-
var
|
|
32172
|
+
var import_node_crypto11 = require("node:crypto");
|
|
32144
32173
|
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
32145
32174
|
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
32146
32175
|
function safeBranchPart(value, maxLength, fallback) {
|
|
@@ -32153,7 +32182,7 @@ function repoSlug2(repo) {
|
|
|
32153
32182
|
function propagationBranch(repo, target) {
|
|
32154
32183
|
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
32155
32184
|
const targetPart = safeBranchPart(target, 48, "target");
|
|
32156
|
-
const hash = (0,
|
|
32185
|
+
const hash = (0, import_node_crypto11.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
32157
32186
|
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
32158
32187
|
}
|
|
32159
32188
|
function legacyPropagationBranch(repo) {
|
|
@@ -33309,7 +33338,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
33309
33338
|
}
|
|
33310
33339
|
|
|
33311
33340
|
// src/bootstrap-seed-delivery.ts
|
|
33312
|
-
var
|
|
33341
|
+
var import_node_crypto12 = require("node:crypto");
|
|
33313
33342
|
init_github_client();
|
|
33314
33343
|
var SHA = /^[a-f0-9]{40}$/;
|
|
33315
33344
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -33332,7 +33361,7 @@ async function readTargetedSeedFiles(repo, plan, target, ref, client) {
|
|
|
33332
33361
|
const candidate = ref === plan.baseSha ? base : await readSeedFile(repo, target, ref, client);
|
|
33333
33362
|
return { base, candidate };
|
|
33334
33363
|
}
|
|
33335
|
-
async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, client) {
|
|
33364
|
+
async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, client, forcePr = false) {
|
|
33336
33365
|
const [rules, base] = await Promise.all([
|
|
33337
33366
|
client.rest("GET", `repos/${repo}/rules/branches/${encodeURIComponent(baseBranch)}`),
|
|
33338
33367
|
client.rest("GET", `repos/${repo}/git/ref/heads/${encodeURIComponent(baseBranch)}`)
|
|
@@ -33342,8 +33371,10 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
33342
33371
|
}
|
|
33343
33372
|
const baseSha = base?.object?.sha;
|
|
33344
33373
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
33345
|
-
const identity = (0,
|
|
33346
|
-
|
|
33374
|
+
const identity = (0, import_node_crypto12.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
33375
|
+
const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
|
|
33376
|
+
const scoped = forcePr && delivery.mode === "direct" ? { mode: "pr", ref: `bootstrap-seed-${identity}-${slug}`, branch: `bootstrap-seed-${identity}-${slug}`, reason: "registry gate render requires review" } : delivery;
|
|
33377
|
+
return { ...scoped, baseSha, baseBranch };
|
|
33347
33378
|
}
|
|
33348
33379
|
async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
|
|
33349
33380
|
let ref;
|
|
@@ -33405,6 +33436,24 @@ async function enableTargetedSeedAutoMerge(repo, number, plan, target, expected,
|
|
|
33405
33436
|
|
|
33406
33437
|
// src/bootstrap-commands.ts
|
|
33407
33438
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
33439
|
+
async function gateRenderDiff(current, rendered) {
|
|
33440
|
+
const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path28.join)((0, import_node_os17.tmpdir)(), "mmi-gate-diff-"));
|
|
33441
|
+
const before = (0, import_node_path28.join)(dir, "committed-gate.yml");
|
|
33442
|
+
const after = (0, import_node_path28.join)(dir, "registry-render.yml");
|
|
33443
|
+
try {
|
|
33444
|
+
(0, import_node_fs28.writeFileSync)(before, current);
|
|
33445
|
+
(0, import_node_fs28.writeFileSync)(after, rendered);
|
|
33446
|
+
try {
|
|
33447
|
+
return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
33448
|
+
} catch (e) {
|
|
33449
|
+
const result = e;
|
|
33450
|
+
if (result.code === 1 && result.stdout) return result.stdout;
|
|
33451
|
+
throw e;
|
|
33452
|
+
}
|
|
33453
|
+
} finally {
|
|
33454
|
+
(0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
|
|
33455
|
+
}
|
|
33456
|
+
}
|
|
33408
33457
|
function bootstrapTrainDoctorDeps() {
|
|
33409
33458
|
return {
|
|
33410
33459
|
run: async (file, args) => (process.platform === "win32" && file === "npm" ? await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout: 3e4 }) : await execFileP(file, args, { timeout: 3e4 })).stdout,
|
|
@@ -33645,18 +33694,21 @@ function registerBootstrapCommands(program3) {
|
|
|
33645
33694
|
}
|
|
33646
33695
|
if (findings.some((f) => f.state !== "waived")) process.exitCode = 1;
|
|
33647
33696
|
});
|
|
33648
|
-
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 target environments (#5035) to run the deployment-environment reconcile alone (dry-run classifies, --execute writes)").option("--var <KEY=VALUE...>", "placeholder values for repo-owned templates (repeatable)").option("--json", "machine-readable output").action(async (repo, cmdOpts) => {
|
|
33697
|
+
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 target environments (#5035) to run the deployment-environment reconcile alone (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) => {
|
|
33649
33698
|
const o = {
|
|
33650
33699
|
class: rawValue("--class", "deployable"),
|
|
33651
33700
|
projectType: rawValue("--project-type", ""),
|
|
33652
33701
|
deployModel: rawValue("--deploy-model", ""),
|
|
33653
33702
|
releaseTrack: rawValue("--release-track", ""),
|
|
33654
33703
|
only: rawValue("--only", ""),
|
|
33704
|
+
fromConfig: rawFlag("--from-config"),
|
|
33655
33705
|
execute: rawFlag("--execute"),
|
|
33656
33706
|
json: rawFlag("--json"),
|
|
33657
33707
|
box: rawValue("--box", ""),
|
|
33658
33708
|
domains: rawValue("--domains", "")
|
|
33659
33709
|
};
|
|
33710
|
+
if (o.fromConfig && (o.only.trim() !== ".github/workflows/gate.yml" && o.only.trim() !== "gate.yml" || o.class !== "deployable" || cmdOpts.var?.length))
|
|
33711
|
+
return fail("bootstrap apply: --from-config requires --only gate.yml, --class deployable, and no --var overrides");
|
|
33660
33712
|
if (o.domains && !o.box) return fail("bootstrap apply: --domains requires --box");
|
|
33661
33713
|
if (o.box && o.only) return fail("bootstrap apply: --box cannot be combined with --only");
|
|
33662
33714
|
let boxDomains;
|
|
@@ -33725,7 +33777,7 @@ function registerBootstrapCommands(program3) {
|
|
|
33725
33777
|
const manifest = loadBootstrapSeeds((0, import_node_fs28.readFileSync)(manifestPath, "utf8"));
|
|
33726
33778
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
33727
33779
|
const slug = parsedRepo.slug;
|
|
33728
|
-
const onlyTarget = o.only.trim();
|
|
33780
|
+
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
33729
33781
|
const seedsToApply = onlyTarget ? manifest.seeds.filter((s) => s.target === onlyTarget || s.target.replace("{{REPO_SLUG}}", slug) === onlyTarget) : manifest.seeds;
|
|
33730
33782
|
if (onlyTarget && !seedsToApply.length) {
|
|
33731
33783
|
const known = manifest.seeds.map((s) => s.target).join("\n ");
|
|
@@ -33761,6 +33813,8 @@ function registerBootstrapCommands(program3) {
|
|
|
33761
33813
|
registryMeta = meta;
|
|
33762
33814
|
registryRequiredCheckBranches = meta?.requiredCheckBranches;
|
|
33763
33815
|
for (const [k, v] of Object.entries(gateConfigToVars(meta?.gate))) if (rawVars[k] == null) rawVars[k] = v;
|
|
33816
|
+
} else if (o.fromConfig) {
|
|
33817
|
+
return failGraceful(`bootstrap apply: registry META unreadable (${metaRead.error}); --from-config requires the committed registry gate config`);
|
|
33764
33818
|
} else if (o.execute && !onlyTarget) {
|
|
33765
33819
|
return failGraceful(`bootstrap apply: registry META unreadable (${metaRead.error}); refusing the live registry write, which would reset a registered project's releaseTrack and edgeDomains to class defaults (#7138)`);
|
|
33766
33820
|
} else {
|
|
@@ -33867,10 +33921,42 @@ function registerBootstrapCommands(program3) {
|
|
|
33867
33921
|
let scopedPlan;
|
|
33868
33922
|
let scopedContent;
|
|
33869
33923
|
let seededToBranch = 0;
|
|
33924
|
+
let configGateAction;
|
|
33925
|
+
let configGateContent;
|
|
33870
33926
|
if (onlyTarget) {
|
|
33871
|
-
scopedPlan = await targetedSeedPlan(repo, slug, onlyTarget, baseBranch, seedSource.sha, controlClient);
|
|
33927
|
+
scopedPlan = await targetedSeedPlan(repo, slug, onlyTarget, baseBranch, seedSource.sha, controlClient, o.fromConfig);
|
|
33872
33928
|
seedPlan = scopedPlan;
|
|
33873
|
-
if (o.
|
|
33929
|
+
if (o.fromConfig) {
|
|
33930
|
+
const gate = registryMeta?.gate;
|
|
33931
|
+
if (!gate || !Object.keys(gate).length) return failGraceful("bootstrap apply: --from-config requires a non-empty registry gate config; set it with oracle org project set --var gate={...} first");
|
|
33932
|
+
const gateSeed = seedsToApply[0];
|
|
33933
|
+
if (gateSeed.waivers?.[slug]) return failGraceful(`bootstrap apply: gate.yml is waived for ${slug}: ${gateSeed.waivers[slug]}`);
|
|
33934
|
+
if (gateSeed.source !== "seed:gate.template.yml") return failGraceful("bootstrap apply: gate template is not the ci-reconcile seed");
|
|
33935
|
+
try {
|
|
33936
|
+
const gateVars = withDerivedRepoVars(
|
|
33937
|
+
{ ...gateConfigToVars(gate), REPO_SLUG: slug },
|
|
33938
|
+
parsedRepo,
|
|
33939
|
+
"deployable",
|
|
33940
|
+
effectiveTrack,
|
|
33941
|
+
registryMeta?.requiredCheckBranches,
|
|
33942
|
+
registryMeta?.requiredChecks
|
|
33943
|
+
);
|
|
33944
|
+
const rendered = resolveSeedContent(gateSeed, gateVars, readFile9);
|
|
33945
|
+
if (rendered === null || missingPlaceholders(rendered).length) return failGraceful("bootstrap apply: registry gate template is unreadable or has unfilled placeholders");
|
|
33946
|
+
const base = (await readTargetedSeedFiles(repo, scopedPlan, onlyTarget, scopedPlan.baseSha, controlClient)).base?.content ?? null;
|
|
33947
|
+
configGateAction = planGateFromConfig(base, rendered);
|
|
33948
|
+
if (configGateAction.refused && base !== null) {
|
|
33949
|
+
const stamped = parseGateRenderStamp(base);
|
|
33950
|
+
const committedBody = stamped?.body.startsWith(GATE_RENDER_NOTE) ? stamped.body.slice(GATE_RENDER_NOTE.length) : stamped?.body ?? base;
|
|
33951
|
+
console.error(await gateRenderDiff(committedBody, rendered));
|
|
33952
|
+
return failGraceful(`bootstrap apply: ${configGateAction.reason}`);
|
|
33953
|
+
}
|
|
33954
|
+
configGateContent = stampGateRender(rendered);
|
|
33955
|
+
} catch (e) {
|
|
33956
|
+
return failGraceful(`bootstrap apply --from-config: ${e.message}`);
|
|
33957
|
+
}
|
|
33958
|
+
}
|
|
33959
|
+
if (o.execute && scopedPlan.branch && configGateAction?.action !== "skip") {
|
|
33874
33960
|
await prepareTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient, gh2);
|
|
33875
33961
|
}
|
|
33876
33962
|
} else if (o.execute) {
|
|
@@ -33896,7 +33982,7 @@ function registerBootstrapCommands(program3) {
|
|
|
33896
33982
|
}
|
|
33897
33983
|
}
|
|
33898
33984
|
const docsForIndex = [];
|
|
33899
|
-
const seedReadRef = !o.execute
|
|
33985
|
+
const seedReadRef = scopedPlan && (!o.execute || configGateAction?.action === "skip") ? scopedPlan.baseSha : seedPlan.ref;
|
|
33900
33986
|
for (const seed of seedsToApply) {
|
|
33901
33987
|
if (!seed.classes.includes(o.class)) continue;
|
|
33902
33988
|
if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
|
|
@@ -33936,14 +34022,16 @@ function registerBootstrapCommands(program3) {
|
|
|
33936
34022
|
} catch {
|
|
33937
34023
|
exists = false;
|
|
33938
34024
|
}
|
|
33939
|
-
const planned = planSeedAction(resolved, scopedPlan ? baseContent !== null : exists);
|
|
34025
|
+
const planned = configGateAction ?? planSeedAction(resolved, scopedPlan ? baseContent !== null : exists);
|
|
33940
34026
|
if (scopedPlan && planned.action !== "skip") planned.action = exists ? "update" : "create";
|
|
33941
34027
|
const isLegacyBlock = resolved.source === "managed-block";
|
|
33942
34028
|
let content = null;
|
|
33943
34029
|
let isManaged = isLegacyBlock || resolved.managedBlock != null;
|
|
33944
34030
|
const preservedContent = scopedPlan ? baseContent : remoteContent;
|
|
33945
34031
|
if (planned.action === "create" || planned.action === "update") {
|
|
33946
|
-
if (
|
|
34032
|
+
if (o.fromConfig) {
|
|
34033
|
+
content = configGateContent;
|
|
34034
|
+
} else if (isLegacyBlock) {
|
|
33947
34035
|
content = upsertManagedGitignoreBlock(preservedContent).content;
|
|
33948
34036
|
} else {
|
|
33949
34037
|
const rulesetVars = preserveRuleset ? gateSeedVars(
|
|
@@ -34012,8 +34100,8 @@ function registerBootstrapCommands(program3) {
|
|
|
34012
34100
|
}
|
|
34013
34101
|
}
|
|
34014
34102
|
let seedPrUrl;
|
|
34015
|
-
const scopedHead = o.execute && scopedPlan?.branch ? await verifyTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient) : null;
|
|
34016
|
-
if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && (seededToBranch > 0 || scopedContent !== void 0 && scopedHead !== null && scopedHead !== scopedPlan?.baseSha)) {
|
|
34103
|
+
const scopedHead = o.execute && scopedPlan?.branch && configGateAction?.action !== "skip" ? await verifyTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient) : null;
|
|
34104
|
+
if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && configGateAction?.action !== "skip" && (seededToBranch > 0 || scopedContent !== void 0 && scopedHead !== null && scopedHead !== scopedPlan?.baseSha)) {
|
|
34017
34105
|
await gh2(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-F", `delete_branch_on_merge=${MANAGED_DELETE_BRANCH_ON_MERGE}`]).catch(() => {
|
|
34018
34106
|
});
|
|
34019
34107
|
const openPrs = await gh2(["pr", "list", "--repo", repo, "--head", seedPlan.branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
|
|
@@ -34031,9 +34119,9 @@ function registerBootstrapCommands(program3) {
|
|
|
34031
34119
|
"--head",
|
|
34032
34120
|
seedPlan.branch,
|
|
34033
34121
|
"--title",
|
|
34034
|
-
onlyTarget ? `chore: propagate ${onlyManagedBlock ? "Hub-managed block in" : "org-owned"} ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
|
|
34122
|
+
o.fromConfig ? `chore: render ${onlyTarget} from registry gate config` : onlyTarget ? `chore: propagate ${onlyManagedBlock ? "Hub-managed block in" : "org-owned"} ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
|
|
34035
34123
|
"--body",
|
|
34036
|
-
onlyTarget ? `Auto-opened by \`mmi-cli devops bootstrap apply ${repo} --only ${onlyTarget} --execute\` (#3818).
|
|
34124
|
+
o.fromConfig ? `Auto-opened by \`mmi-cli devops bootstrap apply ${repo} --only gate.yml --from-config --execute\`. The gate is rendered from the repo's registry gate config through the ci-reconcile seed renderer at MMI-Hub@${seedSource.sha}. This PR carries only \`${onlyTarget}\` for review; no registry META or other controls were changed.` : onlyTarget ? `Auto-opened by \`mmi-cli devops bootstrap apply ${repo} --only ${onlyTarget} --execute\` (#3818).
|
|
34037
34125
|
|
|
34038
34126
|
${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owned \`${onlyTarget}\`` : `The org-owned \`${onlyTarget}\` seed`} is declared by MMI-Hub's \`skills/bootstrap/seeds/manifest.json\` at MMI-Hub@${seedSource.sha}; this PR reconciles that declared scope and preserves everything outside it. It carries that file and nothing else \u2014 no labels, ruleset, merge settings or registry META were touched.
|
|
34039
34127
|
|
|
@@ -34946,7 +35034,7 @@ function formatSuiteStatus(report) {
|
|
|
34946
35034
|
// src/suite-train.ts
|
|
34947
35035
|
var import_node_fs29 = require("node:fs");
|
|
34948
35036
|
var import_node_path29 = require("node:path");
|
|
34949
|
-
var
|
|
35037
|
+
var import_node_crypto13 = require("node:crypto");
|
|
34950
35038
|
var SCHEMA = "mmi-suite-cut/v1";
|
|
34951
35039
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
34952
35040
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
@@ -34982,7 +35070,7 @@ function readSuiteCut(path2) {
|
|
|
34982
35070
|
}
|
|
34983
35071
|
function writeSuiteCut(path2, record) {
|
|
34984
35072
|
(0, import_node_fs29.mkdirSync)((0, import_node_path29.dirname)(path2), { recursive: true });
|
|
34985
|
-
const temp = `${path2}.${(0,
|
|
35073
|
+
const temp = `${path2}.${(0, import_node_crypto13.randomBytes)(8).toString("hex")}.tmp`;
|
|
34986
35074
|
try {
|
|
34987
35075
|
(0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
34988
35076
|
(0, import_node_fs29.renameSync)(temp, path2);
|
|
@@ -38761,7 +38849,7 @@ init_github_client();
|
|
|
38761
38849
|
|
|
38762
38850
|
// src/issue-commands.ts
|
|
38763
38851
|
var import_node_fs34 = require("node:fs");
|
|
38764
|
-
var
|
|
38852
|
+
var import_node_crypto14 = require("node:crypto");
|
|
38765
38853
|
init_cli_shared();
|
|
38766
38854
|
init_clean_exit();
|
|
38767
38855
|
init_error_codes();
|
|
@@ -39234,7 +39322,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
39234
39322
|
const identity = `${spec.type}
|
|
39235
39323
|
${spec.title.trim()}
|
|
39236
39324
|
${spec.body ?? ""}`;
|
|
39237
|
-
const hash = (0,
|
|
39325
|
+
const hash = (0, import_node_crypto14.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
39238
39326
|
return `${batchKey}:${hash}`;
|
|
39239
39327
|
}
|
|
39240
39328
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -49303,14 +49391,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
49303
49391
|
}
|
|
49304
49392
|
|
|
49305
49393
|
// src/tenant-artifact.ts
|
|
49306
|
-
var
|
|
49394
|
+
var import_node_crypto15 = require("node:crypto");
|
|
49307
49395
|
var import_node_fs48 = require("node:fs");
|
|
49308
49396
|
var import_promises10 = require("node:fs/promises");
|
|
49309
49397
|
var import_node_path46 = require("node:path");
|
|
49310
49398
|
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}$/;
|
|
49311
49399
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
49312
49400
|
async function sha256File(path2) {
|
|
49313
|
-
const hash = (0,
|
|
49401
|
+
const hash = (0, import_node_crypto15.createHash)("sha256");
|
|
49314
49402
|
for await (const chunk of (0, import_node_fs48.createReadStream)(path2)) hash.update(chunk);
|
|
49315
49403
|
return hash.digest("hex");
|
|
49316
49404
|
}
|
|
@@ -49540,13 +49628,13 @@ async function executeWaveLand(plan, deps) {
|
|
|
49540
49628
|
|
|
49541
49629
|
// src/dist-drift.ts
|
|
49542
49630
|
var import_node_child_process18 = require("node:child_process");
|
|
49543
|
-
var
|
|
49631
|
+
var import_node_crypto17 = require("node:crypto");
|
|
49544
49632
|
var import_node_fs50 = require("node:fs");
|
|
49545
49633
|
var import_node_os22 = require("node:os");
|
|
49546
49634
|
var import_node_path48 = require("node:path");
|
|
49547
49635
|
|
|
49548
49636
|
// ../scripts/distribution-digest.mjs
|
|
49549
|
-
var
|
|
49637
|
+
var import_node_crypto16 = require("node:crypto");
|
|
49550
49638
|
var import_node_fs49 = require("node:fs");
|
|
49551
49639
|
var import_node_path47 = require("node:path");
|
|
49552
49640
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
@@ -49559,7 +49647,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
49559
49647
|
return target;
|
|
49560
49648
|
}
|
|
49561
49649
|
function digestFiles(files) {
|
|
49562
|
-
const hash = (0,
|
|
49650
|
+
const hash = (0, import_node_crypto16.createHash)("sha256");
|
|
49563
49651
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
49564
49652
|
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs49.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs49.readFileSync)(file.absolute);
|
|
49565
49653
|
hash.update(file.relative, "utf8");
|
|
@@ -49589,7 +49677,7 @@ var DIST_ARTIFACTS = [
|
|
|
49589
49677
|
];
|
|
49590
49678
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
49591
49679
|
var ABSENT = "absent";
|
|
49592
|
-
var sha256 = (bytes) => `sha256:${(0,
|
|
49680
|
+
var sha256 = (bytes) => `sha256:${(0, import_node_crypto17.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
49593
49681
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
49594
49682
|
const committed = committedBytes ? sha256(committedBytes) : ABSENT;
|
|
49595
49683
|
const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
|
package/package.json
CHANGED