@mutmutco/cli 3.25.0 → 3.26.0
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 +40 -24
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -6196,7 +6196,7 @@ function formatGcPlan(plan, apply) {
|
|
|
6196
6196
|
}
|
|
6197
6197
|
|
|
6198
6198
|
// src/project-model.ts
|
|
6199
|
-
var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-game", "non-deployable", "cli-tool", "worker"];
|
|
6199
|
+
var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "non-deployable", "cli-tool", "worker"];
|
|
6200
6200
|
var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "registry-publish", "content", "none"];
|
|
6201
6201
|
var RELEASE_TRACKS = ["full", "direct", "trunk"];
|
|
6202
6202
|
var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
|
|
@@ -6233,12 +6233,12 @@ function resolveDeployModel(meta, repo) {
|
|
|
6233
6233
|
const projectType = resolveProjectType(meta, repo);
|
|
6234
6234
|
if (projectType === "content" || meta?.class === "content") return "content";
|
|
6235
6235
|
if (projectType === "hub-service" || repoIsHub(repo)) return "hub-serverless";
|
|
6236
|
-
if (projectType === "desktop-game" || projectType === "non-deployable") return "none";
|
|
6236
|
+
if (projectType === "desktop-app" || projectType === "desktop-game" || projectType === "non-deployable") return "none";
|
|
6237
6237
|
if (projectType === "cli-tool") return "registry-publish";
|
|
6238
6238
|
return "tenant-container";
|
|
6239
6239
|
}
|
|
6240
6240
|
function projectTypeClearsWebProfile(projectType, deployModel) {
|
|
6241
|
-
return projectType === "content" || projectType === "desktop-game" || projectType === "non-deployable" || projectType === "cli-tool" || deployModel === "content" || deployModel === "none" || deployModel === "registry-publish";
|
|
6241
|
+
return projectType === "content" || projectType === "desktop-app" || projectType === "desktop-game" || projectType === "non-deployable" || projectType === "cli-tool" || deployModel === "content" || deployModel === "none" || deployModel === "registry-publish";
|
|
6242
6242
|
}
|
|
6243
6243
|
function inferReleaseTrackFromBranches(hints) {
|
|
6244
6244
|
if (hints.hasRcBranch) return void 0;
|
|
@@ -9608,6 +9608,10 @@ function seedMatchesDeployModel(seed, deployModel) {
|
|
|
9608
9608
|
if (!seed.deployModels?.length) return true;
|
|
9609
9609
|
return deployModel != null && seed.deployModels.includes(deployModel);
|
|
9610
9610
|
}
|
|
9611
|
+
function seedMatchesProjectType(seed, projectType) {
|
|
9612
|
+
if (!seed.projectTypes?.length) return true;
|
|
9613
|
+
return projectType != null && seed.projectTypes.includes(projectType);
|
|
9614
|
+
}
|
|
9611
9615
|
function planSeedAction(seed, exists) {
|
|
9612
9616
|
if (seed.source === "fanout") {
|
|
9613
9617
|
return { target: seed.target, action: "skip", ownership: "fanout", reason: "delivered by the fanout pipeline" };
|
|
@@ -9620,18 +9624,25 @@ function planSeedAction(seed, exists) {
|
|
|
9620
9624
|
}
|
|
9621
9625
|
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" };
|
|
9622
9626
|
}
|
|
9623
|
-
function reconcileSeedAction(action, content, isManagedBlock) {
|
|
9627
|
+
function reconcileSeedAction(action, content, isManagedBlock, remoteContent = null) {
|
|
9624
9628
|
if (action.action === "skip") return action;
|
|
9625
9629
|
if (content == null) {
|
|
9626
9630
|
return { ...action, action: "skip", reason: "no resolvable content" };
|
|
9627
9631
|
}
|
|
9628
|
-
if (isManagedBlock)
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
+
if (!isManagedBlock) {
|
|
9633
|
+
const unfilled = missingPlaceholders(content);
|
|
9634
|
+
if (unfilled.length) {
|
|
9635
|
+
return { ...action, action: "skip", reason: `unfilled: ${unfilled.join(", ")} \u2014 pass --var` };
|
|
9636
|
+
}
|
|
9637
|
+
}
|
|
9638
|
+
if (action.action === "update" && remoteContent != null && sameIgnoringEol(content, remoteContent)) {
|
|
9639
|
+
return { ...action, action: "skip", reason: "already current" };
|
|
9632
9640
|
}
|
|
9633
9641
|
return action;
|
|
9634
9642
|
}
|
|
9643
|
+
function sameIgnoringEol(a, b) {
|
|
9644
|
+
return a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
|
|
9645
|
+
}
|
|
9635
9646
|
function renderSeedPlan(actions) {
|
|
9636
9647
|
const lines = ["bootstrap apply \u2014 seed plan (dry-run; no mutations):"];
|
|
9637
9648
|
for (const a of actions) {
|
|
@@ -11927,7 +11938,6 @@ function bootstrapPlan(repo, repoClass) {
|
|
|
11927
11938
|
{ label: `apply branch protection / allowlist: ${protectedBranches}`, gated: true },
|
|
11928
11939
|
{ label: "attach GitHub Project v2 and register Hub registry META", gated: true },
|
|
11929
11940
|
{ label: "seed README.md and architecture.md", gated: true },
|
|
11930
|
-
{ label: "commit .cursor/rules/<repo>.mdc", gated: true },
|
|
11931
11941
|
{ label: `register fanout target on ${repoClass === "content" ? "main" : "development"}`, gated: true }
|
|
11932
11942
|
];
|
|
11933
11943
|
}
|
|
@@ -14957,9 +14967,6 @@ async function contentText(deps, repo, branch, path2) {
|
|
|
14957
14967
|
return null;
|
|
14958
14968
|
}
|
|
14959
14969
|
}
|
|
14960
|
-
function repoSlugFromFullName(repo) {
|
|
14961
|
-
return (repo.includes("/") ? repo.split("/")[1] : repo).toLowerCase();
|
|
14962
|
-
}
|
|
14963
14970
|
async function getProtection(deps, repo, branch) {
|
|
14964
14971
|
try {
|
|
14965
14972
|
return await deps.client.rest("GET", `repos/${repo}/branches/${branch}/protection`) ?? {};
|
|
@@ -15109,13 +15116,6 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
15109
15116
|
label: "architecture.md is filled (no template placeholders)",
|
|
15110
15117
|
detail: archPlaceholders.length ? `unfilled: ${archPlaceholders.join(", ")}` : void 0
|
|
15111
15118
|
});
|
|
15112
|
-
const agentRulesPath = `.cursor/rules/${repoSlugFromFullName(repo)}.mdc`;
|
|
15113
|
-
const agentRulesOk = await contentExists2(deps, repo, baseBranch, agentRulesPath);
|
|
15114
|
-
checks.push({
|
|
15115
|
-
ok: agentRulesOk,
|
|
15116
|
-
label: "Cursor agent rules file exists",
|
|
15117
|
-
detail: agentRulesOk ? void 0 : `expected: ${agentRulesPath}`
|
|
15118
|
-
});
|
|
15119
15119
|
const labels = await restPagedJson2(deps, `repos/${repo}/labels`, []);
|
|
15120
15120
|
const labelNames = new Set(labels.map((l) => l.name));
|
|
15121
15121
|
for (const label of requiredLabels) {
|
|
@@ -15699,10 +15699,16 @@ function attestedLine(att) {
|
|
|
15699
15699
|
var CONTRACT_UNDECLARED_LINE = "No runtime secrets declared \u2014 declare requiredRuntimeSecrets (a per-stage name map) in the registry META, or attest the app needs none with an explicit empty stage map ({ dev: [], rc: [], main: [] }).";
|
|
15700
15700
|
function appGapsFor(meta, model, slug, projectType, mainDeployFact) {
|
|
15701
15701
|
const attested = appAttestationOf(meta);
|
|
15702
|
-
const isTenantWeb = !(projectType === "content" || model === "content") && projectType !== "desktop-game" && projectType !== "cli-tool" && !(projectType === "non-deployable" || model === "none") && model !== "hub-serverless" && model !== "serverless" && model !== "registry-publish";
|
|
15702
|
+
const isTenantWeb = !(projectType === "content" || model === "content") && projectType !== "desktop-app" && projectType !== "desktop-game" && projectType !== "cli-tool" && !(projectType === "non-deployable" || model === "none") && model !== "hub-serverless" && model !== "serverless" && model !== "registry-publish";
|
|
15703
15703
|
const contractUndeclared = isTenantWeb && Boolean(meta) && !hasRuntimeSecretContract(meta?.requiredRuntimeSecrets);
|
|
15704
15704
|
if (attested) return contractUndeclared ? [attestedLine(attested), CONTRACT_UNDECLARED_LINE] : [attestedLine(attested)];
|
|
15705
15705
|
if (projectType === "content" || model === "content") return ["Content repo: keep app-owned work to docs/content changes; release train does not apply."];
|
|
15706
|
+
if (projectType === "desktop-app") {
|
|
15707
|
+
return [
|
|
15708
|
+
"Desktop app: no Hub deploy coords, Google OAuth, DWD, or tenant control by default; keep app-owned installer/packaging outside the tenant train.",
|
|
15709
|
+
"Keep README.md and architecture.md explicit about the per-OS build and distribution path (installer, and any registry publish alongside it) so readiness does not guess web infra."
|
|
15710
|
+
];
|
|
15711
|
+
}
|
|
15706
15712
|
if (projectType === "desktop-game") {
|
|
15707
15713
|
return [
|
|
15708
15714
|
"Desktop game: no Hub deploy coords, Google OAuth, DWD, or tenant control by default; keep app-owned release packaging outside the tenant train.",
|
|
@@ -15836,7 +15842,7 @@ function buildV2HealPatch(repoOrSlug, meta, mainDeployFact) {
|
|
|
15836
15842
|
patch.requiredRuntimeSecrets = next;
|
|
15837
15843
|
}
|
|
15838
15844
|
}
|
|
15839
|
-
const appOwnedGaps = confidentType ? appGapsFor(meta, model, slug, confidentType, mainDeployFact) : [`Project type is unset and not derivable \u2014 classify with \`mmi-cli project set ${repo} --project-type <web-app|hub-service|content|desktop-game|non-deployable|cli-tool|worker> --deploy-model <tenant-container|solo-container|hub-serverless|serverless|registry-publish|content|none>\` before heal completes the v2 fields (prevents defaulting a non-web repo to tenant-container).`];
|
|
15845
|
+
const appOwnedGaps = confidentType ? appGapsFor(meta, model, slug, confidentType, mainDeployFact) : [`Project type is unset and not derivable \u2014 classify with \`mmi-cli project set ${repo} --project-type <web-app|hub-service|content|desktop-app|desktop-game|non-deployable|cli-tool|worker> --deploy-model <tenant-container|solo-container|hub-serverless|serverless|registry-publish|content|none>\` before heal completes the v2 fields (prevents defaulting a non-web repo to tenant-container).`];
|
|
15840
15846
|
if (boardRegistryGaps(meta).length) appOwnedGaps.unshift(boardRegistryGapMessage(repo));
|
|
15841
15847
|
return { slug, patch, appOwnedGaps };
|
|
15842
15848
|
}
|
|
@@ -22937,6 +22943,7 @@ bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: i
|
|
|
22937
22943
|
const actions = [];
|
|
22938
22944
|
const applied = [];
|
|
22939
22945
|
let applyDeployModel;
|
|
22946
|
+
let applyProjectType;
|
|
22940
22947
|
try {
|
|
22941
22948
|
const payload = buildRegisterPayload(repo, o.class, vars, {
|
|
22942
22949
|
projectType: o.projectType || void 0,
|
|
@@ -22944,6 +22951,7 @@ bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: i
|
|
|
22944
22951
|
releaseTrack: o.releaseTrack || void 0
|
|
22945
22952
|
});
|
|
22946
22953
|
applyDeployModel = payload.deployModel;
|
|
22954
|
+
applyProjectType = payload.projectType;
|
|
22947
22955
|
} catch {
|
|
22948
22956
|
}
|
|
22949
22957
|
let seedPlan = { mode: "direct", ref: baseBranch, reason: "dry-run (no protection probe)" };
|
|
@@ -22968,6 +22976,7 @@ bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: i
|
|
|
22968
22976
|
for (const seed of manifest.seeds) {
|
|
22969
22977
|
if (!seed.classes.includes(o.class)) continue;
|
|
22970
22978
|
if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
|
|
22979
|
+
if (!seedMatchesProjectType(seed, applyProjectType)) continue;
|
|
22971
22980
|
const resolved = { ...seed, target: seed.target.replace("{{REPO_SLUG}}", slug) };
|
|
22972
22981
|
let exists = false;
|
|
22973
22982
|
let sha;
|
|
@@ -22991,7 +23000,7 @@ bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: i
|
|
|
22991
23000
|
const planned = planSeedAction(resolved, exists);
|
|
22992
23001
|
const isBlock = resolved.source === "managed-block";
|
|
22993
23002
|
const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars, readFile4) : null;
|
|
22994
|
-
const action = reconcileSeedAction(planned, content, isBlock);
|
|
23003
|
+
const action = reconcileSeedAction(planned, content, isBlock, remoteContent);
|
|
22995
23004
|
actions.push(action);
|
|
22996
23005
|
if (o.execute && (action.action === "create" || action.action === "update")) {
|
|
22997
23006
|
await gh(contentPutArgs(repo, resolved.target, content, seedPlan.ref, action.action === "update" ? sha : void 0));
|
|
@@ -23024,10 +23033,17 @@ bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: i
|
|
|
23024
23033
|
]);
|
|
23025
23034
|
seedPrUrl = created.url;
|
|
23026
23035
|
}
|
|
23036
|
+
let autoMergeEnabled = true;
|
|
23027
23037
|
await gh(["pr", "merge", seedPrUrl, "--repo", repo, "--auto", "--squash"]).catch((e) => {
|
|
23028
|
-
|
|
23038
|
+
const message = String(e.message ?? "");
|
|
23039
|
+
if (/already/i.test(message)) return;
|
|
23040
|
+
if (/clean status|enablePullRequestAutoMerge/i.test(message)) {
|
|
23041
|
+
autoMergeEnabled = false;
|
|
23042
|
+
return;
|
|
23043
|
+
}
|
|
23044
|
+
throw e;
|
|
23029
23045
|
});
|
|
23030
|
-
applied.push(`seed: PR ${seedPrUrl} (base ${baseBranch} protected; auto-merge enabled)`);
|
|
23046
|
+
applied.push(autoMergeEnabled ? `seed: PR ${seedPrUrl} (base ${baseBranch} protected; auto-merge enabled)` : `seed: PR ${seedPrUrl} (base ${baseBranch} protected; auto-merge refused \u2014 PR is already clean. Land it: mmi-cli pr land <n> --repo ${repo})`);
|
|
23031
23047
|
}
|
|
23032
23048
|
if (o.execute && o.class === "deployable") {
|
|
23033
23049
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.0",
|
|
4
4
|
"description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|