@mutmutco/cli 2.46.0 → 2.46.1
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 +84 -17
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -12830,6 +12830,33 @@ function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
|
|
|
12830
12830
|
function normPath2(path2) {
|
|
12831
12831
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
12832
12832
|
}
|
|
12833
|
+
function parseWorktreeListPaths(stdout) {
|
|
12834
|
+
const paths = [];
|
|
12835
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
12836
|
+
if (line.startsWith("worktree ")) paths.push(line.slice("worktree ".length).trim());
|
|
12837
|
+
}
|
|
12838
|
+
return paths;
|
|
12839
|
+
}
|
|
12840
|
+
function collectReservedStagePorts(worktreePaths, readStateFile = readState) {
|
|
12841
|
+
const ports = /* @__PURE__ */ new Set();
|
|
12842
|
+
for (const wt of worktreePaths) {
|
|
12843
|
+
const state = readStateFile(stageStatePath(wt));
|
|
12844
|
+
if (state?.port != null && Number.isInteger(state.port) && state.port > 0) ports.add(state.port);
|
|
12845
|
+
}
|
|
12846
|
+
return ports;
|
|
12847
|
+
}
|
|
12848
|
+
async function listRepoWorktreePaths(cwd) {
|
|
12849
|
+
const stdout = await gitText(cwd, ["worktree", "list", "--porcelain"]);
|
|
12850
|
+
const paths = stdout ? parseWorktreeListPaths(stdout) : [];
|
|
12851
|
+
return paths.length ? paths : [cwd];
|
|
12852
|
+
}
|
|
12853
|
+
function parseStagePortFlag(raw) {
|
|
12854
|
+
const port = Number(raw);
|
|
12855
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
12856
|
+
throw new Error("stage port must be an integer 1024..65535");
|
|
12857
|
+
}
|
|
12858
|
+
return port;
|
|
12859
|
+
}
|
|
12833
12860
|
function pathUnder(childPath, parentPath) {
|
|
12834
12861
|
const child = normPath2(childPath);
|
|
12835
12862
|
const parent = normPath2(parentPath);
|
|
@@ -12976,7 +13003,7 @@ async function ensureStagePortAvailable(port, cwd, deps) {
|
|
|
12976
13003
|
);
|
|
12977
13004
|
}
|
|
12978
13005
|
}
|
|
12979
|
-
async function resolveStagePort(config, guard) {
|
|
13006
|
+
async function resolveStagePort(config, guard, reservedPorts = /* @__PURE__ */ new Set()) {
|
|
12980
13007
|
if (!config.portRange) return void 0;
|
|
12981
13008
|
let dockerBusy = /* @__PURE__ */ new Set();
|
|
12982
13009
|
if (guard) {
|
|
@@ -12990,11 +13017,25 @@ async function resolveStagePort(config, guard) {
|
|
|
12990
13017
|
const [s, e] = config.portRange;
|
|
12991
13018
|
const free = /* @__PURE__ */ new Set();
|
|
12992
13019
|
for (let p = s; p <= e; p++) {
|
|
12993
|
-
if (dockerBusy.has(p)) continue;
|
|
13020
|
+
if (dockerBusy.has(p) || reservedPorts.has(p)) continue;
|
|
12994
13021
|
if (await isPortFree(p)) free.add(p);
|
|
12995
13022
|
}
|
|
12996
13023
|
return pickStagePort(config.portRange, (p) => free.has(p));
|
|
12997
13024
|
}
|
|
13025
|
+
async function reservedPortsForWorktree(cwd) {
|
|
13026
|
+
const self = normPath2(cwd);
|
|
13027
|
+
const siblings = (await listRepoWorktreePaths(cwd)).filter((p) => normPath2(p) !== self);
|
|
13028
|
+
return collectReservedStagePorts(siblings);
|
|
13029
|
+
}
|
|
13030
|
+
async function assertStagePortAvailable(port, cwd, guard, reserved) {
|
|
13031
|
+
if (reserved.has(port)) {
|
|
13032
|
+
throw new Error(`stage port ${port} is reserved by another worktree's active stage \u2014 pick a different port or stop that stage`);
|
|
13033
|
+
}
|
|
13034
|
+
if (!await isPortFree(port)) {
|
|
13035
|
+
throw new Error(`stage port ${port} is already in use on loopback`);
|
|
13036
|
+
}
|
|
13037
|
+
if (guard) await ensureStagePortAvailable(port, cwd, guard);
|
|
13038
|
+
}
|
|
12998
13039
|
function substituteStagePort(s, stagePort) {
|
|
12999
13040
|
return s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
|
|
13000
13041
|
}
|
|
@@ -13051,8 +13092,7 @@ async function resolveStageIdentity(cwd) {
|
|
|
13051
13092
|
async function resolveGlobalStatePath(cwd, explicit) {
|
|
13052
13093
|
if (explicit === false) return void 0;
|
|
13053
13094
|
if (typeof explicit === "string") return explicit;
|
|
13054
|
-
|
|
13055
|
-
return commonDir ? stageGlobalStatePath(cwd, commonDir) : void 0;
|
|
13095
|
+
return void 0;
|
|
13056
13096
|
}
|
|
13057
13097
|
function readState(path2) {
|
|
13058
13098
|
if (!(0, import_node_fs19.existsSync)(path2)) return null;
|
|
@@ -13120,11 +13160,12 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
|
|
|
13120
13160
|
}
|
|
13121
13161
|
async function stopStage(opts = {}) {
|
|
13122
13162
|
const cwd = opts.cwd ?? process.cwd();
|
|
13163
|
+
const scopedCwd = opts.requiredIdentityCwd ?? cwd;
|
|
13123
13164
|
const statePath = opts.statePath ?? stageStatePath(cwd);
|
|
13124
13165
|
const globalStatePath = await resolveGlobalStatePath(cwd, opts.globalStatePath);
|
|
13125
13166
|
const localState = readState(statePath);
|
|
13126
13167
|
const globalState = globalStatePath && globalStatePath !== statePath ? readState(globalStatePath) : null;
|
|
13127
|
-
const state = stageStateMatchesRequiredCwd(
|
|
13168
|
+
const state = stageStateMatchesRequiredCwd(localState, scopedCwd) ? localState : stageStateMatchesRequiredCwd(globalState, scopedCwd) ? globalState : null;
|
|
13128
13169
|
if (!state) {
|
|
13129
13170
|
return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
|
|
13130
13171
|
}
|
|
@@ -13146,7 +13187,13 @@ async function startStage(config = {}, opts = {}) {
|
|
|
13146
13187
|
const statePath = opts.statePath ?? stageStatePath(cwd);
|
|
13147
13188
|
const globalStatePath = await resolveGlobalStatePath(cwd, opts.globalStatePath);
|
|
13148
13189
|
const portGuard = resolveStagePortGuard(opts);
|
|
13149
|
-
const
|
|
13190
|
+
const reserved = await reservedPortsForWorktree(cwd);
|
|
13191
|
+
let stagePort = opts.stagePort;
|
|
13192
|
+
if (stagePort != null) {
|
|
13193
|
+
await assertStagePortAvailable(stagePort, cwd, portGuard, reserved);
|
|
13194
|
+
} else {
|
|
13195
|
+
stagePort = await resolveStagePort(config, portGuard, reserved);
|
|
13196
|
+
}
|
|
13150
13197
|
const sub = (s) => substituteStagePort(s, stagePort);
|
|
13151
13198
|
if (!opts.envPrepared) await ensureStageRuntimeEnv(config, opts, cwd);
|
|
13152
13199
|
if (stagePort != null && portGuard) await ensureStagePortAvailable(stagePort, cwd, portGuard);
|
|
@@ -13204,8 +13251,14 @@ async function runStage(config = {}, opts = {}) {
|
|
|
13204
13251
|
const cwd = opts.cwd ?? process.cwd();
|
|
13205
13252
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
13206
13253
|
const portGuard = resolveStagePortGuard(opts);
|
|
13207
|
-
await stopStage({ ...opts, cwd });
|
|
13208
|
-
const
|
|
13254
|
+
await stopStage({ ...opts, cwd, requiredIdentityCwd: opts.requiredIdentityCwd ?? cwd });
|
|
13255
|
+
const reserved = await reservedPortsForWorktree(cwd);
|
|
13256
|
+
let stagePort = opts.stagePort;
|
|
13257
|
+
if (stagePort != null) {
|
|
13258
|
+
await assertStagePortAvailable(stagePort, cwd, portGuard, reserved);
|
|
13259
|
+
} else {
|
|
13260
|
+
stagePort = await resolveStagePort(config, portGuard, reserved);
|
|
13261
|
+
}
|
|
13209
13262
|
const sub = (s) => substituteStagePort(s, stagePort);
|
|
13210
13263
|
await ensureStageRuntimeEnv(config, opts, cwd);
|
|
13211
13264
|
const extraEnv = stageExtraEnv(config, stagePort);
|
|
@@ -19917,6 +19970,22 @@ function rawValues(flag) {
|
|
|
19917
19970
|
}
|
|
19918
19971
|
return out;
|
|
19919
19972
|
}
|
|
19973
|
+
function stagePortFromArgv() {
|
|
19974
|
+
const raw = rawValue("--port", "");
|
|
19975
|
+
if (!raw) return void 0;
|
|
19976
|
+
return parseStagePortFlag(raw);
|
|
19977
|
+
}
|
|
19978
|
+
function stageScopedRunOpts(o) {
|
|
19979
|
+
const cwd = process.cwd();
|
|
19980
|
+
const stagePort = stagePortFromArgv();
|
|
19981
|
+
return {
|
|
19982
|
+
timeoutMs: Number(o.timeoutMs || 6e4),
|
|
19983
|
+
allowStaleEnv: o.allowStaleEnv,
|
|
19984
|
+
cwd,
|
|
19985
|
+
requiredIdentityCwd: cwd,
|
|
19986
|
+
...stagePort != null ? { stagePort } : {}
|
|
19987
|
+
};
|
|
19988
|
+
}
|
|
19920
19989
|
function printLine(value) {
|
|
19921
19990
|
(0, import_node_fs22.writeSync)(1, `${value}
|
|
19922
19991
|
`);
|
|
@@ -20046,7 +20115,7 @@ async function runStageLiveCommand(o) {
|
|
|
20046
20115
|
return failGraceful(`stage --live: ${e.message}`);
|
|
20047
20116
|
}
|
|
20048
20117
|
}
|
|
20049
|
-
var stage = program2.command("stage").description("plan or run the repo local stage environment; --live = personal IP-gated cloud dev stage").option("--json", "machine-readable output").option("--apply", "run the full local stage: stop previous, build, start, health-check").option("--live", "personal cloud dev stage: deploy the current branch to the dev runtime, served only to your public IP").option("--down", "with --live: stop the dev runtime and clear the IP allowlist").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").action(async (o) => {
|
|
20118
|
+
var stage = program2.command("stage").description("plan or run the repo local stage environment; --live = personal IP-gated cloud dev stage").option("--json", "machine-readable output").option("--apply", "run the full local stage: stop previous in this worktree, build, start, health-check").option("--port <port>", "loopback port for this worktree stage (1024..65535; default picks a free port in the registry range)").option("--live", "personal cloud dev stage: deploy the current branch to the dev runtime, served only to your public IP").option("--down", "with --live: stop the dev runtime and clear the IP allowlist").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").action(async (o) => {
|
|
20050
20119
|
if (o.down && !o.live) return fail("stage: --down applies to --live only (local teardown is `mmi-cli stage stop --apply`)");
|
|
20051
20120
|
if (o.live) return runStageLiveCommand(o);
|
|
20052
20121
|
const res = await resolveStage();
|
|
@@ -20055,7 +20124,7 @@ var stage = program2.command("stage").description("plan or run the repo local st
|
|
|
20055
20124
|
const cfg = res.config ?? res.derived.config;
|
|
20056
20125
|
const hold = stageKeepAlive();
|
|
20057
20126
|
try {
|
|
20058
|
-
const result = await runStage(cfg, { timeoutMs:
|
|
20127
|
+
const result = await runStage(cfg, stageScopedRunOpts({ timeoutMs: o.timeoutMs }));
|
|
20059
20128
|
const reportUrl = reportedStageUrl(res, result);
|
|
20060
20129
|
const url = reportUrl ? ` \u2014 ${reportUrl}` : "";
|
|
20061
20130
|
return printLine(o.json ? JSON.stringify({ ...result, source: res.source, url: reportUrl }) : `mmi-cli stage: ${result.message}${url}`);
|
|
@@ -20078,13 +20147,13 @@ stage.command("stop").description("stop the previous local stage process recorde
|
|
|
20078
20147
|
return printLine(o.json ? JSON.stringify({ command: "stage stop", steps }, null, 2) : renderSteps("mmi-cli stage stop: dry-run plan", steps));
|
|
20079
20148
|
}
|
|
20080
20149
|
try {
|
|
20081
|
-
const result = await stopStage();
|
|
20150
|
+
const result = await stopStage({ cwd: process.cwd(), requiredIdentityCwd: process.cwd() });
|
|
20082
20151
|
printLine(o.json ? JSON.stringify(result) : `mmi-cli stage stop: ${result.message}`);
|
|
20083
20152
|
} catch (e) {
|
|
20084
20153
|
fail(`stage stop: ${e.message}`);
|
|
20085
20154
|
}
|
|
20086
20155
|
});
|
|
20087
|
-
stage.command("start").description("start the configured local stage process and optionally wait for health").option("--json", "machine-readable output").option("--apply", "start the configured stage.up process").option("--timeout-ms <ms>", "bounded health timeout", "60000").option("--allow-stale-env", "start despite a stale ensureEnv target file").action(async () => {
|
|
20156
|
+
stage.command("start").description("start the configured local stage process and optionally wait for health").option("--json", "machine-readable output").option("--apply", "start the configured stage.up process").option("--port <port>", "loopback port for this worktree stage (1024..65535)").option("--timeout-ms <ms>", "bounded health timeout", "60000").option("--allow-stale-env", "start despite a stale ensureEnv target file").action(async () => {
|
|
20088
20157
|
const o = { json: rawFlag("--json"), apply: rawFlag("--apply"), timeoutMs: rawValue("--timeout-ms", "60000"), allowStaleEnv: rawFlag("--allow-stale-env") };
|
|
20089
20158
|
const res = await resolveStage();
|
|
20090
20159
|
if (!o.apply) {
|
|
@@ -20102,8 +20171,7 @@ stage.command("start").description("start the configured local stage process and
|
|
|
20102
20171
|
let printed = false;
|
|
20103
20172
|
try {
|
|
20104
20173
|
const result = await startStage(cfg, {
|
|
20105
|
-
timeoutMs:
|
|
20106
|
-
allowStaleEnv: o.allowStaleEnv,
|
|
20174
|
+
...stageScopedRunOpts({ timeoutMs: o.timeoutMs, allowStaleEnv: o.allowStaleEnv }),
|
|
20107
20175
|
vaultEnvMerge,
|
|
20108
20176
|
onReady: (ready) => {
|
|
20109
20177
|
printed = true;
|
|
@@ -20120,7 +20188,7 @@ stage.command("start").description("start the configured local stage process and
|
|
|
20120
20188
|
return failGraceful(`stage start: ${e.message}`);
|
|
20121
20189
|
}
|
|
20122
20190
|
});
|
|
20123
|
-
stage.command("run").description("force-stop previous stage, build, start, and health-check").option("--json", "machine-readable output").option("--apply", "run the configured stage sequence").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").option("--allow-stale-env", "run despite a stale ensureEnv target file").action(async () => {
|
|
20191
|
+
stage.command("run").description("force-stop previous stage, build, start, and health-check").option("--json", "machine-readable output").option("--apply", "run the configured stage sequence").option("--port <port>", "loopback port for this worktree stage (1024..65535)").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").option("--allow-stale-env", "run despite a stale ensureEnv target file").action(async () => {
|
|
20124
20192
|
const o = { json: rawFlag("--json"), apply: rawFlag("--apply"), timeoutMs: rawValue("--timeout-ms", "60000"), allowStaleEnv: rawFlag("--allow-stale-env") };
|
|
20125
20193
|
const res = await resolveStage();
|
|
20126
20194
|
if (!o.apply) {
|
|
@@ -20138,8 +20206,7 @@ stage.command("run").description("force-stop previous stage, build, start, and h
|
|
|
20138
20206
|
let printed = false;
|
|
20139
20207
|
try {
|
|
20140
20208
|
const result = await runStage(cfg, {
|
|
20141
|
-
timeoutMs:
|
|
20142
|
-
allowStaleEnv: o.allowStaleEnv,
|
|
20209
|
+
...stageScopedRunOpts({ timeoutMs: o.timeoutMs, allowStaleEnv: o.allowStaleEnv }),
|
|
20143
20210
|
vaultEnvMerge,
|
|
20144
20211
|
onReady: (ready) => {
|
|
20145
20212
|
const reportUrl = reportedStageUrl(res, ready);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.46.
|
|
3
|
+
"version": "2.46.1",
|
|
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",
|