@mutmutco/cli 2.30.0 → 2.31.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/index.cjs +19 -9
- package/dist/main.cjs +337 -49
- package/dist/saga.cjs +19 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -23,18 +23,28 @@ var init_compat = __esm({
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
// src/client-version.ts
|
|
26
|
-
function
|
|
26
|
+
function resolveClientVersionManifestCandidates(distDir = __dirname) {
|
|
27
|
+
return [
|
|
28
|
+
(0, import_node_path.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
|
|
29
|
+
(0, import_node_path.join)(distDir, "..", "..", ".cursor-plugin", "plugin.json"),
|
|
30
|
+
(0, import_node_path.join)(distDir, "..", "..", ".codex-plugin", "plugin.json"),
|
|
31
|
+
(0, import_node_path.join)(distDir, "..", "package.json")
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
function readVersionFromManifest(path) {
|
|
27
35
|
try {
|
|
28
|
-
const
|
|
29
|
-
return
|
|
36
|
+
const version = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8")).version;
|
|
37
|
+
return typeof version === "string" && version.trim() ? version.trim() : null;
|
|
30
38
|
} catch {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function resolveClientVersion() {
|
|
43
|
+
for (const manifest of resolveClientVersionManifestCandidates()) {
|
|
44
|
+
const version = readVersionFromManifest(manifest);
|
|
45
|
+
if (version) return version;
|
|
37
46
|
}
|
|
47
|
+
return "0.0.0";
|
|
38
48
|
}
|
|
39
49
|
var import_node_fs, import_node_path;
|
|
40
50
|
var init_client_version = __esm({
|
package/dist/main.cjs
CHANGED
|
@@ -3473,19 +3473,29 @@ function versionAtLeast(v, min) {
|
|
|
3473
3473
|
}
|
|
3474
3474
|
|
|
3475
3475
|
// src/client-version.ts
|
|
3476
|
-
function
|
|
3476
|
+
function resolveClientVersionManifestCandidates(distDir = __dirname) {
|
|
3477
|
+
return [
|
|
3478
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
|
|
3479
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".cursor-plugin", "plugin.json"),
|
|
3480
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".codex-plugin", "plugin.json"),
|
|
3481
|
+
(0, import_node_path2.join)(distDir, "..", "package.json")
|
|
3482
|
+
];
|
|
3483
|
+
}
|
|
3484
|
+
function readVersionFromManifest(path2) {
|
|
3477
3485
|
try {
|
|
3478
|
-
const
|
|
3479
|
-
return
|
|
3486
|
+
const version = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8")).version;
|
|
3487
|
+
return typeof version === "string" && version.trim() ? version.trim() : null;
|
|
3480
3488
|
} catch {
|
|
3481
|
-
|
|
3482
|
-
const pkg = (0, import_node_path2.join)(__dirname, "..", "package.json");
|
|
3483
|
-
return JSON.parse((0, import_node_fs2.readFileSync)(pkg, "utf8")).version || "0.0.0";
|
|
3484
|
-
} catch {
|
|
3485
|
-
return "0.0.0";
|
|
3486
|
-
}
|
|
3489
|
+
return null;
|
|
3487
3490
|
}
|
|
3488
3491
|
}
|
|
3492
|
+
function resolveClientVersion() {
|
|
3493
|
+
for (const manifest of resolveClientVersionManifestCandidates()) {
|
|
3494
|
+
const version = readVersionFromManifest(manifest);
|
|
3495
|
+
if (version) return version;
|
|
3496
|
+
}
|
|
3497
|
+
return "0.0.0";
|
|
3498
|
+
}
|
|
3489
3499
|
function clientVersionHeaders() {
|
|
3490
3500
|
return { [CLIENT_VERSION_HEADER]: resolveClientVersion() };
|
|
3491
3501
|
}
|
|
@@ -7268,10 +7278,59 @@ function buildPanelPlan(input) {
|
|
|
7268
7278
|
}
|
|
7269
7279
|
|
|
7270
7280
|
// src/gc.ts
|
|
7281
|
+
var DEFERRED_SWEEP_COMMAND = "mmi-cli gc --apply";
|
|
7282
|
+
var DEFERRED_NOTE = "Worktree cleanup deferred \u2014 close this folder in your editor (or run cleanup from a shell outside it), then rerun mmi-cli gc --apply.";
|
|
7271
7283
|
var WORKTREE_LOCK_RE = /EPERM|EBUSY|EACCES|ENOTEMPTY|permission denied|access is denied|used by another process|resource busy|directory not empty/i;
|
|
7272
7284
|
function isWorktreeLockError(error) {
|
|
7273
7285
|
return WORKTREE_LOCK_RE.test(error instanceof Error ? error.message : String(error));
|
|
7274
7286
|
}
|
|
7287
|
+
function deferredWorktreesRegistryPath(gitDir) {
|
|
7288
|
+
const base2 = gitDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7289
|
+
return `${base2}/mmi-deferred-worktrees.json`;
|
|
7290
|
+
}
|
|
7291
|
+
function parseDeferredWorktreesFile(text) {
|
|
7292
|
+
const parsed = JSON.parse(text);
|
|
7293
|
+
if (!parsed || !Array.isArray(parsed.entries)) return [];
|
|
7294
|
+
return parsed.entries.filter((e) => Boolean(e) && typeof e === "object" && typeof e.path === "string" && typeof e.branch === "string" && e.reason === "lock-held").map((e) => ({ ...e, registeredAt: e.registeredAt || (/* @__PURE__ */ new Date(0)).toISOString() }));
|
|
7295
|
+
}
|
|
7296
|
+
function serializeDeferredWorktrees(entries) {
|
|
7297
|
+
return `${JSON.stringify({ entries }, null, 2)}
|
|
7298
|
+
`;
|
|
7299
|
+
}
|
|
7300
|
+
function deferredPathKey(path2) {
|
|
7301
|
+
return normPath(path2);
|
|
7302
|
+
}
|
|
7303
|
+
function isPersistentWorktreeLockFailure(outcome) {
|
|
7304
|
+
return outcome.status === "failed" && isWorktreeLockError(outcome.error);
|
|
7305
|
+
}
|
|
7306
|
+
async function registerDeferredWorktree(store, entry) {
|
|
7307
|
+
const existing = await store.read();
|
|
7308
|
+
const key = deferredPathKey(entry.path);
|
|
7309
|
+
const already = existing.some((e) => deferredPathKey(e.path) === key);
|
|
7310
|
+
if (already) return { entries: existing, newlyRegistered: false };
|
|
7311
|
+
const next = {
|
|
7312
|
+
...entry,
|
|
7313
|
+
registeredAt: entry.registeredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7314
|
+
reason: "lock-held"
|
|
7315
|
+
};
|
|
7316
|
+
const entries = [...existing, next];
|
|
7317
|
+
await store.write(entries);
|
|
7318
|
+
return { entries, newlyRegistered: true };
|
|
7319
|
+
}
|
|
7320
|
+
async function sweepDeferredWorktrees(store, deps) {
|
|
7321
|
+
if (!store) return { removed: [], stillDeferred: [] };
|
|
7322
|
+
const entries = await store.read();
|
|
7323
|
+
if (!entries.length) return { removed: [], stillDeferred: [] };
|
|
7324
|
+
const removed = [];
|
|
7325
|
+
const stillDeferred = [];
|
|
7326
|
+
for (const entry of entries) {
|
|
7327
|
+
const outcome = await removeWorktreeWithRecovery(entry.path, deps);
|
|
7328
|
+
if (outcome.status === "removed") removed.push(entry.path);
|
|
7329
|
+
else stillDeferred.push(entry);
|
|
7330
|
+
}
|
|
7331
|
+
await store.write(stillDeferred);
|
|
7332
|
+
return { removed, stillDeferred };
|
|
7333
|
+
}
|
|
7275
7334
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7276
7335
|
async function removeWorktreeWithRecovery(wtPath, deps) {
|
|
7277
7336
|
const maxAttempts = deps.maxAttempts ?? 3;
|
|
@@ -7337,7 +7396,9 @@ function summarizePrMergeCleanupStatus(input) {
|
|
|
7337
7396
|
if (input.remoteBranch.status === "failed") return "warnings";
|
|
7338
7397
|
if (input.localBranch.status === "failed") return "warnings";
|
|
7339
7398
|
if (input.localBranch.reason === "worktree-removal-failed") return "warnings";
|
|
7399
|
+
if (input.localBranch.reason === "worktree-removal-deferred") return "warnings";
|
|
7340
7400
|
if (input.worktree?.status === "failed") return "warnings";
|
|
7401
|
+
if (input.worktree?.status === "deferred") return "warnings";
|
|
7341
7402
|
return "clean";
|
|
7342
7403
|
}
|
|
7343
7404
|
function buildPrMergeResultPayload(input) {
|
|
@@ -7600,6 +7661,12 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
7600
7661
|
const wtPath = selectPrMergeCleanupWorktree(branch, beforeWorktrees, afterWorktrees, options.startingPath);
|
|
7601
7662
|
const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath);
|
|
7602
7663
|
const git = (args) => safeCwd ? options.execGit(["-C", safeCwd, ...args]) : options.execGit(args);
|
|
7664
|
+
const removeDeps = {
|
|
7665
|
+
git,
|
|
7666
|
+
sleep: options.sleep ?? defaultSleep,
|
|
7667
|
+
removeWorktreeDir: options.removeWorktreeDir
|
|
7668
|
+
};
|
|
7669
|
+
await sweepDeferredWorktrees(options.deferredStore, removeDeps).catch(() => void 0);
|
|
7603
7670
|
const mainWorktreePath = beforeWorktrees[0]?.path ?? afterWorktrees[0]?.path;
|
|
7604
7671
|
const mainWorktreeTarget = Boolean(wtPath && mainWorktreePath && samePath(wtPath, mainWorktreePath));
|
|
7605
7672
|
if (wtPath && mainWorktreeTarget) {
|
|
@@ -7620,6 +7687,24 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
7620
7687
|
});
|
|
7621
7688
|
if (outcome.status === "removed") {
|
|
7622
7689
|
report.worktree = { path: wtPath, status: "removed", stageTeardown, recovery: outcome.recovery };
|
|
7690
|
+
} else if (isPersistentWorktreeLockFailure(outcome) && options.deferredStore) {
|
|
7691
|
+
const { newlyRegistered } = await registerDeferredWorktree(options.deferredStore, {
|
|
7692
|
+
path: wtPath,
|
|
7693
|
+
branch,
|
|
7694
|
+
reason: "lock-held"
|
|
7695
|
+
});
|
|
7696
|
+
report.worktree = {
|
|
7697
|
+
path: wtPath,
|
|
7698
|
+
status: "deferred",
|
|
7699
|
+
reason: "lock-held",
|
|
7700
|
+
error: outcome.error,
|
|
7701
|
+
deferredNote: DEFERRED_NOTE,
|
|
7702
|
+
deferredSweepCommand: DEFERRED_SWEEP_COMMAND,
|
|
7703
|
+
...newlyRegistered ? { safeCleanupCommand: safeWorktreeRemoveCommand(safeCwd, wtPath) } : {},
|
|
7704
|
+
stageTeardown
|
|
7705
|
+
};
|
|
7706
|
+
report.localBranch = { name: branch, status: "not-attempted", reason: "worktree-removal-deferred" };
|
|
7707
|
+
return report;
|
|
7623
7708
|
} else {
|
|
7624
7709
|
report.worktree = {
|
|
7625
7710
|
path: wtPath,
|
|
@@ -8419,6 +8504,7 @@ var CURSOR_PLUGIN_INSTALL_LABEL = "Cursor Team Marketplace plugin install";
|
|
|
8419
8504
|
var CURSOR_MARKETPLACE_INSTALL_GUIDE = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/cursor-marketplace-install.md";
|
|
8420
8505
|
var CURSOR_PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
|
|
8421
8506
|
var CURSOR_HOOKS_JSON_REL = "hooks/hooks.json";
|
|
8507
|
+
var CURSOR_HOOK_CLI_LABEL = "Cursor hook CLI bundle";
|
|
8422
8508
|
function joinCachePath(root, ...parts) {
|
|
8423
8509
|
const sep = root.includes("\\") ? "\\" : "/";
|
|
8424
8510
|
return [root.replace(/[\\/]+$/, ""), ...parts].join(sep);
|
|
@@ -8474,6 +8560,20 @@ function buildCursorPluginInstallCheck(input) {
|
|
|
8474
8560
|
}
|
|
8475
8561
|
return { ...base2, cacheRoot: input.cacheRoot, pins: input.pins };
|
|
8476
8562
|
}
|
|
8563
|
+
function buildCursorHookCliCheck(input) {
|
|
8564
|
+
const fix = "update the MMI Team Marketplace plugin (releases ship cli/dist under plugins/mmi/cli/dist) or install mmi-cli on PATH \u2014 Cursor hooks fall back to PATH when the bundled CLI is missing";
|
|
8565
|
+
const base2 = { ok: true, label: CURSOR_HOOK_CLI_LABEL, fix };
|
|
8566
|
+
if (!input.isOrgRepo) return base2;
|
|
8567
|
+
const shouldCheck = input.surface === "cursor" || input.pins.length > 0;
|
|
8568
|
+
if (!shouldCheck) return base2;
|
|
8569
|
+
if (input.pins.length === 0) {
|
|
8570
|
+
if (input.surface === "cursor" && !input.mmiCliOnPath) return { ...base2, ok: false };
|
|
8571
|
+
return base2;
|
|
8572
|
+
}
|
|
8573
|
+
const missingBundle = input.pins.some((p) => !p.hasCliBundle);
|
|
8574
|
+
if (missingBundle && !input.mmiCliOnPath) return { ...base2, ok: false };
|
|
8575
|
+
return base2;
|
|
8576
|
+
}
|
|
8477
8577
|
var HUB_COMPAT_FIX = "update mmi-cli (npm i -g @mutmutco/cli) / refresh the MMI plugin, then rerun doctor";
|
|
8478
8578
|
function buildHubCompatCheck(input) {
|
|
8479
8579
|
const label = "Hub compatibility (client version vs Hub minimum)";
|
|
@@ -9502,7 +9602,8 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
9502
9602
|
const requiredChecks2 = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
9503
9603
|
const checks2 = await waitForRequiredTrainChecks(deps, ctx, releaseSha2, requiredChecks2);
|
|
9504
9604
|
await deps.run("git", ["push", "origin", "main"]);
|
|
9505
|
-
const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
9605
|
+
const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
9606
|
+
await verifyPublishedRelease(deps, ctx.repo, tag2, "main", releaseSha2);
|
|
9506
9607
|
const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
9507
9608
|
const autoRunSince2 = (deps.now ?? Date.now)();
|
|
9508
9609
|
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2, "report");
|
|
@@ -9571,7 +9672,8 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
9571
9672
|
const requiredChecks2 = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
9572
9673
|
const checks2 = await waitForRequiredTrainChecks(deps, ctx, releaseSha2, requiredChecks2);
|
|
9573
9674
|
await deps.run("git", ["push", "origin", "main"]);
|
|
9574
|
-
const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
9675
|
+
const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
9676
|
+
await verifyPublishedRelease(deps, ctx.repo, tag2, "main", releaseSha2);
|
|
9575
9677
|
const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
9576
9678
|
const autoRunSince2 = (deps.now ?? Date.now)();
|
|
9577
9679
|
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2, "report");
|
|
@@ -9987,6 +10089,11 @@ async function deriveHotfixVersion(deps) {
|
|
|
9987
10089
|
function hotfixBranch(tag) {
|
|
9988
10090
|
return `hotfix/${tag}`;
|
|
9989
10091
|
}
|
|
10092
|
+
async function resolveHotfixDeployModel(deps, ctx) {
|
|
10093
|
+
const load = await loadProjectMeta(deps, ctx);
|
|
10094
|
+
const meta = load.status === "ok" ? load.meta : null;
|
|
10095
|
+
return resolveDeployModel2(meta, ctx.repo);
|
|
10096
|
+
}
|
|
9990
10097
|
async function findHotfixPr(deps, ctx, tag) {
|
|
9991
10098
|
const out = await deps.run("gh", [
|
|
9992
10099
|
"pr",
|
|
@@ -10024,6 +10131,7 @@ async function resolveHotfixSource(deps, ctx, from) {
|
|
|
10024
10131
|
}
|
|
10025
10132
|
async function runHotfixStart(deps, options) {
|
|
10026
10133
|
const ctx = await buildTrainApplyContext(deps);
|
|
10134
|
+
const deployModel = await resolveHotfixDeployModel(deps, ctx);
|
|
10027
10135
|
const status = await deps.run("git", ["status", "--porcelain"]);
|
|
10028
10136
|
if (status.trim()) throw new Error("working tree must be clean before hotfix start");
|
|
10029
10137
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
@@ -10061,18 +10169,23 @@ async function runHotfixStart(deps, options) {
|
|
|
10061
10169
|
throw new Error(`cherry-pick of ${label} onto ${branch} conflicted \u2014 aborted; resolve by hand on a manual hotfix branch, keeping the -x trailer (${e.message ?? e})`);
|
|
10062
10170
|
}
|
|
10063
10171
|
notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10172
|
+
if (deployModel === "hub-serverless") {
|
|
10173
|
+
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
|
|
10174
|
+
const changedFiles = (await deps.run("node", ["scripts/release-distribution.mjs", "changed-files"])).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
10175
|
+
await deps.run("git", ["add", "--", ...changedFiles]);
|
|
10176
|
+
const staged = await deps.run("git", ["diff", "--cached", "--name-only"]);
|
|
10177
|
+
if (staged.trim()) {
|
|
10178
|
+
await deps.run("git", ["commit", "-m", `hotfix ${tag}: lock plugin set + @mutmutco/cli distribution to ${version}`]);
|
|
10179
|
+
notes.push(`distribution prepared + committed for ${version} (${changedFiles.length} locked paths)`);
|
|
10180
|
+
} else {
|
|
10181
|
+
notes.push("distribution prepare produced no changes \u2014 nothing extra committed");
|
|
10182
|
+
}
|
|
10071
10183
|
} else {
|
|
10072
|
-
notes.push(
|
|
10184
|
+
notes.push(`distribution bump skipped (deployModel=${deployModel}, Hub-only step)`);
|
|
10073
10185
|
}
|
|
10074
10186
|
await deps.run("git", ["push", "-u", "origin", branch]);
|
|
10075
10187
|
}
|
|
10188
|
+
const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : "";
|
|
10076
10189
|
const prUrl = clean2(await deps.run("gh", [
|
|
10077
10190
|
"pr",
|
|
10078
10191
|
"create",
|
|
@@ -10085,7 +10198,7 @@ async function runHotfixStart(deps, options) {
|
|
|
10085
10198
|
"--title",
|
|
10086
10199
|
`[hotfix] ${tag}`,
|
|
10087
10200
|
"--body",
|
|
10088
|
-
`Hotfix ${tag}: cherry-pick of ${label} onto origin/main
|
|
10201
|
+
`Hotfix ${tag}: cherry-pick of ${label} onto origin/main${bumpNote}.
|
|
10089
10202
|
|
|
10090
10203
|
Merge this PR (human-initiated), then run \`mmi-cli hotfix release ${tag}\`.`
|
|
10091
10204
|
]));
|
|
@@ -10130,6 +10243,7 @@ async function watchReleaseRun(deps, ctx, workflow, sha) {
|
|
|
10130
10243
|
}
|
|
10131
10244
|
async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
10132
10245
|
const ctx = await buildTrainApplyContext(deps);
|
|
10246
|
+
const deployModel = await resolveHotfixDeployModel(deps, ctx);
|
|
10133
10247
|
const { tag, version } = normalizeHotfixVersion(versionInput);
|
|
10134
10248
|
const status = await deps.run("git", ["status", "--porcelain"]);
|
|
10135
10249
|
if (status.trim()) throw new Error("working tree must be clean before hotfix release");
|
|
@@ -10164,33 +10278,74 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
10164
10278
|
}
|
|
10165
10279
|
}
|
|
10166
10280
|
const runs = [];
|
|
10167
|
-
|
|
10168
|
-
|
|
10281
|
+
let deployNote;
|
|
10282
|
+
if (deployModel === "hub-serverless") {
|
|
10283
|
+
for (const workflow of HOTFIX_RELEASE_WORKFLOWS) {
|
|
10284
|
+
runs.push(await watchReleaseRun(deps, ctx, workflow, mergedSha));
|
|
10285
|
+
}
|
|
10286
|
+
deployNote = "watched release-triggered deploy.yml + publish.yml";
|
|
10287
|
+
} else if (deployModel === "tenant-container" || deployModel === "solo-container") {
|
|
10288
|
+
const dispatch = await dispatchDeploy(
|
|
10289
|
+
deps,
|
|
10290
|
+
ctx,
|
|
10291
|
+
"main",
|
|
10292
|
+
"main",
|
|
10293
|
+
deployModel,
|
|
10294
|
+
true,
|
|
10295
|
+
(deps.now ?? Date.now)(),
|
|
10296
|
+
mergedSha,
|
|
10297
|
+
"report"
|
|
10298
|
+
);
|
|
10299
|
+
deployNote = dispatch.note;
|
|
10300
|
+
runs.push({
|
|
10301
|
+
workflow: "tenant-deploy.yml",
|
|
10302
|
+
url: dispatch.runUrl,
|
|
10303
|
+
conclusion: dispatch.deployStatus === "success" ? "success" : dispatch.deployStatus === "failure" ? "failure" : dispatch.deployStatus ?? "pending"
|
|
10304
|
+
});
|
|
10305
|
+
} else {
|
|
10306
|
+
deployNote = `no hotfix deploy dispatch for deployModel=${deployModel} \u2014 prod deploy is repo-specific`;
|
|
10169
10307
|
}
|
|
10170
|
-
const previousRef = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
10171
10308
|
let verifyNote;
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
attempt
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10309
|
+
if (deployModel === "hub-serverless") {
|
|
10310
|
+
const previousRef = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
10311
|
+
const publishSucceeded = runs.find((r) => r.workflow === "publish.yml")?.conclusion === "success";
|
|
10312
|
+
try {
|
|
10313
|
+
await deps.run("git", ["-c", "advice.detachedHead=false", "checkout", tag]);
|
|
10314
|
+
const verifyArgs = ["scripts/release-distribution.mjs", "verify", version, ...publishSucceeded ? [] : ["--skip-npm-view"]];
|
|
10315
|
+
const sleep = sleeper(deps);
|
|
10316
|
+
let attempt = 0;
|
|
10317
|
+
for (; ; ) {
|
|
10318
|
+
attempt++;
|
|
10319
|
+
try {
|
|
10320
|
+
await deps.run("node", verifyArgs);
|
|
10321
|
+
break;
|
|
10322
|
+
} catch (err) {
|
|
10323
|
+
if (attempt >= HOTFIX_VERIFY_ATTEMPTS) throw err;
|
|
10324
|
+
await sleep(HOTFIX_VERIFY_RETRY_MS);
|
|
10325
|
+
}
|
|
10186
10326
|
}
|
|
10327
|
+
const retried = attempt > 1 ? `, after ${attempt} attempts (npm propagation lag)` : "";
|
|
10328
|
+
verifyNote = `distribution verified at ${tag}${publishSucceeded ? ` (npm included${retried})` : " (npm view skipped \u2014 publish run not confirmed)"}`;
|
|
10329
|
+
} finally {
|
|
10330
|
+
if (previousRef && previousRef !== "HEAD") await deps.run("git", ["checkout", previousRef]);
|
|
10187
10331
|
}
|
|
10188
|
-
|
|
10189
|
-
verifyNote = `distribution
|
|
10190
|
-
} finally {
|
|
10191
|
-
if (previousRef && previousRef !== "HEAD") await deps.run("git", ["checkout", previousRef]);
|
|
10332
|
+
} else {
|
|
10333
|
+
verifyNote = `distribution verify skipped (deployModel=${deployModel}, Hub-only step)`;
|
|
10192
10334
|
}
|
|
10193
|
-
return {
|
|
10335
|
+
return {
|
|
10336
|
+
...ctx,
|
|
10337
|
+
command: "hotfix-release",
|
|
10338
|
+
tag,
|
|
10339
|
+
mergedSha,
|
|
10340
|
+
deployModel,
|
|
10341
|
+
checks,
|
|
10342
|
+
tagNote,
|
|
10343
|
+
releaseNote,
|
|
10344
|
+
runs,
|
|
10345
|
+
deployNote,
|
|
10346
|
+
verifyNote,
|
|
10347
|
+
announceNote
|
|
10348
|
+
};
|
|
10194
10349
|
}
|
|
10195
10350
|
function deriveHotfixState(f) {
|
|
10196
10351
|
if (!f.branchExists && !f.pr && !f.tagPushed && !f.releaseExists) {
|
|
@@ -10226,6 +10381,11 @@ async function runHotfixStatus(deps, versionInput) {
|
|
|
10226
10381
|
return { ...ctx, command: "hotfix-status", ...latestFacts, ...latestDerived };
|
|
10227
10382
|
}
|
|
10228
10383
|
}
|
|
10384
|
+
const inFlight = await findInFlightHotfixVersion(deps, ctx);
|
|
10385
|
+
if (inFlight) {
|
|
10386
|
+
const facts2 = await gatherHotfixFacts(deps, ctx, inFlight.tag, inFlight.version);
|
|
10387
|
+
return { ...ctx, command: "hotfix-status", ...facts2, ...deriveHotfixState(facts2) };
|
|
10388
|
+
}
|
|
10229
10389
|
({ tag, version } = await deriveHotfixVersion(deps));
|
|
10230
10390
|
}
|
|
10231
10391
|
const facts = await gatherHotfixFacts(deps, ctx, tag, version);
|
|
@@ -10271,6 +10431,47 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
|
|
|
10271
10431
|
const npmVersion = await deps.run("npm", ["view", "@mutmutco/cli", "version", "--silent"]).then(clean2, () => "unknown");
|
|
10272
10432
|
return { tag, version, branchExists, pr: pr2 ? { number: pr2.number, state: pr2.state, url: pr2.url } : null, tagPushed, releaseExists, runs, npmVersion };
|
|
10273
10433
|
}
|
|
10434
|
+
async function findInFlightHotfixVersion(deps, ctx) {
|
|
10435
|
+
const tags = /* @__PURE__ */ new Set();
|
|
10436
|
+
const out = await deps.run("gh", [
|
|
10437
|
+
"pr",
|
|
10438
|
+
"list",
|
|
10439
|
+
"--repo",
|
|
10440
|
+
ctx.repo,
|
|
10441
|
+
"--base",
|
|
10442
|
+
"main",
|
|
10443
|
+
"--state",
|
|
10444
|
+
"all",
|
|
10445
|
+
"--limit",
|
|
10446
|
+
"50",
|
|
10447
|
+
"--json",
|
|
10448
|
+
"headRefName"
|
|
10449
|
+
]);
|
|
10450
|
+
for (const row of JSON.parse(out || "[]")) {
|
|
10451
|
+
const m = typeof row.headRefName === "string" && /^hotfix\/(v\d+\.\d+\.\d+)/.exec(row.headRefName);
|
|
10452
|
+
if (m) tags.add(m[1]);
|
|
10453
|
+
}
|
|
10454
|
+
const branchOut = clean2(await deps.run("git", ["ls-remote", "origin", "refs/heads/hotfix/v*"]));
|
|
10455
|
+
for (const line of branchOut.split("\n").filter(Boolean)) {
|
|
10456
|
+
const ref = line.split(/\s+/)[1] ?? "";
|
|
10457
|
+
const m = /^refs\/heads\/hotfix\/(v\d+\.\d+\.\d+)/.exec(ref);
|
|
10458
|
+
if (m) tags.add(m[1]);
|
|
10459
|
+
}
|
|
10460
|
+
const sorted = [...tags].sort((a, b) => {
|
|
10461
|
+
const pa = a.slice(1).split(".").map(Number);
|
|
10462
|
+
const pb = b.slice(1).split(".").map(Number);
|
|
10463
|
+
for (let i = 0; i < 3; i++) {
|
|
10464
|
+
if (pa[i] !== pb[i]) return pb[i] - pa[i];
|
|
10465
|
+
}
|
|
10466
|
+
return 0;
|
|
10467
|
+
});
|
|
10468
|
+
for (const tag of sorted) {
|
|
10469
|
+
const version = tag.slice(1);
|
|
10470
|
+
const facts = await gatherHotfixFacts(deps, ctx, tag, version);
|
|
10471
|
+
if (deriveHotfixState(facts).state !== "complete") return { tag, version };
|
|
10472
|
+
}
|
|
10473
|
+
return null;
|
|
10474
|
+
}
|
|
10274
10475
|
|
|
10275
10476
|
// src/release-announce.ts
|
|
10276
10477
|
var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
|
|
@@ -10716,6 +10917,9 @@ var requiredIssueTemplates = [
|
|
|
10716
10917
|
".github/ISSUE_TEMPLATE/config.yml"
|
|
10717
10918
|
];
|
|
10718
10919
|
var requiredWorkflows = [];
|
|
10920
|
+
var requiredProductWorkflows = [".github/workflows/gate.yml"];
|
|
10921
|
+
var requiredProductRulesetRef = ".github/rulesets/mmi-product-required-checks.json";
|
|
10922
|
+
var HUB_REPO3 = "mutmutco/MMI-Hub";
|
|
10719
10923
|
var requiredLabels = ["bug", "feature", "task", "priority:urgent", "priority:high", "priority:medium", "priority:low"];
|
|
10720
10924
|
var requiredPriorityOptions = ["Urgent", "High", "Medium", "Low"];
|
|
10721
10925
|
var strayDefaultLabels = ["documentation", "duplicate", "enhancement", "good first issue", "help wanted", "invalid", "question", "wontfix"];
|
|
@@ -10728,6 +10932,7 @@ var requiredProjectWorkflows = [
|
|
|
10728
10932
|
];
|
|
10729
10933
|
var requiredOrgRulesetTypes = ["pull_request", "non_fast_forward", "deletion"];
|
|
10730
10934
|
var requiredHubStatusChecks = ["cli", "infra", "docs"];
|
|
10935
|
+
var requiredProductStatusChecks = ["gate"];
|
|
10731
10936
|
function expectedBranches(repoClass, releaseTrack) {
|
|
10732
10937
|
if (isReleaseTrack(releaseTrack)) return branchesForTrack(releaseTrack);
|
|
10733
10938
|
return repoClass === "content" ? ["main"] : ["development", "rc", "main"];
|
|
@@ -10880,6 +11085,16 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
10880
11085
|
for (const path2 of requiredWorkflows) {
|
|
10881
11086
|
checks.push({ ok: await contentExists(deps, repo, baseBranch, path2), label: `automation workflow exists: ${path2}` });
|
|
10882
11087
|
}
|
|
11088
|
+
if (repo !== HUB_REPO3) {
|
|
11089
|
+
for (const path2 of requiredProductWorkflows) {
|
|
11090
|
+
checks.push({ ok: await contentExists(deps, repo, baseBranch, path2), label: `gate workflow exists: ${path2}` });
|
|
11091
|
+
}
|
|
11092
|
+
checks.push({
|
|
11093
|
+
ok: await contentExists(deps, repo, baseBranch, requiredProductRulesetRef),
|
|
11094
|
+
label: "product required-check ruleset reference exists",
|
|
11095
|
+
detail: `expected: ${requiredProductRulesetRef} (apply as an active repo ruleset after bootstrap)`
|
|
11096
|
+
});
|
|
11097
|
+
}
|
|
10883
11098
|
if (repoClass === "deployable") {
|
|
10884
11099
|
const trainScript = "scripts/next-version.mjs";
|
|
10885
11100
|
checks.push({ ok: await contentExists(deps, repo, baseBranch, trainScript), label: `train tooling script exists: ${trainScript}` });
|
|
@@ -11015,7 +11230,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
11015
11230
|
label: "covered by an active org ruleset",
|
|
11016
11231
|
detail: orgRuleset ? void 0 : activeOrgRulesets.length === 0 ? "no active Organization-sourced branch ruleset targets this repo" : `missing rule types: ${missingOrgRuleTypes.join(", ")}`
|
|
11017
11232
|
});
|
|
11018
|
-
if (repo ===
|
|
11233
|
+
if (repo === HUB_REPO3) {
|
|
11019
11234
|
const statusChecks = rulesetStatusChecks(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
11020
11235
|
const missing = requiredHubStatusChecks.filter((check) => !statusChecks.has(check));
|
|
11021
11236
|
checks.push({
|
|
@@ -11023,6 +11238,14 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
11023
11238
|
label: "Hub required status checks configured",
|
|
11024
11239
|
detail: optionDetail(missing)
|
|
11025
11240
|
});
|
|
11241
|
+
} else {
|
|
11242
|
+
const statusChecks = rulesetStatusChecks(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
11243
|
+
const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
|
|
11244
|
+
checks.push({
|
|
11245
|
+
ok: missing.length === 0,
|
|
11246
|
+
label: "product required status checks configured",
|
|
11247
|
+
detail: missing.length ? `missing contexts: ${missing.join(", ")} \u2014 apply ${requiredProductRulesetRef} as an active repo ruleset` : void 0
|
|
11248
|
+
});
|
|
11026
11249
|
}
|
|
11027
11250
|
const declaredApis = (deps.requiredGcpApis ?? []).filter((a) => a && a.trim());
|
|
11028
11251
|
if (declaredApis.length > 0) {
|
|
@@ -11074,12 +11297,32 @@ function parseOwnerRepo(repo) {
|
|
|
11074
11297
|
return { owner, name, slug: name.toLowerCase(), fullName: `${owner}/${name}` };
|
|
11075
11298
|
}
|
|
11076
11299
|
var DEFAULT_INSTALL_CMD = "npm ci";
|
|
11300
|
+
var DEFAULT_GATE_CMD = "npm run check";
|
|
11301
|
+
function gateSeedVars(cls) {
|
|
11302
|
+
if (cls === "content") {
|
|
11303
|
+
return {
|
|
11304
|
+
GATE_CMD: DEFAULT_GATE_CMD,
|
|
11305
|
+
GATE_PUSH_BRANCHES_YAML: "[main]",
|
|
11306
|
+
GATE_FULL_RUN_BRANCH: "main",
|
|
11307
|
+
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/main"]'
|
|
11308
|
+
};
|
|
11309
|
+
}
|
|
11310
|
+
return {
|
|
11311
|
+
GATE_CMD: DEFAULT_GATE_CMD,
|
|
11312
|
+
GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
|
|
11313
|
+
GATE_FULL_RUN_BRANCH: "development",
|
|
11314
|
+
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/rc", "refs/heads/main"]'
|
|
11315
|
+
};
|
|
11316
|
+
}
|
|
11077
11317
|
function withDerivedRepoVars(vars, parsed, cls) {
|
|
11078
11318
|
const out = { ...vars };
|
|
11079
11319
|
out.REPO_NAME ??= parsed.name;
|
|
11080
11320
|
out.REPO_SLUG ??= parsed.slug;
|
|
11081
11321
|
out.CLASS ??= cls;
|
|
11082
11322
|
out.INSTALL_CMD ??= DEFAULT_INSTALL_CMD;
|
|
11323
|
+
for (const [key, value] of Object.entries(gateSeedVars(cls))) {
|
|
11324
|
+
out[key] ??= value;
|
|
11325
|
+
}
|
|
11083
11326
|
return out;
|
|
11084
11327
|
}
|
|
11085
11328
|
function planSeedAction(seed, exists) {
|
|
@@ -13045,7 +13288,14 @@ program2.command("gc").description("dry-run cleanup for merged/closed PR branche
|
|
|
13045
13288
|
const plan2 = await gcPlan(o.remote, limit);
|
|
13046
13289
|
if (o.apply && !o.json) console.log(formatGcPlan(plan2, false));
|
|
13047
13290
|
let applyResult;
|
|
13048
|
-
if (o.apply)
|
|
13291
|
+
if (o.apply) {
|
|
13292
|
+
const deferredStore = await createDeferredWorktreeStore();
|
|
13293
|
+
await sweepDeferredWorktrees(
|
|
13294
|
+
deferredStore,
|
|
13295
|
+
worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout)
|
|
13296
|
+
).catch(() => void 0);
|
|
13297
|
+
applyResult = await applyGcPlan(plan2, o.remote);
|
|
13298
|
+
}
|
|
13049
13299
|
if (o.json) {
|
|
13050
13300
|
console.log(JSON.stringify({ dryRun: !o.apply, remote: o.remote, plan: plan2, applyResult }, null, 2));
|
|
13051
13301
|
} else if (!o.apply) {
|
|
@@ -14032,6 +14282,33 @@ async function remoteBranchExists(branch, options = {}) {
|
|
|
14032
14282
|
}, options);
|
|
14033
14283
|
}
|
|
14034
14284
|
var COMPOSE_TIMEOUT_MS = 12e4;
|
|
14285
|
+
async function createDeferredWorktreeStore() {
|
|
14286
|
+
try {
|
|
14287
|
+
const { stdout } = await execFileP2("git", ["rev-parse", "--git-dir"], { timeout: GIT_TIMEOUT_MS });
|
|
14288
|
+
const registryPath = deferredWorktreesRegistryPath(stdout.trim());
|
|
14289
|
+
return {
|
|
14290
|
+
read: async () => {
|
|
14291
|
+
try {
|
|
14292
|
+
return parseDeferredWorktreesFile(await (0, import_promises5.readFile)(registryPath, "utf8"));
|
|
14293
|
+
} catch {
|
|
14294
|
+
return [];
|
|
14295
|
+
}
|
|
14296
|
+
},
|
|
14297
|
+
write: async (entries) => {
|
|
14298
|
+
await (0, import_promises5.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
14299
|
+
}
|
|
14300
|
+
};
|
|
14301
|
+
} catch {
|
|
14302
|
+
return void 0;
|
|
14303
|
+
}
|
|
14304
|
+
}
|
|
14305
|
+
function worktreeRemoveDeps(execGit) {
|
|
14306
|
+
return {
|
|
14307
|
+
git: execGit,
|
|
14308
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
14309
|
+
removeWorktreeDir: async (worktreePath) => (0, import_promises5.rm)(worktreePath, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
14310
|
+
};
|
|
14311
|
+
}
|
|
14035
14312
|
function teardownWorktreeStage(worktreePath) {
|
|
14036
14313
|
return runWorktreeStageTeardown(worktreePath, {
|
|
14037
14314
|
hasStageState: (wt) => (0, import_node_fs14.existsSync)(stageStatePath(wt)),
|
|
@@ -14087,6 +14364,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
14087
14364
|
existedBefore: remoteBefore,
|
|
14088
14365
|
reason: remoteNotAttemptedReason
|
|
14089
14366
|
});
|
|
14367
|
+
const deferredStore = await createDeferredWorktreeStore();
|
|
14090
14368
|
const localCleanup = repoArgs.length ? {
|
|
14091
14369
|
branch: headRef,
|
|
14092
14370
|
localBranch: { name: headRef, status: "not-attempted", reason: "repo-option" },
|
|
@@ -14096,10 +14374,8 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
14096
14374
|
startingPath,
|
|
14097
14375
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
14098
14376
|
teardownWorktreeStage,
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
// own maxRetries/retryDelay rides out a handle that an indexer/antivirus releases a moment later.
|
|
14102
|
-
removeWorktreeDir: async (worktreePath) => (0, import_promises5.rm)(worktreePath, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
14377
|
+
deferredStore,
|
|
14378
|
+
removeWorktreeDir: worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout).removeWorktreeDir
|
|
14103
14379
|
});
|
|
14104
14380
|
console.log(JSON.stringify(buildPrMergeResultPayload({
|
|
14105
14381
|
number,
|
|
@@ -14567,10 +14843,11 @@ function renderHotfixStart(r) {
|
|
|
14567
14843
|
}
|
|
14568
14844
|
function renderHotfixRelease(r) {
|
|
14569
14845
|
return [
|
|
14570
|
-
`mmi-cli hotfix release: ${r.tag} at ${r.mergedSha.slice(0, 7)} on ${r.repo}`,
|
|
14846
|
+
`mmi-cli hotfix release: ${r.tag} at ${r.mergedSha.slice(0, 7)} on ${r.repo} (deployModel=${r.deployModel})`,
|
|
14571
14847
|
` - checks: ${r.checks}`,
|
|
14572
14848
|
` - ${r.tagNote}`,
|
|
14573
14849
|
` - ${r.releaseNote}`,
|
|
14850
|
+
` - deploy: ${r.deployNote}`,
|
|
14574
14851
|
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
14575
14852
|
` - ${r.verifyNote}`,
|
|
14576
14853
|
...r.announceNote ? [` - announce: ${r.announceNote}`] : [],
|
|
@@ -14975,6 +15252,7 @@ function cursorPluginCachePinSnapshots() {
|
|
|
14975
15252
|
const path2 = (0, import_node_path13.join)(root, entry.name);
|
|
14976
15253
|
const pluginJson = (0, import_node_path13.join)(path2, ".cursor-plugin", "plugin.json");
|
|
14977
15254
|
const hooksJson = (0, import_node_path13.join)(path2, "hooks", "hooks.json");
|
|
15255
|
+
const cliBundle = (0, import_node_path13.join)(path2, "cli", "dist", "index.cjs");
|
|
14978
15256
|
let isEmpty = true;
|
|
14979
15257
|
try {
|
|
14980
15258
|
isEmpty = (0, import_node_fs14.readdirSync)(path2).length === 0;
|
|
@@ -14986,6 +15264,7 @@ function cursorPluginCachePinSnapshots() {
|
|
|
14986
15264
|
path: path2,
|
|
14987
15265
|
hasPluginJson: (0, import_node_fs14.existsSync)(pluginJson),
|
|
14988
15266
|
hasHooksJson: (0, import_node_fs14.existsSync)(hooksJson),
|
|
15267
|
+
hasCliBundle: (0, import_node_fs14.existsSync)(cliBundle),
|
|
14989
15268
|
isEmpty
|
|
14990
15269
|
};
|
|
14991
15270
|
});
|
|
@@ -15237,6 +15516,15 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
15237
15516
|
hubCheckout: hubCheckoutForCursorSeed()
|
|
15238
15517
|
})
|
|
15239
15518
|
);
|
|
15519
|
+
const cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
15520
|
+
checks.push(
|
|
15521
|
+
buildCursorHookCliCheck({
|
|
15522
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
15523
|
+
surface,
|
|
15524
|
+
pins: cursorPins,
|
|
15525
|
+
mmiCliOnPath: onPath
|
|
15526
|
+
})
|
|
15527
|
+
);
|
|
15240
15528
|
const gaps = checks.filter((c) => !c.ok);
|
|
15241
15529
|
if (opts.banner) {
|
|
15242
15530
|
if (gaps.length) io.log(`\u26A0 MMI setup needed \u2014 ${gaps.map((g) => g.fix).join(" \xB7 ")} \xB7 guide: ${MMI_AGENTIC_ONBOARDING_GUIDE.url}`);
|
package/dist/saga.cjs
CHANGED
|
@@ -3411,18 +3411,28 @@ var import_node_path2 = require("node:path");
|
|
|
3411
3411
|
var CLIENT_VERSION_HEADER = "x-client-version";
|
|
3412
3412
|
|
|
3413
3413
|
// src/client-version.ts
|
|
3414
|
-
function
|
|
3414
|
+
function resolveClientVersionManifestCandidates(distDir = __dirname) {
|
|
3415
|
+
return [
|
|
3416
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
|
|
3417
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".cursor-plugin", "plugin.json"),
|
|
3418
|
+
(0, import_node_path2.join)(distDir, "..", "..", ".codex-plugin", "plugin.json"),
|
|
3419
|
+
(0, import_node_path2.join)(distDir, "..", "package.json")
|
|
3420
|
+
];
|
|
3421
|
+
}
|
|
3422
|
+
function readVersionFromManifest(path2) {
|
|
3415
3423
|
try {
|
|
3416
|
-
const
|
|
3417
|
-
return
|
|
3424
|
+
const version = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8")).version;
|
|
3425
|
+
return typeof version === "string" && version.trim() ? version.trim() : null;
|
|
3418
3426
|
} catch {
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3427
|
+
return null;
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
function resolveClientVersion() {
|
|
3431
|
+
for (const manifest of resolveClientVersionManifestCandidates()) {
|
|
3432
|
+
const version = readVersionFromManifest(manifest);
|
|
3433
|
+
if (version) return version;
|
|
3425
3434
|
}
|
|
3435
|
+
return "0.0.0";
|
|
3426
3436
|
}
|
|
3427
3437
|
function clientVersionHeaders() {
|
|
3428
3438
|
return { [CLIENT_VERSION_HEADER]: resolveClientVersion() };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|