mcproc 0.14.2 → 0.15.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/README.md +2 -1
- package/dist/cli.js +2244 -870
- package/dist/cli.js.map +1 -1
- package/docs/monit.md +27 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/program.ts
|
|
4
|
-
import
|
|
4
|
+
import fs20 from "fs";
|
|
5
5
|
import { Command, Option } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/version.ts
|
|
@@ -259,9 +259,9 @@ function ensureOk(res, host, what) {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
// src/runtime/unpack.ts
|
|
262
|
-
import
|
|
263
|
-
import
|
|
264
|
-
import
|
|
262
|
+
import fs7 from "fs";
|
|
263
|
+
import os5 from "os";
|
|
264
|
+
import path10 from "path";
|
|
265
265
|
|
|
266
266
|
// src/core/pm2.ts
|
|
267
267
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -459,7 +459,8 @@ async function resolvePm2Bin(host, transport) {
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
// src/core/resolve.ts
|
|
462
|
-
import
|
|
462
|
+
import os2 from "os";
|
|
463
|
+
import path7 from "path";
|
|
463
464
|
|
|
464
465
|
// src/core/manifest.ts
|
|
465
466
|
import fs2 from "fs";
|
|
@@ -1553,6 +1554,166 @@ function loadManifest(startDir) {
|
|
|
1553
1554
|
);
|
|
1554
1555
|
}
|
|
1555
1556
|
|
|
1557
|
+
// src/core/registry.ts
|
|
1558
|
+
import fs4 from "fs";
|
|
1559
|
+
import path6 from "path";
|
|
1560
|
+
|
|
1561
|
+
// src/core/slots.ts
|
|
1562
|
+
import crypto from "crypto";
|
|
1563
|
+
import fs3 from "fs";
|
|
1564
|
+
import path4 from "path";
|
|
1565
|
+
function registryKey(baseProject, repo) {
|
|
1566
|
+
return `${baseProject}-${crypto.createHash("sha256").update(realOf(repo)).digest("hex").slice(0, 8)}`;
|
|
1567
|
+
}
|
|
1568
|
+
function registryPath(stateDir2, baseProject, repo) {
|
|
1569
|
+
return path4.join(stateDir2, "slots", `${registryKey(baseProject, repo)}.json`);
|
|
1570
|
+
}
|
|
1571
|
+
function emptyRegistry(repo) {
|
|
1572
|
+
return { repo, slots: {} };
|
|
1573
|
+
}
|
|
1574
|
+
function readRegistry(file) {
|
|
1575
|
+
if (!fs3.existsSync(file)) return null;
|
|
1576
|
+
return JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
1577
|
+
}
|
|
1578
|
+
function writeRegistry(file, r) {
|
|
1579
|
+
fs3.mkdirSync(path4.dirname(file), { recursive: true });
|
|
1580
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1581
|
+
fs3.writeFileSync(tmp, JSON.stringify(r, null, 2));
|
|
1582
|
+
fs3.renameSync(tmp, file);
|
|
1583
|
+
}
|
|
1584
|
+
function withMain(r, entry) {
|
|
1585
|
+
return { ...r, slots: { ...r.slots, "0": entry } };
|
|
1586
|
+
}
|
|
1587
|
+
function claimSlot(r, opts) {
|
|
1588
|
+
if (opts.want !== void 0) {
|
|
1589
|
+
if (opts.want < 1 || opts.want > opts.max)
|
|
1590
|
+
throw new McprocError(
|
|
1591
|
+
"BAD_ARGUMENT",
|
|
1592
|
+
`slot ${opts.want} is outside 1..${opts.max} (slot 0 is the main checkout)`
|
|
1593
|
+
);
|
|
1594
|
+
if (Object.hasOwn(r.slots, String(opts.want)))
|
|
1595
|
+
throw new McprocError(
|
|
1596
|
+
"SLOT_TAKEN",
|
|
1597
|
+
`slot ${opts.want} is taken by ${r.slots[String(opts.want)].path}`,
|
|
1598
|
+
{ hint: "Pick another slot, or remove that worktree first." }
|
|
1599
|
+
);
|
|
1600
|
+
return {
|
|
1601
|
+
registry: { ...r, slots: { ...r.slots, [opts.want]: opts.entry } },
|
|
1602
|
+
slot: opts.want
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
for (let s = 1; s <= opts.max; s++)
|
|
1606
|
+
if (!Object.hasOwn(r.slots, String(s)))
|
|
1607
|
+
return {
|
|
1608
|
+
registry: { ...r, slots: { ...r.slots, [s]: opts.entry } },
|
|
1609
|
+
slot: s
|
|
1610
|
+
};
|
|
1611
|
+
throw new McprocError(
|
|
1612
|
+
"NO_FREE_SLOT",
|
|
1613
|
+
`all ${opts.max} worktree slots of ${r.repo} are taken`,
|
|
1614
|
+
{
|
|
1615
|
+
hint: "Remove a worktree (worktree remove), or raise worktree.slots in the manifest."
|
|
1616
|
+
}
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
function releaseSlot(r, slot) {
|
|
1620
|
+
const slots = { ...r.slots };
|
|
1621
|
+
delete slots[String(slot)];
|
|
1622
|
+
return { ...r, slots };
|
|
1623
|
+
}
|
|
1624
|
+
function realOf(p) {
|
|
1625
|
+
try {
|
|
1626
|
+
return fs3.realpathSync(p);
|
|
1627
|
+
} catch {
|
|
1628
|
+
return path4.resolve(p);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function reclaimGone(r, exists) {
|
|
1632
|
+
const freed = [];
|
|
1633
|
+
let out = r;
|
|
1634
|
+
for (const [k, e] of Object.entries(r.slots)) {
|
|
1635
|
+
if (k === "0" || exists(realOf(e.path))) continue;
|
|
1636
|
+
out = releaseSlot(out, Number(k));
|
|
1637
|
+
freed.push(Number(k));
|
|
1638
|
+
}
|
|
1639
|
+
return { registry: out, freed: freed.sort((a, b) => a - b) };
|
|
1640
|
+
}
|
|
1641
|
+
function siblingsOf(r, root) {
|
|
1642
|
+
return Object.entries(r.slots).filter(([, e]) => realOf(e.path) !== realOf(root)).map(([k, entry]) => ({ slot: Number(k), entry })).sort((a, b) => a.slot - b.slot);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// src/core/state.ts
|
|
1646
|
+
import os from "os";
|
|
1647
|
+
import path5 from "path";
|
|
1648
|
+
function stateDir(env = process.env) {
|
|
1649
|
+
if (env.MCPROC_STATE_DIR) return path5.resolve(env.MCPROC_STATE_DIR);
|
|
1650
|
+
return path5.join(
|
|
1651
|
+
env.XDG_STATE_HOME || path5.join(os.homedir(), ".local", "state"),
|
|
1652
|
+
"mcproc"
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// src/core/registry.ts
|
|
1657
|
+
var projectsDir = (dir) => path6.join(dir, "projects");
|
|
1658
|
+
function projectFile(dir, project, root) {
|
|
1659
|
+
return path6.join(projectsDir(dir), `${registryKey(project, root)}.json`);
|
|
1660
|
+
}
|
|
1661
|
+
function recordProject(m, dir = stateDir()) {
|
|
1662
|
+
try {
|
|
1663
|
+
const file = projectFile(dir, m.project, m.root);
|
|
1664
|
+
const text = JSON.stringify(
|
|
1665
|
+
{
|
|
1666
|
+
project: m.project,
|
|
1667
|
+
root: m.root,
|
|
1668
|
+
manifest: m.path
|
|
1669
|
+
},
|
|
1670
|
+
null,
|
|
1671
|
+
2
|
|
1672
|
+
);
|
|
1673
|
+
try {
|
|
1674
|
+
if (fs4.readFileSync(file, "utf8") === text) return;
|
|
1675
|
+
} catch {
|
|
1676
|
+
}
|
|
1677
|
+
fs4.mkdirSync(path6.dirname(file), { recursive: true });
|
|
1678
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1679
|
+
fs4.writeFileSync(tmp, text);
|
|
1680
|
+
fs4.renameSync(tmp, file);
|
|
1681
|
+
} catch {
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function forgetProject(project, root, dir = stateDir()) {
|
|
1685
|
+
try {
|
|
1686
|
+
fs4.rmSync(projectFile(dir, project, root), { force: true });
|
|
1687
|
+
} catch {
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
function isEntry(v) {
|
|
1691
|
+
const e = v;
|
|
1692
|
+
return typeof e === "object" && e !== null && typeof e.project === "string" && typeof e.root === "string" && typeof e.manifest === "string";
|
|
1693
|
+
}
|
|
1694
|
+
function listProjects(dir = stateDir()) {
|
|
1695
|
+
let names;
|
|
1696
|
+
try {
|
|
1697
|
+
names = fs4.readdirSync(projectsDir(dir)).filter((n) => n.endsWith(".json"));
|
|
1698
|
+
} catch (e) {
|
|
1699
|
+
const code = e.code;
|
|
1700
|
+
return { entries: [], error: code === "ENOENT" ? null : String(code ?? e) };
|
|
1701
|
+
}
|
|
1702
|
+
const entries = [];
|
|
1703
|
+
for (const n of names.sort()) {
|
|
1704
|
+
const file = path6.join(projectsDir(dir), n);
|
|
1705
|
+
try {
|
|
1706
|
+
const e = JSON.parse(fs4.readFileSync(file, "utf8"));
|
|
1707
|
+
if (!isEntry(e)) continue;
|
|
1708
|
+
if (fs4.existsSync(e.manifest)) entries.push(e);
|
|
1709
|
+
else if (fs4.existsSync(path6.dirname(e.root)))
|
|
1710
|
+
fs4.rmSync(file, { force: true });
|
|
1711
|
+
} catch {
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return { entries, error: null };
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1556
1717
|
// src/core/urlcheck.ts
|
|
1557
1718
|
import http from "http";
|
|
1558
1719
|
import https from "https";
|
|
@@ -1878,7 +2039,18 @@ function reasonWord(reason) {
|
|
|
1878
2039
|
var MAX_PM2_HOME = 90;
|
|
1879
2040
|
function resolveProjectDir(explicit, roots = [], env = process.env) {
|
|
1880
2041
|
const pick = explicit || env.MCPROC_PROJECT || env.CLAUDE_PROJECT_DIR || roots[0] || process.cwd();
|
|
1881
|
-
return
|
|
2042
|
+
return path7.resolve(pick);
|
|
2043
|
+
}
|
|
2044
|
+
function bareLocalHost() {
|
|
2045
|
+
return {
|
|
2046
|
+
name: "local",
|
|
2047
|
+
local: true,
|
|
2048
|
+
ssh_args: [],
|
|
2049
|
+
cwd: os2.homedir(),
|
|
2050
|
+
readonly: false,
|
|
2051
|
+
shell: parseShell(DEFAULT_SHELL),
|
|
2052
|
+
env: {}
|
|
2053
|
+
};
|
|
1882
2054
|
}
|
|
1883
2055
|
function pickHost(m, name2 = "local") {
|
|
1884
2056
|
const h = Object.hasOwn(m.hosts, name2) ? m.hosts[name2] : void 0;
|
|
@@ -1915,6 +2087,7 @@ function assertApp(m, name2) {
|
|
|
1915
2087
|
}
|
|
1916
2088
|
function prepare(opts) {
|
|
1917
2089
|
const manifest = loadManifest(resolveProjectDir(opts.project, opts.roots));
|
|
2090
|
+
recordProject(manifest);
|
|
1918
2091
|
return { manifest, host: pickHost(manifest, opts.host) };
|
|
1919
2092
|
}
|
|
1920
2093
|
var cache = /* @__PURE__ */ new Map();
|
|
@@ -1948,30 +2121,39 @@ async function connect(prep, opts = {}) {
|
|
|
1948
2121
|
hereCheck: opts.hereCheck ?? checkHere
|
|
1949
2122
|
};
|
|
1950
2123
|
}
|
|
2124
|
+
async function connectHost(host, opts = {}) {
|
|
2125
|
+
const transport = createTransport(host, opts.spawner);
|
|
2126
|
+
const resolution = await resolvePm2Bin(host, transport);
|
|
2127
|
+
return {
|
|
2128
|
+
host,
|
|
2129
|
+
transport,
|
|
2130
|
+
pm2: new Pm2Client(transport, host, resolution.bin, host.pm2_home)
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
1951
2133
|
|
|
1952
2134
|
// src/runtime/bundle.ts
|
|
1953
2135
|
import { execFileSync } from "child_process";
|
|
1954
2136
|
import { createHash } from "crypto";
|
|
1955
|
-
import
|
|
1956
|
-
import
|
|
1957
|
-
import
|
|
2137
|
+
import fs5 from "fs";
|
|
2138
|
+
import os3 from "os";
|
|
2139
|
+
import path8 from "path";
|
|
1958
2140
|
import { fileURLToPath } from "url";
|
|
1959
2141
|
var sha256 = (b) => createHash("sha256").update(b).digest("hex");
|
|
1960
2142
|
var readJson = (p) => (
|
|
1961
2143
|
// biome-ignore lint/suspicious/noExplicitAny: package.json files are untyped data, probed field by field
|
|
1962
|
-
JSON.parse(
|
|
2144
|
+
JSON.parse(fs5.readFileSync(p, "utf8"))
|
|
1963
2145
|
);
|
|
1964
|
-
function findPackageRoot(from =
|
|
1965
|
-
let dir =
|
|
2146
|
+
function findPackageRoot(from = path8.dirname(fileURLToPath(import.meta.url))) {
|
|
2147
|
+
let dir = path8.resolve(from);
|
|
1966
2148
|
for (; ; ) {
|
|
1967
|
-
const p =
|
|
1968
|
-
if (
|
|
2149
|
+
const p = path8.join(dir, "package.json");
|
|
2150
|
+
if (fs5.existsSync(p)) {
|
|
1969
2151
|
try {
|
|
1970
2152
|
if (readJson(p).name === "mcproc") return dir;
|
|
1971
2153
|
} catch {
|
|
1972
2154
|
}
|
|
1973
2155
|
}
|
|
1974
|
-
const parent =
|
|
2156
|
+
const parent = path8.dirname(dir);
|
|
1975
2157
|
if (parent === dir)
|
|
1976
2158
|
throw new McprocError(
|
|
1977
2159
|
"BUNDLE_INVALID",
|
|
@@ -1982,12 +2164,12 @@ function findPackageRoot(from = path5.dirname(fileURLToPath(import.meta.url))) {
|
|
|
1982
2164
|
}
|
|
1983
2165
|
function lockfileHash(root) {
|
|
1984
2166
|
return sha256(
|
|
1985
|
-
|
|
2167
|
+
fs5.readFileSync(path8.join(root, "runtime", "package-lock.json"))
|
|
1986
2168
|
);
|
|
1987
2169
|
}
|
|
1988
2170
|
function defaultPm2CacheDir(env = process.env) {
|
|
1989
|
-
return
|
|
1990
|
-
env.XDG_CACHE_HOME ||
|
|
2171
|
+
return path8.join(
|
|
2172
|
+
env.XDG_CACHE_HOME || path8.join(os3.homedir(), ".cache"),
|
|
1991
2173
|
"mcproc",
|
|
1992
2174
|
"pm2"
|
|
1993
2175
|
);
|
|
@@ -2013,31 +2195,31 @@ async function ensurePm2Bundle(opts = {}) {
|
|
|
2013
2195
|
const root = opts.root ?? findPackageRoot();
|
|
2014
2196
|
const cacheDir = opts.cacheDir ?? defaultPm2CacheDir();
|
|
2015
2197
|
const run = opts.run ?? defaultRunner;
|
|
2016
|
-
const pin = readJson(
|
|
2198
|
+
const pin = readJson(path8.join(root, "package.json")).dependencies?.pm2 ?? "";
|
|
2017
2199
|
if (!/^\d+\.\d+\.\d+$/.test(pin))
|
|
2018
2200
|
throw new McprocError(
|
|
2019
2201
|
"BUNDLE_INVALID",
|
|
2020
2202
|
`pm2 must be pinned to an exact version in mcproc's package.json, got "${pin}"`
|
|
2021
2203
|
);
|
|
2022
|
-
const runtimePin = readJson(
|
|
2204
|
+
const runtimePin = readJson(path8.join(root, "runtime", "package.json")).dependencies?.pm2 ?? "";
|
|
2023
2205
|
if (runtimePin !== pin)
|
|
2024
2206
|
throw new McprocError(
|
|
2025
2207
|
"BUNDLE_INVALID",
|
|
2026
2208
|
`runtime/package.json pins pm2 ${runtimePin}, package.json pins ${pin}`
|
|
2027
2209
|
);
|
|
2028
2210
|
const lockSha = lockfileHash(root);
|
|
2029
|
-
const target =
|
|
2211
|
+
const target = path8.join(cacheDir, `pm2-${pin}-${lockSha.slice(0, 12)}.tgz`);
|
|
2030
2212
|
const sidecar = `${target}.json`;
|
|
2031
|
-
if (!opts.force &&
|
|
2213
|
+
if (!opts.force && fs5.existsSync(target) && fs5.existsSync(sidecar)) {
|
|
2032
2214
|
const meta = readJson(sidecar);
|
|
2033
|
-
const data =
|
|
2215
|
+
const data = fs5.readFileSync(target);
|
|
2034
2216
|
if (meta.pm2 === pin && meta.lock_sha256 === lockSha && sha256(data) === meta.sha256)
|
|
2035
2217
|
return { version: pin, sha256: meta.sha256, data, path: target };
|
|
2036
2218
|
}
|
|
2037
|
-
const stage =
|
|
2219
|
+
const stage = fs5.mkdtempSync(path8.join(os3.tmpdir(), "mcproc-pm2-"));
|
|
2038
2220
|
try {
|
|
2039
2221
|
for (const f of ["package.json", "package-lock.json"])
|
|
2040
|
-
|
|
2222
|
+
fs5.copyFileSync(path8.join(root, "runtime", f), path8.join(stage, f));
|
|
2041
2223
|
try {
|
|
2042
2224
|
run(
|
|
2043
2225
|
"npm",
|
|
@@ -2054,14 +2236,14 @@ async function ensurePm2Bundle(opts = {}) {
|
|
|
2054
2236
|
);
|
|
2055
2237
|
}
|
|
2056
2238
|
const got = readJson(
|
|
2057
|
-
|
|
2239
|
+
path8.join(stage, "node_modules", "pm2", "package.json")
|
|
2058
2240
|
).version;
|
|
2059
2241
|
if (got !== pin)
|
|
2060
2242
|
throw new McprocError(
|
|
2061
2243
|
"BUNDLE_INVALID",
|
|
2062
2244
|
`npm installed pm2 ${got}, expected ${pin}`
|
|
2063
2245
|
);
|
|
2064
|
-
|
|
2246
|
+
fs5.rmSync(path8.join(stage, "node_modules", ".package-lock.json"), {
|
|
2065
2247
|
force: true
|
|
2066
2248
|
});
|
|
2067
2249
|
const tgz = execFileSync(
|
|
@@ -2069,9 +2251,9 @@ async function ensurePm2Bundle(opts = {}) {
|
|
|
2069
2251
|
[...tarFlags(), "-czf", "-", "node_modules"],
|
|
2070
2252
|
{ cwd: stage, maxBuffer: 1 << 30 }
|
|
2071
2253
|
);
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2254
|
+
fs5.mkdirSync(cacheDir, { recursive: true });
|
|
2255
|
+
fs5.writeFileSync(`${target}.part`, tgz);
|
|
2256
|
+
fs5.renameSync(`${target}.part`, target);
|
|
2075
2257
|
const meta = {
|
|
2076
2258
|
pm2: pin,
|
|
2077
2259
|
sha256: sha256(tgz),
|
|
@@ -2079,11 +2261,11 @@ async function ensurePm2Bundle(opts = {}) {
|
|
|
2079
2261
|
lock_sha256: lockSha,
|
|
2080
2262
|
built_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2081
2263
|
};
|
|
2082
|
-
|
|
2264
|
+
fs5.writeFileSync(sidecar, `${JSON.stringify(meta, null, 2)}
|
|
2083
2265
|
`);
|
|
2084
2266
|
return { version: pin, sha256: meta.sha256, data: tgz, path: target };
|
|
2085
2267
|
} finally {
|
|
2086
|
-
|
|
2268
|
+
fs5.rmSync(stage, { recursive: true, force: true });
|
|
2087
2269
|
}
|
|
2088
2270
|
}
|
|
2089
2271
|
|
|
@@ -2240,16 +2422,16 @@ ${original}`;
|
|
|
2240
2422
|
}
|
|
2241
2423
|
|
|
2242
2424
|
// src/runtime/node-dist.ts
|
|
2243
|
-
import
|
|
2244
|
-
import
|
|
2245
|
-
import
|
|
2425
|
+
import fs6 from "fs";
|
|
2426
|
+
import os4 from "os";
|
|
2427
|
+
import path9 from "path";
|
|
2246
2428
|
import { createHash as createHash2 } from "crypto";
|
|
2247
2429
|
var defaultFetcher = (url) => fetch(url, {
|
|
2248
2430
|
signal: AbortSignal.timeout(url.endsWith(".tar.gz") ? 6e5 : 3e4)
|
|
2249
2431
|
});
|
|
2250
2432
|
function defaultCacheDir(env = process.env) {
|
|
2251
|
-
return
|
|
2252
|
-
env.XDG_CACHE_HOME ||
|
|
2433
|
+
return path9.join(
|
|
2434
|
+
env.XDG_CACHE_HOME || path9.join(os4.homedir(), ".cache"),
|
|
2253
2435
|
"mcproc",
|
|
2254
2436
|
"node"
|
|
2255
2437
|
);
|
|
@@ -2285,7 +2467,7 @@ async function getBytes(fetcher, url) {
|
|
|
2285
2467
|
function cachedVersions(cacheDir, platform) {
|
|
2286
2468
|
let names = [];
|
|
2287
2469
|
try {
|
|
2288
|
-
names =
|
|
2470
|
+
names = fs6.readdirSync(cacheDir);
|
|
2289
2471
|
} catch {
|
|
2290
2472
|
return [];
|
|
2291
2473
|
}
|
|
@@ -2322,11 +2504,11 @@ async function fetchNodeTarball(version, platform, opts = {}) {
|
|
|
2322
2504
|
const fetcher = opts.fetcher ?? defaultFetcher;
|
|
2323
2505
|
const cacheDir = opts.cacheDir ?? defaultCacheDir();
|
|
2324
2506
|
const file = nodeDistFile(version, platform);
|
|
2325
|
-
const target =
|
|
2507
|
+
const target = path9.join(cacheDir, file);
|
|
2326
2508
|
const sidecar = `${target}.sha256`;
|
|
2327
|
-
if (
|
|
2328
|
-
const want2 =
|
|
2329
|
-
const data2 =
|
|
2509
|
+
if (fs6.existsSync(target) && fs6.existsSync(sidecar)) {
|
|
2510
|
+
const want2 = fs6.readFileSync(sidecar, "utf8").trim();
|
|
2511
|
+
const data2 = fs6.readFileSync(target);
|
|
2330
2512
|
if (sha2562(data2) === want2)
|
|
2331
2513
|
return {
|
|
2332
2514
|
version,
|
|
@@ -2356,10 +2538,10 @@ async function fetchNodeTarball(version, platform, opts = {}) {
|
|
|
2356
2538
|
`checksum mismatch for ${file}: SHASUMS256 says ${want}, downloaded ${got}`,
|
|
2357
2539
|
{ hint: HINT }
|
|
2358
2540
|
);
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2541
|
+
fs6.mkdirSync(cacheDir, { recursive: true });
|
|
2542
|
+
fs6.writeFileSync(`${target}.part`, data);
|
|
2543
|
+
fs6.renameSync(`${target}.part`, target);
|
|
2544
|
+
fs6.writeFileSync(sidecar, `${want}
|
|
2363
2545
|
`);
|
|
2364
2546
|
return {
|
|
2365
2547
|
version,
|
|
@@ -2437,9 +2619,9 @@ function defaultUnpackDeps() {
|
|
|
2437
2619
|
nodeVersion: (requested, platform) => resolveNodeVersion(requested, platform),
|
|
2438
2620
|
nodeTarball: async (version, platform) => {
|
|
2439
2621
|
const t = await fetchNodeTarball(version, platform);
|
|
2440
|
-
return { data:
|
|
2622
|
+
return { data: fs7.readFileSync(t.path), sha256: t.sha256 };
|
|
2441
2623
|
},
|
|
2442
|
-
hostname: () =>
|
|
2624
|
+
hostname: () => os5.hostname(),
|
|
2443
2625
|
now: () => /* @__PURE__ */ new Date()
|
|
2444
2626
|
};
|
|
2445
2627
|
}
|
|
@@ -2464,8 +2646,8 @@ async function unpack(target, opts, deps2) {
|
|
|
2464
2646
|
}
|
|
2465
2647
|
return res;
|
|
2466
2648
|
};
|
|
2467
|
-
const dotDir =
|
|
2468
|
-
const remoteManifest =
|
|
2649
|
+
const dotDir = path10.posix.join(host.cwd, ".mcproc");
|
|
2650
|
+
const remoteManifest = path10.posix.join(dotDir, "mcproc.yaml");
|
|
2469
2651
|
const pr = ensureOk(
|
|
2470
2652
|
await transport.exec(unpackProbeScript(rt, remoteManifest), { env }),
|
|
2471
2653
|
host,
|
|
@@ -2584,18 +2766,18 @@ async function unpack(target, opts, deps2) {
|
|
|
2584
2766
|
`mkdir -p ${shq(`${dotDir}/.generated`)} && { [ -e ${shq(gi)} ] || printf '*\\n' > ${shq(gi)}; } && cat > ${shq(remoteManifest)}`,
|
|
2585
2767
|
{
|
|
2586
2768
|
stdin: manifestCopy(
|
|
2587
|
-
|
|
2769
|
+
fs7.readFileSync(manifest.path, "utf8"),
|
|
2588
2770
|
deps2.hostname(),
|
|
2589
2771
|
deps2.now()
|
|
2590
2772
|
)
|
|
2591
2773
|
}
|
|
2592
2774
|
);
|
|
2593
|
-
const readme =
|
|
2594
|
-
if (
|
|
2775
|
+
const readme = path10.join(manifest.root, ".mcproc", "README.md");
|
|
2776
|
+
if (fs7.existsSync(readme))
|
|
2595
2777
|
await run(
|
|
2596
2778
|
"readme copy",
|
|
2597
2779
|
`[ -e ${shq(`${dotDir}/README.md`)} ] || cat > ${shq(`${dotDir}/README.md`)}`,
|
|
2598
|
-
{ stdin:
|
|
2780
|
+
{ stdin: fs7.readFileSync(readme, "utf8") }
|
|
2599
2781
|
);
|
|
2600
2782
|
manifestState = "written";
|
|
2601
2783
|
}
|
|
@@ -2631,7 +2813,7 @@ async function unpack(target, opts, deps2) {
|
|
|
2631
2813
|
}
|
|
2632
2814
|
|
|
2633
2815
|
// src/core/leases/view.ts
|
|
2634
|
-
import
|
|
2816
|
+
import fs10 from "fs";
|
|
2635
2817
|
|
|
2636
2818
|
// src/core/memory.ts
|
|
2637
2819
|
var KB = 1024;
|
|
@@ -2676,33 +2858,22 @@ async function readHostMemory(transport, host) {
|
|
|
2676
2858
|
return i < 0 ? null : parseMeminfo(res.stdout.slice(i + MARK.MEM.length));
|
|
2677
2859
|
}
|
|
2678
2860
|
|
|
2679
|
-
// src/core/state.ts
|
|
2680
|
-
import os4 from "os";
|
|
2681
|
-
import path8 from "path";
|
|
2682
|
-
function stateDir(env = process.env) {
|
|
2683
|
-
if (env.MCPROC_STATE_DIR) return path8.resolve(env.MCPROC_STATE_DIR);
|
|
2684
|
-
return path8.join(
|
|
2685
|
-
env.XDG_STATE_HOME || path8.join(os4.homedir(), ".local", "state"),
|
|
2686
|
-
"mcproc"
|
|
2687
|
-
);
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
2861
|
// src/core/leases/ensure.ts
|
|
2691
|
-
import
|
|
2692
|
-
import
|
|
2862
|
+
import fs9 from "fs";
|
|
2863
|
+
import path13 from "path";
|
|
2693
2864
|
|
|
2694
2865
|
// src/core/ecosystem.ts
|
|
2695
|
-
import
|
|
2866
|
+
import path11 from "path";
|
|
2696
2867
|
var LOG_STAMP = "YYYY-MM-DDTHH:mm:ss.SSS";
|
|
2697
2868
|
var STOP_TRAP = "trap 'exit 130' INT; trap 'exit 143' TERM; ";
|
|
2698
2869
|
function appCwd(host, app) {
|
|
2699
|
-
return
|
|
2870
|
+
return path11.posix.resolve(host.cwd, app.cwd);
|
|
2700
2871
|
}
|
|
2701
2872
|
function generatedDir(host) {
|
|
2702
|
-
return
|
|
2873
|
+
return path11.posix.join(host.cwd, ".mcproc", ".generated");
|
|
2703
2874
|
}
|
|
2704
2875
|
function ecosystemPath(host) {
|
|
2705
|
-
return
|
|
2876
|
+
return path11.posix.join(generatedDir(host), "ecosystem.json");
|
|
2706
2877
|
}
|
|
2707
2878
|
function buildEcosystem(m, host) {
|
|
2708
2879
|
const apps = Object.values(m.apps).map((app) => {
|
|
@@ -2732,10 +2903,10 @@ function buildEcosystem(m, host) {
|
|
|
2732
2903
|
}
|
|
2733
2904
|
|
|
2734
2905
|
// src/core/leases/store.ts
|
|
2735
|
-
import
|
|
2736
|
-
import
|
|
2737
|
-
import
|
|
2738
|
-
import
|
|
2906
|
+
import crypto2 from "crypto";
|
|
2907
|
+
import fs8 from "fs";
|
|
2908
|
+
import os6 from "os";
|
|
2909
|
+
import path12 from "path";
|
|
2739
2910
|
var ALLOWED = {
|
|
2740
2911
|
pending: ["starting", "running", "cancelled"],
|
|
2741
2912
|
starting: ["running", "failed", "cancelled"],
|
|
@@ -2776,7 +2947,7 @@ function parseHeld(raw) {
|
|
|
2776
2947
|
}
|
|
2777
2948
|
function readHeld(file) {
|
|
2778
2949
|
try {
|
|
2779
|
-
return parseHeld(
|
|
2950
|
+
return parseHeld(fs8.readFileSync(file, "utf8"));
|
|
2780
2951
|
} catch {
|
|
2781
2952
|
return null;
|
|
2782
2953
|
}
|
|
@@ -2791,13 +2962,13 @@ var LeaseStore = class {
|
|
|
2791
2962
|
* `beforeUnlink` (tests only) runs while a breaker holds the break guard, before the re-read that decides the unlink.
|
|
2792
2963
|
*/
|
|
2793
2964
|
constructor(stateDir2, deps2 = {}) {
|
|
2794
|
-
this.dir =
|
|
2965
|
+
this.dir = path12.join(stateDir2, "leases");
|
|
2795
2966
|
this.now = deps2.now ?? (() => Date.now());
|
|
2796
2967
|
this.pid = deps2.pid ?? process.pid;
|
|
2797
2968
|
this.beforeUnlink = deps2.beforeUnlink;
|
|
2798
2969
|
}
|
|
2799
2970
|
file(l) {
|
|
2800
|
-
return
|
|
2971
|
+
return path12.join(
|
|
2801
2972
|
this.dir,
|
|
2802
2973
|
`${String(l.seq).padStart(6, "0")}-${l.id}.json`
|
|
2803
2974
|
);
|
|
@@ -2806,11 +2977,11 @@ var LeaseStore = class {
|
|
|
2806
2977
|
return new Date(this.now()).toISOString();
|
|
2807
2978
|
}
|
|
2808
2979
|
write(l) {
|
|
2809
|
-
|
|
2980
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
2810
2981
|
const f = this.file(l);
|
|
2811
2982
|
const tmp = `${f}.${this.pid}.tmp`;
|
|
2812
|
-
|
|
2813
|
-
|
|
2983
|
+
fs8.writeFileSync(tmp, JSON.stringify(l, null, 2));
|
|
2984
|
+
fs8.renameSync(tmp, f);
|
|
2814
2985
|
return l;
|
|
2815
2986
|
}
|
|
2816
2987
|
/**
|
|
@@ -2820,13 +2991,13 @@ var LeaseStore = class {
|
|
|
2820
2991
|
* breakIfStale), never by moving it aside, so the lock path is never left free while a live holder has it.
|
|
2821
2992
|
*/
|
|
2822
2993
|
async withLock(fn) {
|
|
2823
|
-
|
|
2824
|
-
const lock =
|
|
2825
|
-
const token = `${this.pid}-${
|
|
2826
|
-
const tmp =
|
|
2994
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
2995
|
+
const lock = path12.join(this.dir, ".lock");
|
|
2996
|
+
const token = `${this.pid}-${crypto2.randomBytes(6).toString("hex")}`;
|
|
2997
|
+
const tmp = path12.join(this.dir, `.lock.${token}.tmp`);
|
|
2827
2998
|
const writeToken = () => {
|
|
2828
|
-
|
|
2829
|
-
|
|
2999
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
3000
|
+
fs8.writeFileSync(
|
|
2830
3001
|
tmp,
|
|
2831
3002
|
JSON.stringify({ pid: this.pid, at: this.now(), token })
|
|
2832
3003
|
);
|
|
@@ -2857,7 +3028,7 @@ var LeaseStore = class {
|
|
|
2857
3028
|
await new Promise((r2) => setTimeout(r2, 50));
|
|
2858
3029
|
}
|
|
2859
3030
|
} finally {
|
|
2860
|
-
|
|
3031
|
+
fs8.rmSync(tmp, { force: true });
|
|
2861
3032
|
}
|
|
2862
3033
|
LIVE_TOKENS.add(token);
|
|
2863
3034
|
try {
|
|
@@ -2872,7 +3043,7 @@ var LeaseStore = class {
|
|
|
2872
3043
|
}
|
|
2873
3044
|
/** Whether a parsed lock is a leftover: from before this boot, this process's own but not held, or old with a dead holder. */
|
|
2874
3045
|
heldStale(held) {
|
|
2875
|
-
if (held.at < this.now() -
|
|
3046
|
+
if (held.at < this.now() - os6.uptime() * 1e3) return true;
|
|
2876
3047
|
if (held.pid === process.pid && !LIVE_TOKENS.has(held.token)) return true;
|
|
2877
3048
|
return this.now() - held.at > LOCK_STALE_MS && !pidAlive(held.pid);
|
|
2878
3049
|
}
|
|
@@ -2883,15 +3054,15 @@ var LeaseStore = class {
|
|
|
2883
3054
|
sweepStrays() {
|
|
2884
3055
|
let names;
|
|
2885
3056
|
try {
|
|
2886
|
-
names =
|
|
3057
|
+
names = fs8.readdirSync(this.dir);
|
|
2887
3058
|
} catch {
|
|
2888
3059
|
return;
|
|
2889
3060
|
}
|
|
2890
3061
|
for (const n of names) {
|
|
2891
3062
|
if (!STRAY.test(n)) continue;
|
|
2892
|
-
const f =
|
|
3063
|
+
const f = path12.join(this.dir, n);
|
|
2893
3064
|
if (this.mtimeAgeMs(f) > LOCK_STALE_MS)
|
|
2894
|
-
|
|
3065
|
+
fs8.rmSync(f, { recursive: true, force: true });
|
|
2895
3066
|
}
|
|
2896
3067
|
}
|
|
2897
3068
|
/**
|
|
@@ -2902,12 +3073,12 @@ var LeaseStore = class {
|
|
|
2902
3073
|
tryLink(tmp, lock, rewrite) {
|
|
2903
3074
|
for (let attempt = 0; ; attempt++) {
|
|
2904
3075
|
try {
|
|
2905
|
-
|
|
3076
|
+
fs8.linkSync(tmp, lock);
|
|
2906
3077
|
return true;
|
|
2907
3078
|
} catch (e) {
|
|
2908
3079
|
const code = e.code;
|
|
2909
3080
|
if (code === "EEXIST") return false;
|
|
2910
|
-
if (code === "ENOENT" && attempt === 0 && !
|
|
3081
|
+
if (code === "ENOENT" && attempt === 0 && !fs8.existsSync(tmp)) {
|
|
2911
3082
|
rewrite();
|
|
2912
3083
|
continue;
|
|
2913
3084
|
}
|
|
@@ -2919,7 +3090,7 @@ var LeaseStore = class {
|
|
|
2919
3090
|
stillStale(lock, judged) {
|
|
2920
3091
|
let raw;
|
|
2921
3092
|
try {
|
|
2922
|
-
raw =
|
|
3093
|
+
raw = fs8.readFileSync(lock, "utf8");
|
|
2923
3094
|
} catch (e) {
|
|
2924
3095
|
if (e.code === "ENOENT") return false;
|
|
2925
3096
|
throw e;
|
|
@@ -2932,7 +3103,7 @@ var LeaseStore = class {
|
|
|
2932
3103
|
* or, with no readable holder, the directory's mtime is older than LOCK_STALE_MS.
|
|
2933
3104
|
*/
|
|
2934
3105
|
guardStale(guard) {
|
|
2935
|
-
const held = readHeld(
|
|
3106
|
+
const held = readHeld(path12.join(guard, "holder"));
|
|
2936
3107
|
if (held)
|
|
2937
3108
|
return this.now() - held.at > LOCK_STALE_MS && !pidAlive(held.pid);
|
|
2938
3109
|
return this.mtimeAgeMs(guard) > LOCK_STALE_MS;
|
|
@@ -2950,7 +3121,7 @@ var LeaseStore = class {
|
|
|
2950
3121
|
breakIfStale(lock, token, take) {
|
|
2951
3122
|
let raw;
|
|
2952
3123
|
try {
|
|
2953
|
-
raw =
|
|
3124
|
+
raw = fs8.readFileSync(lock, "utf8");
|
|
2954
3125
|
} catch (e) {
|
|
2955
3126
|
if (e.code === "ENOENT") return "retry";
|
|
2956
3127
|
throw e;
|
|
@@ -2960,57 +3131,57 @@ var LeaseStore = class {
|
|
|
2960
3131
|
if (!stale) return "wait";
|
|
2961
3132
|
const guard = `${lock}.break`;
|
|
2962
3133
|
try {
|
|
2963
|
-
|
|
3134
|
+
fs8.mkdirSync(guard);
|
|
2964
3135
|
} catch (e) {
|
|
2965
3136
|
if (e.code !== "EEXIST") throw e;
|
|
2966
3137
|
if (!this.guardStale(guard)) return "wait";
|
|
2967
3138
|
const moved = `${guard}.${token}.stale`;
|
|
2968
3139
|
try {
|
|
2969
|
-
|
|
3140
|
+
fs8.renameSync(guard, moved);
|
|
2970
3141
|
} catch (err) {
|
|
2971
3142
|
if (err.code === "ENOENT") return "retry";
|
|
2972
3143
|
throw err;
|
|
2973
3144
|
}
|
|
2974
|
-
|
|
3145
|
+
fs8.rmSync(moved, { recursive: true, force: true });
|
|
2975
3146
|
return "retry";
|
|
2976
3147
|
}
|
|
2977
3148
|
try {
|
|
2978
|
-
|
|
2979
|
-
|
|
3149
|
+
fs8.writeFileSync(
|
|
3150
|
+
path12.join(guard, "holder"),
|
|
2980
3151
|
JSON.stringify({ pid: this.pid, at: this.now(), token })
|
|
2981
3152
|
);
|
|
2982
3153
|
this.beforeUnlink?.();
|
|
2983
3154
|
if (!this.stillStale(lock, held)) return "retry";
|
|
2984
3155
|
try {
|
|
2985
|
-
|
|
3156
|
+
fs8.unlinkSync(lock);
|
|
2986
3157
|
} catch (e) {
|
|
2987
3158
|
if (e.code !== "ENOENT") throw e;
|
|
2988
3159
|
}
|
|
2989
3160
|
return take() ? "taken" : "retry";
|
|
2990
3161
|
} finally {
|
|
2991
|
-
|
|
3162
|
+
fs8.rmSync(guard, { recursive: true, force: true });
|
|
2992
3163
|
}
|
|
2993
3164
|
}
|
|
2994
3165
|
/** Age of a lock file by its modification time on the file system's clock; 0 when it is gone. */
|
|
2995
3166
|
mtimeAgeMs(lock) {
|
|
2996
3167
|
try {
|
|
2997
|
-
return Date.now() -
|
|
3168
|
+
return Date.now() - fs8.statSync(lock).mtimeMs;
|
|
2998
3169
|
} catch {
|
|
2999
3170
|
return 0;
|
|
3000
3171
|
}
|
|
3001
3172
|
}
|
|
3002
3173
|
/** Unlinks the lock only when it still carries this holder's token; a lock it does not own is never removed. */
|
|
3003
3174
|
release(lock, token) {
|
|
3004
|
-
if (readHeld(lock)?.token === token)
|
|
3175
|
+
if (readHeld(lock)?.token === token) fs8.rmSync(lock, { force: true });
|
|
3005
3176
|
}
|
|
3006
3177
|
/** Every readable lease, in sequence order; a half-written file is skipped. */
|
|
3007
3178
|
list() {
|
|
3008
|
-
if (!
|
|
3009
|
-
return
|
|
3179
|
+
if (!fs8.existsSync(this.dir)) return [];
|
|
3180
|
+
return fs8.readdirSync(this.dir).filter((f) => f.endsWith(".json")).sort().flatMap((f) => {
|
|
3010
3181
|
try {
|
|
3011
3182
|
return [
|
|
3012
3183
|
JSON.parse(
|
|
3013
|
-
|
|
3184
|
+
fs8.readFileSync(path12.join(this.dir, f), "utf8")
|
|
3014
3185
|
)
|
|
3015
3186
|
];
|
|
3016
3187
|
} catch {
|
|
@@ -3023,25 +3194,25 @@ var LeaseStore = class {
|
|
|
3023
3194
|
return this.list().find((l) => l.id === id) ?? null;
|
|
3024
3195
|
}
|
|
3025
3196
|
nextSeq() {
|
|
3026
|
-
const f =
|
|
3197
|
+
const f = path12.join(this.dir, ".seq");
|
|
3027
3198
|
const onDisk = Math.max(
|
|
3028
3199
|
0,
|
|
3029
|
-
...
|
|
3200
|
+
...fs8.readdirSync(this.dir).filter((name2) => name2.endsWith(".json")).map((name2) => Number(name2.split("-")[0])).filter(Number.isFinite)
|
|
3030
3201
|
);
|
|
3031
|
-
const parsed =
|
|
3202
|
+
const parsed = fs8.existsSync(f) ? Number(fs8.readFileSync(f, "utf8")) : 0;
|
|
3032
3203
|
const n = Math.max(Number.isFinite(parsed) ? parsed : 0, onDisk);
|
|
3033
3204
|
const tmp = `${f}.${this.pid}.tmp`;
|
|
3034
|
-
|
|
3035
|
-
|
|
3205
|
+
fs8.writeFileSync(tmp, String(n + 1));
|
|
3206
|
+
fs8.renameSync(tmp, f);
|
|
3036
3207
|
return n + 1;
|
|
3037
3208
|
}
|
|
3038
3209
|
/** A new lease, pending (queued) or running (admitted at once), with `child_pid` the process running its start; call it under the lock. */
|
|
3039
3210
|
async create(input, state, opts = {}) {
|
|
3040
|
-
|
|
3211
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
3041
3212
|
const at = this.stamp();
|
|
3042
3213
|
const l = {
|
|
3043
3214
|
...input,
|
|
3044
|
-
id: `l_${
|
|
3215
|
+
id: `l_${crypto2.randomBytes(8).toString("base64url").replace(/[^a-z0-9]/gi, "").toLowerCase().padEnd(10, "0").slice(0, 10)}`,
|
|
3045
3216
|
seq: this.nextSeq(),
|
|
3046
3217
|
state,
|
|
3047
3218
|
child_pid: opts.child_pid ?? null,
|
|
@@ -3119,7 +3290,7 @@ var LeaseStore = class {
|
|
|
3119
3290
|
for (const l of this.list()) {
|
|
3120
3291
|
if (!ENDED.has(l.state) || !l.ended_at) continue;
|
|
3121
3292
|
if (this.now() - Date.parse(l.ended_at) > maxAgeMs) {
|
|
3122
|
-
|
|
3293
|
+
fs8.rmSync(this.file(l), { force: true });
|
|
3123
3294
|
n++;
|
|
3124
3295
|
}
|
|
3125
3296
|
}
|
|
@@ -3137,7 +3308,7 @@ var EXIT_POLL_MS = 250;
|
|
|
3137
3308
|
function exitingAt(dir) {
|
|
3138
3309
|
try {
|
|
3139
3310
|
const v = JSON.parse(
|
|
3140
|
-
|
|
3311
|
+
fs9.readFileSync(path13.join(dir, WATCHER_NOTE), "utf8")
|
|
3141
3312
|
);
|
|
3142
3313
|
const at = typeof v.exiting_at === "string" ? Date.parse(v.exiting_at) : Number.NaN;
|
|
3143
3314
|
return Number.isFinite(at) ? at : null;
|
|
@@ -3146,9 +3317,10 @@ function exitingAt(dir) {
|
|
|
3146
3317
|
}
|
|
3147
3318
|
}
|
|
3148
3319
|
function watcherEntry(env = process.env, argv = process.argv) {
|
|
3149
|
-
if (env.MCPROC_CLI_ENTRY) return
|
|
3320
|
+
if (env.MCPROC_CLI_ENTRY) return path13.resolve(env.MCPROC_CLI_ENTRY);
|
|
3150
3321
|
const own2 = argv[1] ?? "";
|
|
3151
|
-
|
|
3322
|
+
const real = own2 ? realOf(own2) : "";
|
|
3323
|
+
if (real.endsWith("cli.js")) return real;
|
|
3152
3324
|
throw new McprocError(
|
|
3153
3325
|
"NOT_STARTED",
|
|
3154
3326
|
`cannot launch the lease watcher: this mcproc runs from ${own2 || "an unknown entry"}, not from a built cli.js`,
|
|
@@ -3180,10 +3352,10 @@ async function lookAndLaunch(ctx, dir, leaving) {
|
|
|
3180
3352
|
if (await watcherOnline(ctx) && (leaving === null || exitingAt(dir) !== leaving))
|
|
3181
3353
|
return "online";
|
|
3182
3354
|
const entry = watcherEntry();
|
|
3183
|
-
const file =
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3355
|
+
const file = path13.join(dir, "watcher.ecosystem.json");
|
|
3356
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
3357
|
+
fs9.rmSync(path13.join(dir, WATCHER_NOTE), { force: true });
|
|
3358
|
+
fs9.writeFileSync(
|
|
3187
3359
|
file,
|
|
3188
3360
|
JSON.stringify(
|
|
3189
3361
|
{
|
|
@@ -3219,7 +3391,7 @@ async function lookAndLaunch(ctx, dir, leaving) {
|
|
|
3219
3391
|
// src/core/leases/view.ts
|
|
3220
3392
|
async function leasesView(dir = stateDir(), ctx) {
|
|
3221
3393
|
const store = new LeaseStore(dir);
|
|
3222
|
-
if (
|
|
3394
|
+
if (fs10.existsSync(store.dir))
|
|
3223
3395
|
await store.withLock(() => store.prune()).catch(() => {
|
|
3224
3396
|
});
|
|
3225
3397
|
const leases = store.list().map((l) => ({ ...l, position: store.positionOf(l.id) }));
|
|
@@ -3274,17 +3446,12 @@ function cancelLease(id, dir = stateDir()) {
|
|
|
3274
3446
|
});
|
|
3275
3447
|
}
|
|
3276
3448
|
|
|
3277
|
-
// src/core/
|
|
3278
|
-
import
|
|
3279
|
-
import fs12 from "fs";
|
|
3280
|
-
import path16 from "path";
|
|
3281
|
-
|
|
3282
|
-
// src/core/actions.ts
|
|
3283
|
-
import path14 from "path";
|
|
3449
|
+
// src/core/stacks/collect.ts
|
|
3450
|
+
import os8 from "os";
|
|
3284
3451
|
|
|
3285
3452
|
// src/core/list.ts
|
|
3286
|
-
import
|
|
3287
|
-
import
|
|
3453
|
+
import fs11 from "fs";
|
|
3454
|
+
import path14 from "path";
|
|
3288
3455
|
|
|
3289
3456
|
// src/core/metrics.ts
|
|
3290
3457
|
function cpuPercent(pids, before, after, clkTck, intervalSec) {
|
|
@@ -3472,7 +3639,7 @@ function parseRead(stdout, id, offset, opts = {}) {
|
|
|
3472
3639
|
}
|
|
3473
3640
|
const skipped = offset !== null && offset >= 0 && size >= offset ? Math.max(0, start - offset) : 0;
|
|
3474
3641
|
const nextOffset = Math.max(start, size - partial);
|
|
3475
|
-
const
|
|
3642
|
+
const short2 = buf.length < size - ctxStart;
|
|
3476
3643
|
const anchor = hex && HEX.test(hex) ? { at: size, hex } : null;
|
|
3477
3644
|
return {
|
|
3478
3645
|
size,
|
|
@@ -3481,17 +3648,17 @@ function parseRead(stdout, id, offset, opts = {}) {
|
|
|
3481
3648
|
lines,
|
|
3482
3649
|
contextOnly,
|
|
3483
3650
|
nextOffset,
|
|
3484
|
-
short,
|
|
3651
|
+
short: short2,
|
|
3485
3652
|
anchor,
|
|
3486
|
-
nextAnchor: anchorAt(buf, nextOffset, { size, ctxStart, short, anchor })
|
|
3653
|
+
nextAnchor: anchorAt(buf, nextOffset, { size, ctxStart, short: short2, anchor })
|
|
3487
3654
|
};
|
|
3488
3655
|
}
|
|
3489
3656
|
function anchorAt(buf, at, read) {
|
|
3490
|
-
const { size, ctxStart, short, anchor } = read;
|
|
3657
|
+
const { size, ctxStart, short: short2, anchor } = read;
|
|
3491
3658
|
if (at === size) return anchor;
|
|
3492
3659
|
const n = Math.min(16, at);
|
|
3493
3660
|
const end = buf.length - (size - at);
|
|
3494
|
-
if (!anchor ||
|
|
3661
|
+
if (!anchor || short2 || n <= 0 || at - n < ctxStart || end - n < 0)
|
|
3495
3662
|
return null;
|
|
3496
3663
|
const tail = buf.subarray(end - n);
|
|
3497
3664
|
if (tail.includes(REPLACEMENT)) return null;
|
|
@@ -3635,8 +3802,8 @@ var LogMarks = class {
|
|
|
3635
3802
|
this.m.set(key, e);
|
|
3636
3803
|
}
|
|
3637
3804
|
};
|
|
3638
|
-
var sinceMark = (
|
|
3639
|
-
path:
|
|
3805
|
+
var sinceMark = (path23, r) => ({
|
|
3806
|
+
path: path23,
|
|
3640
3807
|
offset: r.nextOffset,
|
|
3641
3808
|
...r.nextAnchor ? { at: r.nextAnchor.at, anchor: r.nextAnchor.hex } : {}
|
|
3642
3809
|
});
|
|
@@ -3667,15 +3834,15 @@ async function readSince(fleet, names, opts) {
|
|
|
3667
3834
|
);
|
|
3668
3835
|
const files = targets.flatMap(
|
|
3669
3836
|
(t) => streams2.map((stream2) => {
|
|
3670
|
-
const
|
|
3837
|
+
const path23 = stream2 === "out" ? t.p.out_log : t.p.err_log;
|
|
3671
3838
|
const key = LogMarks.key(fleet.manifest.path, g.host, t.app);
|
|
3672
3839
|
const mark = spec.kind === "last" ? opts.marks?.get(key)?.[stream2] : void 0;
|
|
3673
|
-
const same = mark !== void 0 && mark.path ===
|
|
3840
|
+
const same = mark !== void 0 && mark.path === path23;
|
|
3674
3841
|
const anchor = same && mark.at !== void 0 && mark.anchor !== void 0 ? { at: mark.at, hex: mark.anchor } : null;
|
|
3675
3842
|
return {
|
|
3676
3843
|
app: t.app,
|
|
3677
3844
|
stream: stream2,
|
|
3678
|
-
path:
|
|
3845
|
+
path: path23,
|
|
3679
3846
|
key,
|
|
3680
3847
|
offset: same ? mark.offset : null,
|
|
3681
3848
|
anchor,
|
|
@@ -4108,38 +4275,14 @@ function urlFields(u) {
|
|
|
4108
4275
|
url_reason: null
|
|
4109
4276
|
};
|
|
4110
4277
|
}
|
|
4111
|
-
async function
|
|
4112
|
-
const withMetrics = opts.metrics !== "none";
|
|
4113
|
-
const cpuSample = (opts.cpu ?? "inline") === "inline" && withMetrics;
|
|
4114
|
-
const which = opts.urls ?? "check";
|
|
4115
|
-
const asked = which === "none" ? [] : Object.values(ctx.manifest.apps).flatMap(
|
|
4116
|
-
(a) => a.url !== null && (which === "check" || which.includes(a.name)) ? [{ name: a.name, url: a.url, url_check: a.url_check }] : []
|
|
4117
|
-
);
|
|
4118
|
-
const urls = [
|
|
4119
|
-
...new Set(
|
|
4120
|
-
asked.filter((a) => ctx.host.local || a.url_check !== "here").map((a) => a.url)
|
|
4121
|
-
)
|
|
4122
|
-
];
|
|
4123
|
-
const containers = withMetrics && opts.containers === "probe" ? [
|
|
4124
|
-
...new Set(
|
|
4125
|
-
Object.values(ctx.manifest.apps).flatMap((a) => a.containers)
|
|
4126
|
-
)
|
|
4127
|
-
] : [];
|
|
4278
|
+
async function probeHost(link, ask) {
|
|
4128
4279
|
const res = ensureOk(
|
|
4129
|
-
await
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
}),
|
|
4136
|
-
{
|
|
4137
|
-
env: ctx.pm2.env(),
|
|
4138
|
-
// each url adds its bounded lookup and request: many on a host whose resolver drops packets outlast the default
|
|
4139
|
-
timeoutMs: probeTimeoutMs(urls.length)
|
|
4140
|
-
}
|
|
4141
|
-
),
|
|
4142
|
-
ctx.host,
|
|
4280
|
+
await link.transport.exec(probeScript(link.pm2.bin, ask), {
|
|
4281
|
+
env: link.pm2.env(),
|
|
4282
|
+
// each url adds its bounded lookup and request: many on a host whose resolver drops packets outlast the default
|
|
4283
|
+
timeoutMs: probeTimeoutMs(ask.urls.length)
|
|
4284
|
+
}),
|
|
4285
|
+
link.host,
|
|
4143
4286
|
"probe"
|
|
4144
4287
|
);
|
|
4145
4288
|
const sampledAt = Date.now();
|
|
@@ -4147,9 +4290,9 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4147
4290
|
const tail = (res.stderr || res.stdout).trim().split("\n").slice(-5).join("\n");
|
|
4148
4291
|
throw new McprocError(
|
|
4149
4292
|
"TRANSPORT_FAILED",
|
|
4150
|
-
`the shell on ${
|
|
4293
|
+
`the shell on ${link.transport.describe()} did not run the probe (exit ${res.code}): ${tail}`,
|
|
4151
4294
|
{
|
|
4152
|
-
hint: `Check hosts.${
|
|
4295
|
+
hint: `Check hosts.${link.host.name}.shell (default /bin/bash -lc) exists on that host.`
|
|
4153
4296
|
}
|
|
4154
4297
|
);
|
|
4155
4298
|
}
|
|
@@ -4157,27 +4300,37 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4157
4300
|
if (data.jlistRc === 127)
|
|
4158
4301
|
throw new McprocError(
|
|
4159
4302
|
"PM2_NOT_FOUND",
|
|
4160
|
-
`pm2 not found on ${
|
|
4303
|
+
`pm2 not found on ${link.transport.describe()}`,
|
|
4161
4304
|
{ hint: PM2_NOT_FOUND_HINT }
|
|
4162
4305
|
);
|
|
4163
4306
|
if (data.jlistRc !== 0)
|
|
4164
4307
|
throw new McprocError(
|
|
4165
4308
|
"PM2_FAILED",
|
|
4166
|
-
`pm2 jlist exited ${data.jlistRc} on ${
|
|
4309
|
+
`pm2 jlist exited ${data.jlistRc} on ${link.transport.describe()}`,
|
|
4167
4310
|
{ details: res.stderr.slice(-500) }
|
|
4168
4311
|
);
|
|
4169
|
-
|
|
4312
|
+
return { data, procs: parseJlist(data.jlistRaw), sampledAt };
|
|
4313
|
+
}
|
|
4314
|
+
function askedUrls(view, opts) {
|
|
4315
|
+
const which = opts.urls ?? "check";
|
|
4316
|
+
return which === "none" ? [] : Object.values(view.manifest.apps).flatMap(
|
|
4317
|
+
(a) => a.url !== null && (which === "check" || which.includes(a.name)) ? [{ name: a.name, url: a.url, url_check: a.url_check }] : []
|
|
4318
|
+
);
|
|
4319
|
+
}
|
|
4320
|
+
async function deriveSnapshot(view, probe, opts = {}) {
|
|
4321
|
+
const { data, procs, sampledAt } = probe;
|
|
4322
|
+
const asked = askedUrls(view, opts);
|
|
4170
4323
|
const pids = appPids(
|
|
4171
|
-
|
|
4324
|
+
view.manifest.apps,
|
|
4172
4325
|
procs,
|
|
4173
4326
|
data.tree,
|
|
4174
|
-
|
|
4327
|
+
view.manifest.project
|
|
4175
4328
|
);
|
|
4176
4329
|
const metricOpts = opts.prev ? { prev: opts.prev.sample, intervalSec: (sampledAt - opts.prev.at) / 1e3 } : {};
|
|
4177
4330
|
const metrics = {};
|
|
4178
4331
|
for (const [name2, set] of pids)
|
|
4179
4332
|
metrics[name2] = treeMetrics(set, data.stats, data.listeners, metricOpts);
|
|
4180
|
-
for (const app of Object.values(
|
|
4333
|
+
for (const app of Object.values(view.manifest.apps)) {
|
|
4181
4334
|
const m = metrics[app.name];
|
|
4182
4335
|
if (!m || app.containers.length === 0) continue;
|
|
4183
4336
|
const mib = app.containers.reduce(
|
|
@@ -4193,13 +4346,13 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4193
4346
|
}
|
|
4194
4347
|
const urlChecks = await settleUrls(
|
|
4195
4348
|
asked,
|
|
4196
|
-
{ host:
|
|
4349
|
+
{ host: view.host.name, local: view.host.local },
|
|
4197
4350
|
data,
|
|
4198
|
-
|
|
4351
|
+
view.hereCheck
|
|
4199
4352
|
);
|
|
4200
4353
|
return {
|
|
4201
4354
|
procs,
|
|
4202
|
-
ports: joinPorts(
|
|
4355
|
+
ports: joinPorts(view.manifest.apps, procs, data, view.manifest.project),
|
|
4203
4356
|
listeners: data.listeners,
|
|
4204
4357
|
tree: data.tree,
|
|
4205
4358
|
stats: data.stats,
|
|
@@ -4208,9 +4361,30 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4208
4361
|
urlChecks,
|
|
4209
4362
|
zone: data.zone,
|
|
4210
4363
|
memory: data.memory,
|
|
4211
|
-
leases:
|
|
4364
|
+
leases: view.host.local && opts.leases !== "none" ? leasesCovering(stateDir(), view.manifest) : []
|
|
4212
4365
|
};
|
|
4213
4366
|
}
|
|
4367
|
+
async function snapshot(ctx, opts = {}) {
|
|
4368
|
+
const withMetrics = opts.metrics !== "none";
|
|
4369
|
+
const asked = askedUrls(ctx, opts);
|
|
4370
|
+
const urls = [
|
|
4371
|
+
...new Set(
|
|
4372
|
+
asked.filter((a) => ctx.host.local || a.url_check !== "here").map((a) => a.url)
|
|
4373
|
+
)
|
|
4374
|
+
];
|
|
4375
|
+
const containers = withMetrics && opts.containers === "probe" ? [
|
|
4376
|
+
...new Set(
|
|
4377
|
+
Object.values(ctx.manifest.apps).flatMap((a) => a.containers)
|
|
4378
|
+
)
|
|
4379
|
+
] : [];
|
|
4380
|
+
const probe = await probeHost(ctx, {
|
|
4381
|
+
cpuSample: (opts.cpu ?? "inline") === "inline" && withMetrics,
|
|
4382
|
+
metrics: withMetrics,
|
|
4383
|
+
urls,
|
|
4384
|
+
containers
|
|
4385
|
+
});
|
|
4386
|
+
return deriveSnapshot(ctx, probe, opts);
|
|
4387
|
+
}
|
|
4214
4388
|
var SIGNAL_EXITS = /* @__PURE__ */ new Set([130, 143]);
|
|
4215
4389
|
function statusWord(p, app) {
|
|
4216
4390
|
if (!p) return "not-started";
|
|
@@ -4345,8 +4519,8 @@ var depView = (d) => ({
|
|
|
4345
4519
|
timeout_s: d.timeoutMs / 1e3
|
|
4346
4520
|
});
|
|
4347
4521
|
function describeProject(m) {
|
|
4348
|
-
const readmePath =
|
|
4349
|
-
const readme =
|
|
4522
|
+
const readmePath = path14.join(m.root, ".mcproc", "README.md");
|
|
4523
|
+
const readme = fs11.existsSync(readmePath) ? fs11.readFileSync(readmePath, "utf8") : null;
|
|
4350
4524
|
return {
|
|
4351
4525
|
project: m.project,
|
|
4352
4526
|
root: m.root,
|
|
@@ -4377,9 +4551,12 @@ function describeProject(m) {
|
|
|
4377
4551
|
};
|
|
4378
4552
|
}
|
|
4379
4553
|
|
|
4554
|
+
// src/core/actions.ts
|
|
4555
|
+
import path16 from "path";
|
|
4556
|
+
|
|
4380
4557
|
// src/core/logfollow.ts
|
|
4381
4558
|
import { open } from "fs/promises";
|
|
4382
|
-
import
|
|
4559
|
+
import path15 from "path";
|
|
4383
4560
|
|
|
4384
4561
|
// src/core/logwatch.ts
|
|
4385
4562
|
import { watch as fsWatch } from "fs";
|
|
@@ -4722,8 +4899,8 @@ function hostStreamFor(t) {
|
|
|
4722
4899
|
var MAX_PENDING = 65536;
|
|
4723
4900
|
var MAX_READY = 1e4;
|
|
4724
4901
|
function chunkScript(reqs) {
|
|
4725
|
-
return reqs.map(({ path:
|
|
4726
|
-
const f = shq(
|
|
4902
|
+
return reqs.map(({ path: path23, off, anchor }, i) => {
|
|
4903
|
+
const f = shq(path23);
|
|
4727
4904
|
const tail = `st=$((sz>${HISTORY_BYTES}?sz-${HISTORY_BYTES}:0))`;
|
|
4728
4905
|
const replaced = off !== null && anchor ? ` || [ "${hex16(f, String(off))}" != '${anchor}' ]` : "";
|
|
4729
4906
|
const pick = off === null ? `k=H; ${tail}` : `if [ "$sz" -lt ${off} ]${replaced}; then k=R; ${tail}; elif [ $((sz-${off})) -gt ${READ_CAP2} ]; then k=S; st=$((sz-${READ_CAP2})); else k=N; st=${off}; fi`;
|
|
@@ -4873,8 +5050,8 @@ var LogFollower = class {
|
|
|
4873
5050
|
}
|
|
4874
5051
|
const byDir = /* @__PURE__ */ new Map();
|
|
4875
5052
|
for (const p of [this.paths.out, this.paths.err]) {
|
|
4876
|
-
const dir =
|
|
4877
|
-
byDir.set(dir, (byDir.get(dir) ?? /* @__PURE__ */ new Set()).add(
|
|
5053
|
+
const dir = path15.dirname(p);
|
|
5054
|
+
byDir.set(dir, (byDir.get(dir) ?? /* @__PURE__ */ new Set()).add(path15.basename(p)));
|
|
4878
5055
|
}
|
|
4879
5056
|
for (const [dir, names] of byDir)
|
|
4880
5057
|
this.unwatch.push(
|
|
@@ -5247,6 +5424,9 @@ function detectReloads(input, rules, opts) {
|
|
|
5247
5424
|
return [...t.push(input, 0), ...t.flush()];
|
|
5248
5425
|
}
|
|
5249
5426
|
|
|
5427
|
+
// src/mcp/render.ts
|
|
5428
|
+
import os7 from "os";
|
|
5429
|
+
|
|
5250
5430
|
// src/core/logstream.ts
|
|
5251
5431
|
var WAKE_GAP_MS = 50;
|
|
5252
5432
|
function signals() {
|
|
@@ -5442,42 +5622,242 @@ async function streamLogs(sources, opts, deps2) {
|
|
|
5442
5622
|
}
|
|
5443
5623
|
}
|
|
5444
5624
|
|
|
5445
|
-
// src/
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
}
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5625
|
+
// src/monit/screentext.ts
|
|
5626
|
+
import { charWidth, chars } from "@pm2/blessed/lib/unicode.js";
|
|
5627
|
+
var segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
5628
|
+
var EMOJI_PRESENTATION = new RegExp("\\p{Emoji_Presentation}", "u");
|
|
5629
|
+
var PICTOGRAPH = new RegExp("\\p{Extended_Pictographic}", "u");
|
|
5630
|
+
var REGIONAL = new RegExp("\\p{Regional_Indicator}", "u");
|
|
5631
|
+
var KEYCAP = /^[0-9#*]\uFE0F?\u20E3$/;
|
|
5632
|
+
var INVISIBLE = /[\p{Cc}\p{Cf}]/u;
|
|
5633
|
+
var LINE_BREAK = /\r\n|[\n\v\f\u0085\u2028\u2029]/g;
|
|
5634
|
+
var MARK2 = new RegExp("\\p{M}", "u");
|
|
5635
|
+
var SPACING_MARK = new RegExp("\\p{Mc}", "u");
|
|
5636
|
+
var BLESSED_WIDE = new RegExp(chars.wide.source);
|
|
5637
|
+
var NARROW = {
|
|
5638
|
+
"\u2705": "\u2714",
|
|
5639
|
+
"\u274C": "\u2716",
|
|
5640
|
+
"\u274E": "\u2716",
|
|
5641
|
+
"\u2757": "!",
|
|
5642
|
+
"\u2755": "!",
|
|
5643
|
+
"\u2753": "?",
|
|
5644
|
+
"\u2754": "?"
|
|
5645
|
+
};
|
|
5646
|
+
var TAB = 8;
|
|
5647
|
+
function displayWidth(s) {
|
|
5648
|
+
let w = 0;
|
|
5649
|
+
for (const ch of s.includes("\x1B") ? s.replace(SGR_RE, "") : s)
|
|
5650
|
+
w += charWidth(ch.codePointAt(0)) === 2 ? 2 : 1;
|
|
5651
|
+
return w;
|
|
5652
|
+
}
|
|
5653
|
+
function columns(s) {
|
|
5654
|
+
let w = 0;
|
|
5655
|
+
for (const ch of s) w += charWidth(ch.codePointAt(0));
|
|
5656
|
+
return w;
|
|
5657
|
+
}
|
|
5658
|
+
function keepMark(ch, cp) {
|
|
5659
|
+
return cp <= 65535 && charWidth(cp) === (SPACING_MARK.test(ch) ? 1 : 0);
|
|
5660
|
+
}
|
|
5661
|
+
function cluster(c) {
|
|
5662
|
+
if (c === " ") return c;
|
|
5663
|
+
const first = String.fromCodePoint(c.codePointAt(0));
|
|
5664
|
+
if (REGIONAL.test(first))
|
|
5665
|
+
return [...c].filter((ch) => REGIONAL.test(ch)).map((ch) => String.fromCharCode(ch.codePointAt(0) - 127462 + 65)).join("");
|
|
5666
|
+
if (KEYCAP.test(c)) return c[0];
|
|
5667
|
+
if (PICTOGRAPH.test(first) || EMOJI_PRESENTATION.test(first)) {
|
|
5668
|
+
if (NARROW[first]) return NARROW[first];
|
|
5669
|
+
return !EMOJI_PRESENTATION.test(first) && first.length === 1 ? first : "?";
|
|
5670
|
+
}
|
|
5671
|
+
let out = "";
|
|
5672
|
+
for (const ch of c) {
|
|
5673
|
+
if (INVISIBLE.test(ch)) continue;
|
|
5674
|
+
const cp = ch.codePointAt(0);
|
|
5675
|
+
if (MARK2.test(ch)) {
|
|
5676
|
+
if (keepMark(ch, cp)) out += ch;
|
|
5677
|
+
continue;
|
|
5678
|
+
}
|
|
5679
|
+
if (cp > 65535 || cp >= 55296 && cp <= 57343) {
|
|
5680
|
+
out += "?";
|
|
5681
|
+
continue;
|
|
5682
|
+
}
|
|
5683
|
+
out += charWidth(cp) === 2 === BLESSED_WIDE.test(ch) ? ch : "?";
|
|
5684
|
+
}
|
|
5685
|
+
return out;
|
|
5686
|
+
}
|
|
5687
|
+
function blessedSgr(code) {
|
|
5688
|
+
if (!code.includes(":")) return code;
|
|
5689
|
+
const groups = code.slice(2, -1).split(";").flatMap((g) => {
|
|
5690
|
+
if (!g.includes(":")) return [g];
|
|
5691
|
+
const [kind, mode, ...rest] = g.split(":");
|
|
5692
|
+
if (kind === "4") return ["4"];
|
|
5693
|
+
if (!["38", "48", "58"].includes(kind)) return [];
|
|
5694
|
+
if (mode === "5" && rest[0]) return [`${kind};5;${rest[0]}`];
|
|
5695
|
+
if (mode === "2" && rest.length >= 3)
|
|
5696
|
+
return [`${kind};2;${rest.slice(-3).join(";")}`];
|
|
5697
|
+
return [];
|
|
5698
|
+
});
|
|
5699
|
+
return groups.length ? `\x1B[${groups.join(";")}m` : "";
|
|
5700
|
+
}
|
|
5701
|
+
var SGR_SPLIT = /(\x1b\[[0-9;:]*m)/;
|
|
5702
|
+
var visible = (t) => t.replace(SGR_RE, "");
|
|
5703
|
+
function screenText(input, opts = {}) {
|
|
5704
|
+
const parts = (opts.colors ? keepSgr(input) : stripAnsi(input)).replace(/\r\n/g, "\n").split("\r");
|
|
5705
|
+
let last = parts.length - 1;
|
|
5706
|
+
while (last > 0 && visible(parts[last]) === "") last--;
|
|
5707
|
+
const carried = parts.slice(0, last).flatMap((p) => p.match(SGR_RE) ?? []).join("");
|
|
5708
|
+
let out = "";
|
|
5709
|
+
let col = 0;
|
|
5710
|
+
for (const piece of (carried + parts[last]).split(SGR_SPLIT)) {
|
|
5711
|
+
if (SGR_SPLIT.test(piece) && piece.startsWith("\x1B")) {
|
|
5712
|
+
out += blessedSgr(piece);
|
|
5713
|
+
continue;
|
|
5714
|
+
}
|
|
5715
|
+
for (const { segment } of segmenter.segment(
|
|
5716
|
+
piece.replace(LINE_BREAK, " ").normalize("NFC")
|
|
5717
|
+
)) {
|
|
5718
|
+
const c = cluster(segment);
|
|
5719
|
+
if (c === " ") {
|
|
5720
|
+
const n = TAB - col % TAB;
|
|
5721
|
+
out += " ".repeat(n);
|
|
5722
|
+
col += n;
|
|
5723
|
+
continue;
|
|
5724
|
+
}
|
|
5725
|
+
out += c;
|
|
5726
|
+
col += columns(c);
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
return out;
|
|
5730
|
+
}
|
|
5731
|
+
function cutWidth(s, width) {
|
|
5732
|
+
if (displayWidth(s) <= width) return s;
|
|
5733
|
+
let out = "";
|
|
5734
|
+
let w = 0;
|
|
5735
|
+
for (const ch of s) {
|
|
5736
|
+
const cw = displayWidth(ch);
|
|
5737
|
+
if (w + cw > width - 1) break;
|
|
5738
|
+
out += ch;
|
|
5739
|
+
w += cw;
|
|
5740
|
+
}
|
|
5741
|
+
return `${out}\u2026`;
|
|
5742
|
+
}
|
|
5743
|
+
function cutStartWidth(s, width) {
|
|
5744
|
+
if (displayWidth(s) <= width) return s;
|
|
5745
|
+
const all = [...s];
|
|
5746
|
+
let out = "";
|
|
5747
|
+
for (let i = all.length - 1; i >= 0; i--) {
|
|
5748
|
+
if (displayWidth(all[i] + out) > width - 1) break;
|
|
5749
|
+
out = all[i] + out;
|
|
5750
|
+
}
|
|
5751
|
+
return `\u2026${out}`;
|
|
5752
|
+
}
|
|
5753
|
+
|
|
5754
|
+
// src/mcp/render.ts
|
|
5755
|
+
function fmtUptime(ms) {
|
|
5756
|
+
if (ms === null) return "-";
|
|
5757
|
+
const s = Math.floor(ms / 1e3);
|
|
5758
|
+
if (s < 60) return `${s}s`;
|
|
5759
|
+
const m = Math.floor(s / 60);
|
|
5760
|
+
if (m < 60) return `${m}m`;
|
|
5761
|
+
const h = Math.floor(m / 60);
|
|
5762
|
+
if (h < 48) return `${h}h${m % 60}m`;
|
|
5763
|
+
return `${Math.floor(h / 24)}d`;
|
|
5764
|
+
}
|
|
5765
|
+
function fmtBytes(b) {
|
|
5766
|
+
if (b < 1024) return `${b}B`;
|
|
5767
|
+
if (b < 1024 ** 2) return `${(b / 1024).toFixed(0)}K`;
|
|
5768
|
+
if (b < 1024 ** 3) return `${(b / 1024 ** 2).toFixed(1)}M`;
|
|
5769
|
+
return `${(b / 1024 ** 3).toFixed(2)}G`;
|
|
5770
|
+
}
|
|
5771
|
+
function fmtPorts(p) {
|
|
5772
|
+
const parts = [];
|
|
5773
|
+
for (const c of p.checks) {
|
|
5774
|
+
if (c.status === "listening") parts.push(`:${c.port} \u2713`);
|
|
5775
|
+
else if (c.held_by) {
|
|
5776
|
+
const who = c.held_by.app ? `app ${c.held_by.app}` : c.held_by.container ? `container ${c.held_by.container}` : c.held_by.pid !== null ? `pid ${c.held_by.pid}${c.held_by.command ? ` (${c.held_by.command})` : ""}` : "another user's process";
|
|
5777
|
+
parts.push(`:${c.port} \u2717 held by ${who}`);
|
|
5778
|
+
} else parts.push(`:${c.port} \u2717 nothing listens`);
|
|
5779
|
+
}
|
|
5471
5780
|
for (const u of p.undeclared) parts.push(`:${u} (undeclared)`);
|
|
5472
5781
|
return parts.length ? parts.join(", ") : "-";
|
|
5473
5782
|
}
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5783
|
+
var GUTTER = 2;
|
|
5784
|
+
var FLEX_MIN = 12;
|
|
5785
|
+
function fitWidths(natural, flex, width) {
|
|
5786
|
+
const w = [...natural];
|
|
5787
|
+
const floor = (i) => Math.min(FLEX_MIN, natural[i]);
|
|
5788
|
+
let total = w.reduce((a, b) => a + b, 0) + GUTTER * (w.length - 1);
|
|
5789
|
+
while (total > width) {
|
|
5790
|
+
let pick = -1;
|
|
5791
|
+
for (const i of flex)
|
|
5792
|
+
if (w[i] > floor(i) && (pick < 0 || w[i] > w[pick])) pick = i;
|
|
5793
|
+
if (pick < 0) break;
|
|
5794
|
+
w[pick]--;
|
|
5795
|
+
total--;
|
|
5796
|
+
}
|
|
5797
|
+
return w;
|
|
5798
|
+
}
|
|
5799
|
+
function takeWidth(s, width) {
|
|
5800
|
+
let out = "";
|
|
5801
|
+
for (const ch of s) {
|
|
5802
|
+
if (out && displayWidth(out + ch) > width) break;
|
|
5803
|
+
out += ch;
|
|
5804
|
+
}
|
|
5805
|
+
return out;
|
|
5806
|
+
}
|
|
5807
|
+
function wrapCell(s, width) {
|
|
5808
|
+
if (displayWidth(s) <= width) return [s];
|
|
5809
|
+
const lines = [];
|
|
5810
|
+
let line = "";
|
|
5811
|
+
for (let t of s.match(/[^\s/,:-]*[\s/,:-]*/g)?.filter(Boolean) ?? []) {
|
|
5812
|
+
if (displayWidth((line + t).trimEnd()) <= width) {
|
|
5813
|
+
line += t;
|
|
5814
|
+
continue;
|
|
5815
|
+
}
|
|
5816
|
+
if (line.trim()) lines.push(line.trimEnd());
|
|
5817
|
+
line = "";
|
|
5818
|
+
while (displayWidth(t.trimEnd()) > width) {
|
|
5819
|
+
const head = takeWidth(t, width);
|
|
5820
|
+
lines.push(head);
|
|
5821
|
+
t = t.slice(head.length);
|
|
5822
|
+
}
|
|
5823
|
+
line = t;
|
|
5824
|
+
}
|
|
5825
|
+
if (line.trim()) lines.push(line.trimEnd());
|
|
5826
|
+
return lines;
|
|
5827
|
+
}
|
|
5828
|
+
function table(rows, opts = {}) {
|
|
5829
|
+
if (opts.width === void 0) {
|
|
5830
|
+
const w = rows[0].map(
|
|
5831
|
+
(_, i) => Math.max(...rows.map((r) => (r[i] ?? "").length))
|
|
5832
|
+
);
|
|
5833
|
+
return rows.map(
|
|
5834
|
+
(r) => r.map((c, i) => (c ?? "").padEnd(w[i])).join(" ").trimEnd()
|
|
5835
|
+
).join("\n");
|
|
5836
|
+
}
|
|
5837
|
+
const at = (r, i) => r[i] ?? "";
|
|
5838
|
+
const natural = rows[0].map(
|
|
5839
|
+
(_, i) => Math.max(...rows.map((r) => displayWidth(at(r, i))))
|
|
5477
5840
|
);
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5841
|
+
const widths = fitWidths(natural, opts.flex ?? [], opts.width);
|
|
5842
|
+
const flex = new Set(opts.flex ?? []);
|
|
5843
|
+
const starts = new Set(opts.cutStart ?? []);
|
|
5844
|
+
const pad2 = (s, w) => s + " ".repeat(Math.max(0, w - displayWidth(s)));
|
|
5845
|
+
const out = [];
|
|
5846
|
+
for (const r of rows) {
|
|
5847
|
+
const cells = widths.map((w, i) => {
|
|
5848
|
+
const s = at(r, i);
|
|
5849
|
+
if (!flex.has(i) || displayWidth(s) <= w) return [s];
|
|
5850
|
+
if (opts.mode === "fit")
|
|
5851
|
+
return [starts.has(i) ? cutStartWidth(s, w) : cutWidth(s, w)];
|
|
5852
|
+
return wrapCell(s, w);
|
|
5853
|
+
});
|
|
5854
|
+
const height = Math.max(...cells.map((c) => c.length));
|
|
5855
|
+
for (let k = 0; k < height; k++)
|
|
5856
|
+
out.push(
|
|
5857
|
+
cells.map((c, i) => pad2(c[k] ?? "", widths[i])).join(" ".repeat(GUTTER)).trimEnd()
|
|
5858
|
+
);
|
|
5859
|
+
}
|
|
5860
|
+
return out.join("\n");
|
|
5481
5861
|
}
|
|
5482
5862
|
function fmtCpu(c) {
|
|
5483
5863
|
return c === null ? "-" : `${c.toFixed(1)}%`;
|
|
@@ -5505,7 +5885,7 @@ function fmtRestarts(r) {
|
|
|
5505
5885
|
const n = r.reloads;
|
|
5506
5886
|
return n && n.count > 0 ? `${r.restarts} (+${n.partial ? "\u2265" : ""}${n.count})` : String(r.restarts);
|
|
5507
5887
|
}
|
|
5508
|
-
function renderLs(rows, host, errors = []) {
|
|
5888
|
+
function renderLs(rows, host, errors = [], width) {
|
|
5509
5889
|
const hosts = [...new Set(rows.map((r) => r.host))];
|
|
5510
5890
|
const multi = hosts.length > 1;
|
|
5511
5891
|
const head = multi ? `hosts: ${hosts.join(", ")}` : `host: ${hosts[0] ?? host}`;
|
|
@@ -5514,40 +5894,45 @@ function renderLs(rows, host, errors = []) {
|
|
|
5514
5894
|
rows.filter((r) => r.error).map((r) => [r.host, `${r.host}: ${r.error}`])
|
|
5515
5895
|
).values()
|
|
5516
5896
|
];
|
|
5897
|
+
const o = multi ? 1 : 0;
|
|
5898
|
+
const cap = (s, n) => width === void 0 ? cut2(s, n) : s;
|
|
5517
5899
|
return [
|
|
5518
5900
|
head,
|
|
5519
|
-
table(
|
|
5901
|
+
table(
|
|
5520
5902
|
[
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5903
|
+
[
|
|
5904
|
+
"APP",
|
|
5905
|
+
...multi ? ["HOST"] : [],
|
|
5906
|
+
"KIND",
|
|
5907
|
+
"STATUS",
|
|
5908
|
+
"PID",
|
|
5909
|
+
"UPTIME",
|
|
5910
|
+
"RESTARTS",
|
|
5911
|
+
"MEM",
|
|
5912
|
+
"CPU",
|
|
5913
|
+
"PORTS",
|
|
5914
|
+
"URL",
|
|
5915
|
+
"RUNTIME",
|
|
5916
|
+
"DESCRIPTION"
|
|
5917
|
+
],
|
|
5918
|
+
...rows.map((r) => [
|
|
5919
|
+
r.app,
|
|
5920
|
+
...multi ? [r.host] : [],
|
|
5921
|
+
r.kind,
|
|
5922
|
+
r.status,
|
|
5923
|
+
r.pid === null ? "-" : String(r.pid),
|
|
5924
|
+
fmtUptime(r.uptime_ms),
|
|
5925
|
+
fmtRestarts(r),
|
|
5926
|
+
r.memory ? fmtBytes(r.memory) : "-",
|
|
5927
|
+
r.status === "online" ? fmtCpu(r.cpu) : "-",
|
|
5928
|
+
fmtPorts(r.ports),
|
|
5929
|
+
fmtUrl(r),
|
|
5930
|
+
r.runtime ? cap(r.runtime, 32) : "-",
|
|
5931
|
+
cap(r.description.split("\n")[0], 40)
|
|
5932
|
+
])
|
|
5534
5933
|
],
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
...multi ? [r.host] : [],
|
|
5538
|
-
r.kind,
|
|
5539
|
-
r.status,
|
|
5540
|
-
r.pid === null ? "-" : String(r.pid),
|
|
5541
|
-
fmtUptime(r.uptime_ms),
|
|
5542
|
-
fmtRestarts(r),
|
|
5543
|
-
r.memory ? fmtBytes(r.memory) : "-",
|
|
5544
|
-
r.status === "online" ? fmtCpu(r.cpu) : "-",
|
|
5545
|
-
fmtPorts(r.ports),
|
|
5546
|
-
fmtUrl(r),
|
|
5547
|
-
r.runtime ? cut2(r.runtime, 32) : "-",
|
|
5548
|
-
cut2(r.description.split("\n")[0], 40)
|
|
5549
|
-
])
|
|
5550
|
-
]),
|
|
5934
|
+
{ width, flex: [8 + o, 9 + o, 10 + o, 11 + o], mode: "wrap" }
|
|
5935
|
+
),
|
|
5551
5936
|
...unreachable,
|
|
5552
5937
|
...errors.map((e) => `${e.host}: ${e.message}`)
|
|
5553
5938
|
].join("\n");
|
|
@@ -5564,36 +5949,42 @@ function startNote(rows) {
|
|
|
5564
5949
|
);
|
|
5565
5950
|
return booting.length ? `note: ${booting.map((r) => r.app).join(", ")} not listening yet; ports are sampled right after start, call wait until listening instead of polling ls` : "";
|
|
5566
5951
|
}
|
|
5567
|
-
function renderPorts(rep, host) {
|
|
5952
|
+
function renderPorts(rep, host, width) {
|
|
5568
5953
|
if (rep.listeners.length === 0)
|
|
5569
5954
|
return `host: ${host}
|
|
5570
5955
|
${rep.port ? `nothing listens on port ${rep.port}` : "no listening sockets visible"}`;
|
|
5571
5956
|
return `host: ${host}
|
|
5572
|
-
` + table(
|
|
5573
|
-
[
|
|
5574
|
-
|
|
5575
|
-
l
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5957
|
+
` + table(
|
|
5958
|
+
[
|
|
5959
|
+
["PROTO", "ADDR", "PORT", "PID", "COMMAND", "APP", "CONTAINER"],
|
|
5960
|
+
...rep.listeners.map((l) => [
|
|
5961
|
+
l.proto,
|
|
5962
|
+
l.addr,
|
|
5963
|
+
String(l.port),
|
|
5964
|
+
l.pid === null ? "?" : String(l.pid),
|
|
5965
|
+
l.command ?? "?",
|
|
5966
|
+
l.app ?? "-",
|
|
5967
|
+
l.container ?? "-"
|
|
5968
|
+
])
|
|
5969
|
+
],
|
|
5970
|
+
{ width, flex: [4], mode: "wrap" }
|
|
5971
|
+
);
|
|
5584
5972
|
}
|
|
5585
|
-
function renderHosts(info) {
|
|
5586
|
-
return table(
|
|
5587
|
-
[
|
|
5588
|
-
|
|
5589
|
-
h
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5973
|
+
function renderHosts(info, width) {
|
|
5974
|
+
return table(
|
|
5975
|
+
[
|
|
5976
|
+
["HOST", "TRANSPORT", "CWD", "READONLY", "PM2_HOME", "APPS"],
|
|
5977
|
+
...info.map((h) => [
|
|
5978
|
+
h.name,
|
|
5979
|
+
h.transport,
|
|
5980
|
+
h.cwd,
|
|
5981
|
+
h.readonly ? "yes" : "no",
|
|
5982
|
+
h.pm2_home ?? "-",
|
|
5983
|
+
h.apps.length ? h.apps.join(", ") : "-"
|
|
5984
|
+
])
|
|
5985
|
+
],
|
|
5986
|
+
{ width, flex: [1, 2, 4, 5], mode: "wrap" }
|
|
5987
|
+
);
|
|
5597
5988
|
}
|
|
5598
5989
|
function fmtEnv(e) {
|
|
5599
5990
|
const kind = e.container === "none" ? "no container" : `container (${e.container})`;
|
|
@@ -5741,7 +6132,7 @@ function fmtDeps(deps2) {
|
|
|
5741
6132
|
(d) => d.condition === "started" ? d.app : `${d.app} (${d.condition})`
|
|
5742
6133
|
).join(", ");
|
|
5743
6134
|
}
|
|
5744
|
-
function renderProject(d) {
|
|
6135
|
+
function renderProject(d, width) {
|
|
5745
6136
|
const L = [`project: ${d.project} (${d.root})`];
|
|
5746
6137
|
const w = d.mcproc.manifest;
|
|
5747
6138
|
if (w)
|
|
@@ -5765,7 +6156,7 @@ function renderProject(d) {
|
|
|
5765
6156
|
L.push(` ${p}: ${apps.join(", ")}`);
|
|
5766
6157
|
L.push(
|
|
5767
6158
|
"hosts:",
|
|
5768
|
-
renderHosts(d.hosts).split("\n").map((x) => ` ${x}`).join("\n")
|
|
6159
|
+
renderHosts(d.hosts, width === void 0 ? void 0 : width - 2).split("\n").map((x) => ` ${x}`).join("\n")
|
|
5769
6160
|
);
|
|
5770
6161
|
if (d.readme) L.push("", "README:", d.readme.trim());
|
|
5771
6162
|
return L.join("\n");
|
|
@@ -5788,9 +6179,9 @@ function incidentLines(i, opts = {}) {
|
|
|
5788
6179
|
function renderIncidents(a, opts = {}) {
|
|
5789
6180
|
const tail = [];
|
|
5790
6181
|
if (a.more > 0) {
|
|
5791
|
-
const
|
|
6182
|
+
const short2 = a.more_by === "size" ? " to keep this answer short" : "";
|
|
5792
6183
|
tail.push(
|
|
5793
|
-
a.taken ? `+${a.more} older incidents not shown${
|
|
6184
|
+
a.taken ? `+${a.more} older incidents not shown${short2}, taken with these (read logs for them)` : short2 ? `+${a.more} more incidents not shown${short2} (pass app for one app, or read logs)` : `+${a.more} more incidents (raise max)`
|
|
5794
6185
|
);
|
|
5795
6186
|
}
|
|
5796
6187
|
for (const s of a.skipped)
|
|
@@ -5839,7 +6230,7 @@ function queuedNote(rows) {
|
|
|
5839
6230
|
return l.state === "starting" ? `${apps}: starting (lease ${l.id})` : `${apps}: queued #${l.position}${l.waiting_for ? `: ${l.waiting_for}` : ""} (lease ${l.id}; wait on an app, or leases)`;
|
|
5840
6231
|
}).join("\n");
|
|
5841
6232
|
}
|
|
5842
|
-
function renderLeases(v) {
|
|
6233
|
+
function renderLeases(v, width) {
|
|
5843
6234
|
const head = [
|
|
5844
6235
|
`state: ${v.state_dir}`,
|
|
5845
6236
|
v.memory ? `memory: ${fmtMib(v.memory.available_mib)} available of ${fmtMib(v.memory.total_mib)}` : "memory: no reading on this host (leases are admitted in FIFO order without a check)",
|
|
@@ -5859,23 +6250,26 @@ function renderLeases(v) {
|
|
|
5859
6250
|
]);
|
|
5860
6251
|
return [
|
|
5861
6252
|
...head,
|
|
5862
|
-
table(
|
|
6253
|
+
table(
|
|
5863
6254
|
[
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
6255
|
+
[
|
|
6256
|
+
"POS",
|
|
6257
|
+
"STATE",
|
|
6258
|
+
"KIND",
|
|
6259
|
+
"PROJECT",
|
|
6260
|
+
"TARGET",
|
|
6261
|
+
"EST \u2192 NEED",
|
|
6262
|
+
"FROM",
|
|
6263
|
+
"WAITING FOR / ERROR",
|
|
6264
|
+
"ID"
|
|
6265
|
+
],
|
|
6266
|
+
...rows
|
|
5873
6267
|
],
|
|
5874
|
-
|
|
5875
|
-
|
|
6268
|
+
{ width, flex: [4, 7], mode: "wrap" }
|
|
6269
|
+
)
|
|
5876
6270
|
].join("\n");
|
|
5877
6271
|
}
|
|
5878
|
-
function renderMutation(rows, fallback, act, waits = []) {
|
|
6272
|
+
function renderMutation(rows, fallback, act, waits = [], width) {
|
|
5879
6273
|
const bad3 = rows.filter((r) => r.error);
|
|
5880
6274
|
const notes = [
|
|
5881
6275
|
act === "start" || act === "restart" ? startNote(rows) : "",
|
|
@@ -5886,7 +6280,7 @@ function renderMutation(rows, fallback, act, waits = []) {
|
|
|
5886
6280
|
const head = bad3.length ? `${act} partly failed: ${bad3.map((r) => `${r.app} (${r.host}): ${r.error}`).join("; ")}
|
|
5887
6281
|
` : "";
|
|
5888
6282
|
return {
|
|
5889
|
-
text: head + renderLs(rows, fallback) + notes.map((n) => `
|
|
6283
|
+
text: head + renderLs(rows, fallback, [], width) + notes.map((n) => `
|
|
5890
6284
|
${n}`).join(""),
|
|
5891
6285
|
failed: bad3.length > 0
|
|
5892
6286
|
};
|
|
@@ -5940,22 +6334,123 @@ function renderWorktreeAdded(r) {
|
|
|
5940
6334
|
L.push(`next: ${r.next}`);
|
|
5941
6335
|
return L.join("\n");
|
|
5942
6336
|
}
|
|
5943
|
-
function renderWorktrees(rows) {
|
|
5944
|
-
return table(
|
|
5945
|
-
[
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
6337
|
+
function renderWorktrees(rows, width) {
|
|
6338
|
+
return table(
|
|
6339
|
+
[
|
|
6340
|
+
["SLOT", "PROJECT", "BRANCH", "ONLINE", "LEASE", "PATH"],
|
|
6341
|
+
...rows.map((r) => [
|
|
6342
|
+
r.main ? `${r.slot} (main)` : String(r.slot),
|
|
6343
|
+
r.project,
|
|
6344
|
+
r.branch ?? "(detached)",
|
|
6345
|
+
String(r.online),
|
|
6346
|
+
r.lease ?? "-",
|
|
6347
|
+
r.path
|
|
6348
|
+
])
|
|
6349
|
+
],
|
|
6350
|
+
{ width, flex: [2, 5], mode: "wrap" }
|
|
6351
|
+
);
|
|
5955
6352
|
}
|
|
5956
6353
|
function renderWorktreeRemoved(r) {
|
|
5957
6354
|
return `removed ${r.path}: slot ${r.slot} is free, branch ${r.branch ?? "(detached)"} kept; stopped ${r.stopped.length ? r.stopped.join(", ") : "nothing"}`;
|
|
5958
6355
|
}
|
|
6356
|
+
function tildePath(p, home = os7.homedir()) {
|
|
6357
|
+
return p === home || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
|
|
6358
|
+
}
|
|
6359
|
+
function stackLabel(s, nested) {
|
|
6360
|
+
if (s.base_project === s.project) return s.project;
|
|
6361
|
+
const wt = `${s.slot !== null ? `w${s.slot} ` : ""}${s.branch ?? s.project}`;
|
|
6362
|
+
return nested ? ` ${wt}` : `${s.base_project} ${wt}`;
|
|
6363
|
+
}
|
|
6364
|
+
function stackApps(s) {
|
|
6365
|
+
if (s.queue_position !== null && s.apps.online === 0)
|
|
6366
|
+
return `queued #${s.queue_position}`;
|
|
6367
|
+
return s.present ? `${s.apps.online}/${s.apps.total}` : "not running";
|
|
6368
|
+
}
|
|
6369
|
+
function stackPorts(s, max = 3) {
|
|
6370
|
+
const ports = [...new Set(s.ports.map((p) => p.port))].sort((a, b) => a - b);
|
|
6371
|
+
if (ports.length === 0) return "-";
|
|
6372
|
+
const shown = ports.slice(0, max).map((p) => `:${p}`).join(" ");
|
|
6373
|
+
return ports.length > max ? `${shown} +${ports.length - max}` : shown;
|
|
6374
|
+
}
|
|
6375
|
+
var stackCells = (s, nested, root) => [
|
|
6376
|
+
stackLabel(s, nested),
|
|
6377
|
+
root(s.root),
|
|
6378
|
+
s.hosts.map((h) => h.name).join(","),
|
|
6379
|
+
s.manifest_error ? "manifest error" : stackApps(s),
|
|
6380
|
+
String(s.apps.errored),
|
|
6381
|
+
s.memory ? fmtBytes(s.memory) : "-",
|
|
6382
|
+
s.cpu === null ? "-" : `${Math.round(s.cpu)}%`,
|
|
6383
|
+
stackPorts(s)
|
|
6384
|
+
];
|
|
6385
|
+
var orphanCells = (o) => {
|
|
6386
|
+
const mem = o.processes.reduce((a, p) => a + p.memory, 0);
|
|
6387
|
+
return [
|
|
6388
|
+
`pm2 \xB7 ${o.namespace}`,
|
|
6389
|
+
"(no manifest)",
|
|
6390
|
+
o.endpoint,
|
|
6391
|
+
`${o.processes.filter((p) => p.status === "online").length}/${o.processes.length}`,
|
|
6392
|
+
"-",
|
|
6393
|
+
mem ? fmtBytes(mem) : "-",
|
|
6394
|
+
"-",
|
|
6395
|
+
"-"
|
|
6396
|
+
];
|
|
6397
|
+
};
|
|
6398
|
+
var STACK_HEADER = [
|
|
6399
|
+
"STACK",
|
|
6400
|
+
"ROOT",
|
|
6401
|
+
"HOSTS",
|
|
6402
|
+
"APPS",
|
|
6403
|
+
"ERR",
|
|
6404
|
+
"MEM",
|
|
6405
|
+
"CPU",
|
|
6406
|
+
"PORTS"
|
|
6407
|
+
];
|
|
6408
|
+
function stacksHostLine(h) {
|
|
6409
|
+
const mem = h.memory ? `RAM ${fmtMib(h.memory.available_mib)} free of ${fmtMib(h.memory.total_mib)}` : "no memory reading";
|
|
6410
|
+
const watcher = h.watcher ? h.watcher.online ? "watcher online" : "watcher stopped" : "no watcher";
|
|
6411
|
+
return `${h.name} \xB7 ${mem} \xB7 ${watcher} \xB7 queue ${h.queue}`;
|
|
6412
|
+
}
|
|
6413
|
+
function renderStacks(a, width, home = os7.homedir()) {
|
|
6414
|
+
const lines = [`host: ${stacksHostLine(a.host)}`, ...a.host.notes];
|
|
6415
|
+
if (a.stacks.length === 0 && a.orphans.length === 0)
|
|
6416
|
+
return [
|
|
6417
|
+
...lines,
|
|
6418
|
+
"no stacks running on this host (--all lists the recorded ones)"
|
|
6419
|
+
].join("\n");
|
|
6420
|
+
const bases = new Set(a.stacks.map((s) => s.project));
|
|
6421
|
+
const root = (p) => width === void 0 ? p : tildePath(p, home);
|
|
6422
|
+
lines.push(
|
|
6423
|
+
table(
|
|
6424
|
+
[
|
|
6425
|
+
STACK_HEADER,
|
|
6426
|
+
...a.stacks.map(
|
|
6427
|
+
(s) => stackCells(
|
|
6428
|
+
s,
|
|
6429
|
+
s.base_project !== s.project && bases.has(s.base_project),
|
|
6430
|
+
root
|
|
6431
|
+
)
|
|
6432
|
+
),
|
|
6433
|
+
...a.orphans.map(orphanCells)
|
|
6434
|
+
],
|
|
6435
|
+
{ width, flex: [1, 7], mode: "wrap" }
|
|
6436
|
+
)
|
|
6437
|
+
);
|
|
6438
|
+
for (const s of a.stacks) {
|
|
6439
|
+
if (s.manifest_error)
|
|
6440
|
+
lines.push(
|
|
6441
|
+
`${s.project}: ${s.manifest_error.code}: ${s.manifest_error.message}${s.manifest_error.hint ? ` (${s.manifest_error.hint})` : ""}`
|
|
6442
|
+
);
|
|
6443
|
+
for (const n of s.notes) lines.push(`${s.project}: ${n}`);
|
|
6444
|
+
for (const h of s.hosts)
|
|
6445
|
+
if (h.error)
|
|
6446
|
+
lines.push(
|
|
6447
|
+
`${s.project} on ${h.name}: ${h.error}${h.age_s ? ` (last read ${h.age_s}s ago)` : ""}`
|
|
6448
|
+
);
|
|
6449
|
+
}
|
|
6450
|
+
for (const o of a.orphans)
|
|
6451
|
+
if (o.note) lines.push(`pm2 \xB7 ${o.namespace}: ${o.note}`);
|
|
6452
|
+
return lines.join("\n");
|
|
6453
|
+
}
|
|
5959
6454
|
|
|
5960
6455
|
// src/core/wait.ts
|
|
5961
6456
|
var WAIT_TIMEOUT_DEFAULT_MS = 6e4;
|
|
@@ -6331,7 +6826,7 @@ function assertNoCollision(ctx, procs, names) {
|
|
|
6331
6826
|
const name2 = pm2Name(ctx.manifest.project, n);
|
|
6332
6827
|
const expectedCwd = appCwd(ctx.host, ctx.manifest.apps[n]);
|
|
6333
6828
|
const other = procs.find(
|
|
6334
|
-
(p) => p.name === name2 && (p.namespace !== ctx.manifest.project || p.cwd !== "" &&
|
|
6829
|
+
(p) => p.name === name2 && (p.namespace !== ctx.manifest.project || p.cwd !== "" && path16.posix.resolve(p.cwd) !== expectedCwd)
|
|
6335
6830
|
);
|
|
6336
6831
|
if (other && other.namespace !== ctx.manifest.project) {
|
|
6337
6832
|
throw new McprocError(
|
|
@@ -6358,7 +6853,7 @@ async function shipEcosystem(ctx) {
|
|
|
6358
6853
|
const eco = buildEcosystem(ctx.manifest, ctx.host);
|
|
6359
6854
|
const file = ecosystemPath(ctx.host);
|
|
6360
6855
|
const dir = generatedDir(ctx.host);
|
|
6361
|
-
const gi =
|
|
6856
|
+
const gi = path16.posix.join(path16.posix.dirname(dir), ".gitignore");
|
|
6362
6857
|
const cmd = `mkdir -p ${shq(dir)} && { [ -e ${shq(gi)} ] || printf '*\\n' > ${shq(gi)}; } && cat > ${shq(file)}`;
|
|
6363
6858
|
const res = ensureOk(
|
|
6364
6859
|
await ctx.transport.exec(cmd, { stdin: JSON.stringify(eco, null, 2) }),
|
|
@@ -6535,93 +7030,7 @@ function admit(opts) {
|
|
|
6535
7030
|
}
|
|
6536
7031
|
|
|
6537
7032
|
// src/core/leases/siblings.ts
|
|
6538
|
-
import
|
|
6539
|
-
|
|
6540
|
-
// src/core/slots.ts
|
|
6541
|
-
import crypto2 from "crypto";
|
|
6542
|
-
import fs10 from "fs";
|
|
6543
|
-
import path15 from "path";
|
|
6544
|
-
function registryKey(baseProject, repo) {
|
|
6545
|
-
return `${baseProject}-${crypto2.createHash("sha256").update(realOf(repo)).digest("hex").slice(0, 8)}`;
|
|
6546
|
-
}
|
|
6547
|
-
function registryPath(stateDir2, baseProject, repo) {
|
|
6548
|
-
return path15.join(stateDir2, "slots", `${registryKey(baseProject, repo)}.json`);
|
|
6549
|
-
}
|
|
6550
|
-
function emptyRegistry(repo) {
|
|
6551
|
-
return { repo, slots: {} };
|
|
6552
|
-
}
|
|
6553
|
-
function readRegistry(file) {
|
|
6554
|
-
if (!fs10.existsSync(file)) return null;
|
|
6555
|
-
return JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
6556
|
-
}
|
|
6557
|
-
function writeRegistry(file, r) {
|
|
6558
|
-
fs10.mkdirSync(path15.dirname(file), { recursive: true });
|
|
6559
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
6560
|
-
fs10.writeFileSync(tmp, JSON.stringify(r, null, 2));
|
|
6561
|
-
fs10.renameSync(tmp, file);
|
|
6562
|
-
}
|
|
6563
|
-
function withMain(r, entry) {
|
|
6564
|
-
return { ...r, slots: { ...r.slots, "0": entry } };
|
|
6565
|
-
}
|
|
6566
|
-
function claimSlot(r, opts) {
|
|
6567
|
-
if (opts.want !== void 0) {
|
|
6568
|
-
if (opts.want < 1 || opts.want > opts.max)
|
|
6569
|
-
throw new McprocError(
|
|
6570
|
-
"BAD_ARGUMENT",
|
|
6571
|
-
`slot ${opts.want} is outside 1..${opts.max} (slot 0 is the main checkout)`
|
|
6572
|
-
);
|
|
6573
|
-
if (Object.hasOwn(r.slots, String(opts.want)))
|
|
6574
|
-
throw new McprocError(
|
|
6575
|
-
"SLOT_TAKEN",
|
|
6576
|
-
`slot ${opts.want} is taken by ${r.slots[String(opts.want)].path}`,
|
|
6577
|
-
{ hint: "Pick another slot, or remove that worktree first." }
|
|
6578
|
-
);
|
|
6579
|
-
return {
|
|
6580
|
-
registry: { ...r, slots: { ...r.slots, [opts.want]: opts.entry } },
|
|
6581
|
-
slot: opts.want
|
|
6582
|
-
};
|
|
6583
|
-
}
|
|
6584
|
-
for (let s = 1; s <= opts.max; s++)
|
|
6585
|
-
if (!Object.hasOwn(r.slots, String(s)))
|
|
6586
|
-
return {
|
|
6587
|
-
registry: { ...r, slots: { ...r.slots, [s]: opts.entry } },
|
|
6588
|
-
slot: s
|
|
6589
|
-
};
|
|
6590
|
-
throw new McprocError(
|
|
6591
|
-
"NO_FREE_SLOT",
|
|
6592
|
-
`all ${opts.max} worktree slots of ${r.repo} are taken`,
|
|
6593
|
-
{
|
|
6594
|
-
hint: "Remove a worktree (worktree remove), or raise worktree.slots in the manifest."
|
|
6595
|
-
}
|
|
6596
|
-
);
|
|
6597
|
-
}
|
|
6598
|
-
function releaseSlot(r, slot) {
|
|
6599
|
-
const slots = { ...r.slots };
|
|
6600
|
-
delete slots[String(slot)];
|
|
6601
|
-
return { ...r, slots };
|
|
6602
|
-
}
|
|
6603
|
-
function realOf(p) {
|
|
6604
|
-
try {
|
|
6605
|
-
return fs10.realpathSync(p);
|
|
6606
|
-
} catch {
|
|
6607
|
-
return path15.resolve(p);
|
|
6608
|
-
}
|
|
6609
|
-
}
|
|
6610
|
-
function reclaimGone(r, exists) {
|
|
6611
|
-
const freed = [];
|
|
6612
|
-
let out = r;
|
|
6613
|
-
for (const [k, e] of Object.entries(r.slots)) {
|
|
6614
|
-
if (k === "0" || exists(realOf(e.path))) continue;
|
|
6615
|
-
out = releaseSlot(out, Number(k));
|
|
6616
|
-
freed.push(Number(k));
|
|
6617
|
-
}
|
|
6618
|
-
return { registry: out, freed: freed.sort((a, b) => a - b) };
|
|
6619
|
-
}
|
|
6620
|
-
function siblingsOf(r, root) {
|
|
6621
|
-
return Object.entries(r.slots).filter(([, e]) => realOf(e.path) !== realOf(root)).map(([k, entry]) => ({ slot: Number(k), entry })).sort((a, b) => a.slot - b.slot);
|
|
6622
|
-
}
|
|
6623
|
-
|
|
6624
|
-
// src/core/leases/siblings.ts
|
|
7033
|
+
import fs12 from "fs";
|
|
6625
7034
|
var MIB = 1024 * 1024;
|
|
6626
7035
|
var repoOf = (m) => m.repo;
|
|
6627
7036
|
function registryOf(m, dir = stateDir()) {
|
|
@@ -6631,7 +7040,7 @@ function ownsManifest(root) {
|
|
|
6631
7040
|
const found = findManifestPath(root);
|
|
6632
7041
|
if (!found) return false;
|
|
6633
7042
|
try {
|
|
6634
|
-
return
|
|
7043
|
+
return fs12.realpathSync(found.root) === fs12.realpathSync(root);
|
|
6635
7044
|
} catch {
|
|
6636
7045
|
return false;
|
|
6637
7046
|
}
|
|
@@ -6649,7 +7058,7 @@ async function listCheckout(root, spawner) {
|
|
|
6649
7058
|
async function measureSiblings(m, deps2) {
|
|
6650
7059
|
const out = /* @__PURE__ */ new Map();
|
|
6651
7060
|
if (!deps2.registry) return out;
|
|
6652
|
-
const exists = deps2.exists ?? ((p) =>
|
|
7061
|
+
const exists = deps2.exists ?? ((p) => fs12.existsSync(p));
|
|
6653
7062
|
const read = await Promise.all(
|
|
6654
7063
|
siblingsOf(deps2.registry, m.root).filter(({ entry }) => exists(entry.path)).map(async ({ slot, entry }) => {
|
|
6655
7064
|
try {
|
|
@@ -7040,7 +7449,15 @@ function failedRows(fleet, r, e) {
|
|
|
7040
7449
|
);
|
|
7041
7450
|
}
|
|
7042
7451
|
async function actOn(fleet, target, act, opts = {}) {
|
|
7043
|
-
|
|
7452
|
+
if (act === "start" && Array.isArray(target))
|
|
7453
|
+
throw new McprocError(
|
|
7454
|
+
"BAD_ARGUMENT",
|
|
7455
|
+
`start takes one app or profile, not a list (${target.join(", ")})`,
|
|
7456
|
+
{
|
|
7457
|
+
hint: "Start one app or profile; a list of targets is for stop, restart and delete."
|
|
7458
|
+
}
|
|
7459
|
+
);
|
|
7460
|
+
const named = fleet.select(Array.isArray(target) ? target : [target]);
|
|
7044
7461
|
const names = act === "start" ? fleet.withDeps(named) : named;
|
|
7045
7462
|
const runs = fleet.runs(names);
|
|
7046
7463
|
assertWritableRuns(fleet, runs, named);
|
|
@@ -7082,6 +7499,7 @@ async function actOn(fleet, target, act, opts = {}) {
|
|
|
7082
7499
|
r.apps,
|
|
7083
7500
|
{
|
|
7084
7501
|
target,
|
|
7502
|
+
// a start of a list is refused above
|
|
7085
7503
|
host: fleet.requested ?? null,
|
|
7086
7504
|
reload: opts.reload,
|
|
7087
7505
|
note: opts.note,
|
|
@@ -7148,62 +7566,586 @@ async function actOn(fleet, target, act, opts = {}) {
|
|
|
7148
7566
|
rows.push(...failedRows(fleet, r, e));
|
|
7149
7567
|
}
|
|
7150
7568
|
}
|
|
7151
|
-
if (failed && done === 0) throw failed.error;
|
|
7152
|
-
const local = rows.filter((r) => pickHost(fleet.manifest, r.host).local);
|
|
7153
|
-
if (act === "start" && opts.lease && local.length > 0)
|
|
7154
|
-
await finishLeasedStart(gate.store, opts.lease, local);
|
|
7155
|
-
return rows;
|
|
7156
|
-
}
|
|
7157
|
-
function probeFleet(fleet, names, take) {
|
|
7158
|
-
return Promise.all(
|
|
7159
|
-
fleet.groups(names).map(async (g) => {
|
|
7160
|
-
try {
|
|
7161
|
-
const ctx = await fleet.ctx(g.host);
|
|
7162
|
-
return { ...g, ctx, snap: await take(ctx) };
|
|
7163
|
-
} catch (e) {
|
|
7164
|
-
return { ...g, error: e };
|
|
7569
|
+
if (failed && done === 0) throw failed.error;
|
|
7570
|
+
const local = rows.filter((r) => pickHost(fleet.manifest, r.host).local);
|
|
7571
|
+
if (act === "start" && opts.lease && local.length > 0)
|
|
7572
|
+
await finishLeasedStart(gate.store, opts.lease, local);
|
|
7573
|
+
return rows;
|
|
7574
|
+
}
|
|
7575
|
+
function probeFleet(fleet, names, take) {
|
|
7576
|
+
return Promise.all(
|
|
7577
|
+
fleet.groups(names).map(async (g) => {
|
|
7578
|
+
try {
|
|
7579
|
+
const ctx = await fleet.ctx(g.host);
|
|
7580
|
+
return { ...g, ctx, snap: await take(ctx) };
|
|
7581
|
+
} catch (e) {
|
|
7582
|
+
return { ...g, error: e };
|
|
7583
|
+
}
|
|
7584
|
+
})
|
|
7585
|
+
);
|
|
7586
|
+
}
|
|
7587
|
+
async function listFleet(fleet, names, opts = {}) {
|
|
7588
|
+
const probes = await probeFleet(
|
|
7589
|
+
fleet,
|
|
7590
|
+
names,
|
|
7591
|
+
(ctx) => snapshot(ctx, { urls: names, ...opts })
|
|
7592
|
+
);
|
|
7593
|
+
const failed = probes.filter((p) => p.error !== void 0);
|
|
7594
|
+
if (failed.length === probes.length && failed.length > 0)
|
|
7595
|
+
throw failed[0].error;
|
|
7596
|
+
const byApp = /* @__PURE__ */ new Map();
|
|
7597
|
+
for (const p of probes) {
|
|
7598
|
+
const rows = p.snap ? rowsFor(p.ctx, p.snap, p.apps) : p.apps.map(
|
|
7599
|
+
(a) => unreachableRow(fleet.manifest.apps[a], p.host, describeError(p.error))
|
|
7600
|
+
);
|
|
7601
|
+
for (const r of rows) byApp.set(r.app, r);
|
|
7602
|
+
}
|
|
7603
|
+
return {
|
|
7604
|
+
rows: names.map((n) => byApp.get(n)),
|
|
7605
|
+
errors: failed.map((p) => ({
|
|
7606
|
+
host: p.host,
|
|
7607
|
+
apps: p.apps,
|
|
7608
|
+
message: describeError(p.error)
|
|
7609
|
+
})),
|
|
7610
|
+
probes
|
|
7611
|
+
};
|
|
7612
|
+
}
|
|
7613
|
+
async function describeFleetApp(fleet, app) {
|
|
7614
|
+
const host = fleet.one(app);
|
|
7615
|
+
const d = await describeApp(await fleet.ctx(host), app);
|
|
7616
|
+
const full = fleet.manifest.apps[app];
|
|
7617
|
+
return {
|
|
7618
|
+
...d,
|
|
7619
|
+
app: { ...full, depends_on: full.depends_on.map(depView) },
|
|
7620
|
+
host
|
|
7621
|
+
};
|
|
7622
|
+
}
|
|
7623
|
+
|
|
7624
|
+
// src/core/stacks/discover.ts
|
|
7625
|
+
function endpointKey(host) {
|
|
7626
|
+
const args = host.ssh && host.ssh_args.length ? ` [${host.ssh_args.join(" ")}]` : "";
|
|
7627
|
+
return `${describeHost(host)}${args}|${host.pm2_home ?? ""}`;
|
|
7628
|
+
}
|
|
7629
|
+
function endpointLabel(host) {
|
|
7630
|
+
if (!host.local) return host.name;
|
|
7631
|
+
return host.pm2_home ? `local (${host.pm2_home})` : "local";
|
|
7632
|
+
}
|
|
7633
|
+
var claimKey = (endpoint, namespace) => `${endpoint}\0${namespace}`;
|
|
7634
|
+
var under = (p, root) => p === root || p.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
7635
|
+
var bare = (dir) => dir.replace(/(.)\/+$/, "$1");
|
|
7636
|
+
function cwdsOn(p, key) {
|
|
7637
|
+
const m = p.manifest;
|
|
7638
|
+
if (!m) return [bare(p.root)];
|
|
7639
|
+
return Object.entries(p.endpoints).filter(([name2, k]) => k === key && Object.hasOwn(m.hosts, name2)).map(([name2]) => bare(m.hosts[name2].cwd));
|
|
7640
|
+
}
|
|
7641
|
+
function ownProcs(p, key, procs, claimants) {
|
|
7642
|
+
const mine = procs.filter((pr) => pr.namespace === p.project);
|
|
7643
|
+
if (claimants.length < 2) return mine;
|
|
7644
|
+
const dirs = claimants.flatMap(
|
|
7645
|
+
(c) => cwdsOn(c, key).map((dir) => ({ c, dir }))
|
|
7646
|
+
);
|
|
7647
|
+
return mine.filter((pr) => {
|
|
7648
|
+
let best = -1;
|
|
7649
|
+
let owners = /* @__PURE__ */ new Set();
|
|
7650
|
+
for (const d of dirs) {
|
|
7651
|
+
if (!under(pr.cwd, d.dir) || d.dir.length < best) continue;
|
|
7652
|
+
if (d.dir.length > best) {
|
|
7653
|
+
best = d.dir.length;
|
|
7654
|
+
owners = /* @__PURE__ */ new Set();
|
|
7655
|
+
}
|
|
7656
|
+
owners.add(d.c);
|
|
7657
|
+
}
|
|
7658
|
+
return owners.size === 1 && owners.has(p);
|
|
7659
|
+
});
|
|
7660
|
+
}
|
|
7661
|
+
function stackError(e) {
|
|
7662
|
+
if (!isMcprocError(e)) return { code: "ERROR", message: describeError(e) };
|
|
7663
|
+
return e.hint ? { code: e.code, message: e.message, hint: e.hint } : { code: e.code, message: e.message };
|
|
7664
|
+
}
|
|
7665
|
+
function procMemory(p, probe) {
|
|
7666
|
+
if (p.pid <= 0) return p.memory;
|
|
7667
|
+
const set = descendants2(probe.data.tree, p.pid);
|
|
7668
|
+
set.add(p.pid);
|
|
7669
|
+
return treeMetrics(set, probe.data.stats, probe.data.listeners)?.memory ?? p.memory;
|
|
7670
|
+
}
|
|
7671
|
+
var ERRORED = (s) => s === "errored" || s.startsWith("failed");
|
|
7672
|
+
var STOPPED = (s) => s === "stopped" || s === "done";
|
|
7673
|
+
function counts(statuses, queued) {
|
|
7674
|
+
return {
|
|
7675
|
+
total: statuses.length,
|
|
7676
|
+
online: statuses.filter((s) => s === "online").length,
|
|
7677
|
+
errored: statuses.filter(ERRORED).length,
|
|
7678
|
+
stopped: statuses.filter(STOPPED).length,
|
|
7679
|
+
queued
|
|
7680
|
+
};
|
|
7681
|
+
}
|
|
7682
|
+
function summary(rows) {
|
|
7683
|
+
const online = rows.filter((r) => r.status === "online");
|
|
7684
|
+
const queued = rows.flatMap(
|
|
7685
|
+
(r) => r.lease?.state === "pending" ? [r.lease.position] : []
|
|
7686
|
+
);
|
|
7687
|
+
const cpus = online.flatMap((r) => r.cpu === null ? [] : [r.cpu]);
|
|
7688
|
+
return {
|
|
7689
|
+
apps: counts(
|
|
7690
|
+
rows.map((r) => r.status),
|
|
7691
|
+
queued.length
|
|
7692
|
+
),
|
|
7693
|
+
queue_position: queued.length ? Math.min(...queued) : null,
|
|
7694
|
+
memory: online.reduce((a, r) => a + r.memory, 0),
|
|
7695
|
+
cpu: cpus.length ? cpus.reduce((a, b) => a + b, 0) : null,
|
|
7696
|
+
ports: online.flatMap(
|
|
7697
|
+
(r) => [...new Set(r.ports.actual.map((l) => l.port))].sort((a, b) => a - b).map((port) => ({ app: r.app, port }))
|
|
7698
|
+
),
|
|
7699
|
+
present: rows.some((r) => r.pm_id !== null || r.lease !== void 0)
|
|
7700
|
+
};
|
|
7701
|
+
}
|
|
7702
|
+
async function buildStacks(projects, reads, opts) {
|
|
7703
|
+
const claims = /* @__PURE__ */ new Map();
|
|
7704
|
+
for (const p of projects)
|
|
7705
|
+
for (const key of new Set(Object.values(p.endpoints))) {
|
|
7706
|
+
const k = claimKey(key, p.project);
|
|
7707
|
+
claims.set(k, [...claims.get(k) ?? [], p]);
|
|
7708
|
+
}
|
|
7709
|
+
const stacks = [];
|
|
7710
|
+
for (const p of projects) stacks.push(await stackOf(p, reads, claims, opts));
|
|
7711
|
+
const orphans = [];
|
|
7712
|
+
for (const r of reads.values()) {
|
|
7713
|
+
const probe = r.probe;
|
|
7714
|
+
if (!probe) continue;
|
|
7715
|
+
const byNs = /* @__PURE__ */ new Map();
|
|
7716
|
+
for (const pr of probe.procs)
|
|
7717
|
+
byNs.set(pr.namespace, [...byNs.get(pr.namespace) ?? [], pr]);
|
|
7718
|
+
for (const [ns, procs] of byNs) {
|
|
7719
|
+
if (ns === WATCHER_NS || claims.has(claimKey(r.key, ns))) continue;
|
|
7720
|
+
orphans.push({
|
|
7721
|
+
namespace: ns,
|
|
7722
|
+
endpoint: r.label,
|
|
7723
|
+
processes: procs.map((pr) => ({
|
|
7724
|
+
name: pr.name,
|
|
7725
|
+
status: pr.status,
|
|
7726
|
+
memory: procMemory(pr, probe)
|
|
7727
|
+
})),
|
|
7728
|
+
note: opts.orphanNotes?.get(claimKey(r.key, ns)) ?? null
|
|
7729
|
+
});
|
|
7730
|
+
}
|
|
7731
|
+
}
|
|
7732
|
+
orphans.sort(
|
|
7733
|
+
(a, b) => a.endpoint.localeCompare(b.endpoint) || a.namespace.localeCompare(b.namespace)
|
|
7734
|
+
);
|
|
7735
|
+
return { stacks, orphans };
|
|
7736
|
+
}
|
|
7737
|
+
async function stackOf(p, reads, claims, opts) {
|
|
7738
|
+
const hosts = Object.entries(p.endpoints).map(([name2, key]) => {
|
|
7739
|
+
const r = reads.get(key);
|
|
7740
|
+
return {
|
|
7741
|
+
name: name2,
|
|
7742
|
+
endpoint: r?.label ?? key,
|
|
7743
|
+
reachable: Boolean(r?.probe) && !r?.error,
|
|
7744
|
+
error: r?.error ?? null,
|
|
7745
|
+
age_s: r?.age_s ?? 0
|
|
7746
|
+
};
|
|
7747
|
+
});
|
|
7748
|
+
const claimantsOn = (key) => claims.get(claimKey(key, p.project)) ?? [];
|
|
7749
|
+
const shared = Math.max(
|
|
7750
|
+
0,
|
|
7751
|
+
...Object.values(p.endpoints).map((k) => claimantsOn(k).length)
|
|
7752
|
+
);
|
|
7753
|
+
const notes = shared > 1 ? [`slug shared by ${shared} checkouts`] : [];
|
|
7754
|
+
const rivals = [
|
|
7755
|
+
...new Set(
|
|
7756
|
+
Object.values(p.endpoints).flatMap((k) => claimantsOn(k)).filter((c) => c !== p).map((c) => c.root)
|
|
7757
|
+
)
|
|
7758
|
+
];
|
|
7759
|
+
const fromNamespace = (error) => {
|
|
7760
|
+
const key = p.endpoints.local;
|
|
7761
|
+
const probe = key ? reads.get(key)?.probe : void 0;
|
|
7762
|
+
const procs = probe ? ownProcs(p, key, probe.procs, claimantsOn(key)) : [];
|
|
7763
|
+
return {
|
|
7764
|
+
...identity(p),
|
|
7765
|
+
hosts,
|
|
7766
|
+
apps: counts(
|
|
7767
|
+
procs.map((x) => x.status),
|
|
7768
|
+
0
|
|
7769
|
+
),
|
|
7770
|
+
queue_position: null,
|
|
7771
|
+
memory: probe ? procs.reduce((a, x) => a + procMemory(x, probe), 0) : 0,
|
|
7772
|
+
cpu: null,
|
|
7773
|
+
ports: [],
|
|
7774
|
+
manifest_error: error,
|
|
7775
|
+
notes,
|
|
7776
|
+
present: procs.length > 0,
|
|
7777
|
+
rows: [],
|
|
7778
|
+
rivals
|
|
7779
|
+
};
|
|
7780
|
+
};
|
|
7781
|
+
if (!p.manifest) return fromNamespace(p.error);
|
|
7782
|
+
let rows;
|
|
7783
|
+
try {
|
|
7784
|
+
rows = await manifestRows(p, p.manifest, reads, claimantsOn, opts);
|
|
7785
|
+
} catch (e) {
|
|
7786
|
+
return fromNamespace(stackError(e));
|
|
7787
|
+
}
|
|
7788
|
+
return {
|
|
7789
|
+
...identity(p),
|
|
7790
|
+
hosts,
|
|
7791
|
+
...summary(rows),
|
|
7792
|
+
manifest_error: null,
|
|
7793
|
+
notes,
|
|
7794
|
+
rows,
|
|
7795
|
+
rivals
|
|
7796
|
+
};
|
|
7797
|
+
}
|
|
7798
|
+
async function manifestRows(p, m, reads, claimantsOn, opts) {
|
|
7799
|
+
const byApp = /* @__PURE__ */ new Map();
|
|
7800
|
+
for (const g of new Fleet(m, void 0).groups(Object.keys(m.apps))) {
|
|
7801
|
+
const key = p.endpoints[g.host];
|
|
7802
|
+
const r = key ? reads.get(key) : void 0;
|
|
7803
|
+
let rows;
|
|
7804
|
+
if (!r?.probe) {
|
|
7805
|
+
rows = g.apps.map(
|
|
7806
|
+
(a) => unreachableRow(m.apps[a], g.host, r?.error ?? "not probed yet")
|
|
7807
|
+
);
|
|
7808
|
+
} else {
|
|
7809
|
+
const claimants = claimantsOn(key);
|
|
7810
|
+
const probe = claimants.length < 2 ? r.probe : {
|
|
7811
|
+
...r.probe,
|
|
7812
|
+
procs: [
|
|
7813
|
+
...r.probe.procs.filter((pr) => pr.namespace !== p.project),
|
|
7814
|
+
...ownProcs(p, key, r.probe.procs, claimants)
|
|
7815
|
+
]
|
|
7816
|
+
};
|
|
7817
|
+
const view = {
|
|
7818
|
+
manifest: scopeManifest(m, g.host, "local"),
|
|
7819
|
+
host: pickHost(m, g.host),
|
|
7820
|
+
hereCheck: opts.hereCheck
|
|
7821
|
+
};
|
|
7822
|
+
const snap = await deriveSnapshot(view, probe, {
|
|
7823
|
+
urls: "none",
|
|
7824
|
+
prev: opts.prev?.get(key)
|
|
7825
|
+
});
|
|
7826
|
+
rows = rowsFor(view, snap, g.apps);
|
|
7827
|
+
}
|
|
7828
|
+
for (const row of rows) byApp.set(row.app, row);
|
|
7829
|
+
}
|
|
7830
|
+
return Object.keys(m.apps).map((a) => byApp.get(a));
|
|
7831
|
+
}
|
|
7832
|
+
function identity(p) {
|
|
7833
|
+
return {
|
|
7834
|
+
project: p.project,
|
|
7835
|
+
root: p.root,
|
|
7836
|
+
base_project: p.manifest?.base_project ?? p.project,
|
|
7837
|
+
slot: p.slot,
|
|
7838
|
+
branch: p.branch,
|
|
7839
|
+
source: p.source
|
|
7840
|
+
};
|
|
7841
|
+
}
|
|
7842
|
+
function orderStacks(stacks, all) {
|
|
7843
|
+
const shown = stacks.filter((s) => all || s.present);
|
|
7844
|
+
const names = new Set(shown.map((s) => s.project));
|
|
7845
|
+
const child = (s) => s.base_project !== s.project && names.has(s.base_project);
|
|
7846
|
+
const bases = shown.filter((s) => !child(s)).sort(
|
|
7847
|
+
(a, b) => Number(b.apps.online > 0) - Number(a.apps.online > 0) || a.project.localeCompare(b.project)
|
|
7848
|
+
);
|
|
7849
|
+
const placed = /* @__PURE__ */ new Set();
|
|
7850
|
+
return bases.flatMap((b) => {
|
|
7851
|
+
if (placed.has(b.project)) return [b];
|
|
7852
|
+
placed.add(b.project);
|
|
7853
|
+
return [
|
|
7854
|
+
b,
|
|
7855
|
+
...shown.filter((s) => child(s) && s.base_project === b.project).sort((x, y) => (x.slot ?? 0) - (y.slot ?? 0))
|
|
7856
|
+
];
|
|
7857
|
+
});
|
|
7858
|
+
}
|
|
7859
|
+
|
|
7860
|
+
// src/core/stacks/collect.ts
|
|
7861
|
+
var REMOTE_EVERY_MS = 1e4;
|
|
7862
|
+
var RETRY_MS = 3e4;
|
|
7863
|
+
var REGISTRY_EVERY = 10;
|
|
7864
|
+
var ASK = {
|
|
7865
|
+
cpuSample: false,
|
|
7866
|
+
metrics: true,
|
|
7867
|
+
urls: [],
|
|
7868
|
+
containers: []
|
|
7869
|
+
};
|
|
7870
|
+
var FIRST_ASK = { ...ASK, cpuSample: true };
|
|
7871
|
+
function defaultCollectorDeps(spawner) {
|
|
7872
|
+
return {
|
|
7873
|
+
stateDir: stateDir(),
|
|
7874
|
+
now: Date.now,
|
|
7875
|
+
connectHost: (host) => connectHost(host, { spawner }),
|
|
7876
|
+
probeHost,
|
|
7877
|
+
loadManifest,
|
|
7878
|
+
findManifest: findManifestPath,
|
|
7879
|
+
hostname: os8.hostname,
|
|
7880
|
+
hereCheck: checkHere
|
|
7881
|
+
};
|
|
7882
|
+
}
|
|
7883
|
+
var short = (e) => describeError(e).split("\n")[0].slice(0, 120);
|
|
7884
|
+
var StacksCollector = class {
|
|
7885
|
+
constructor(deps2 = defaultCollectorDeps()) {
|
|
7886
|
+
this.deps = deps2;
|
|
7887
|
+
}
|
|
7888
|
+
deps;
|
|
7889
|
+
known = [];
|
|
7890
|
+
refreshes = 0;
|
|
7891
|
+
registryError = null;
|
|
7892
|
+
local = bareLocalHost();
|
|
7893
|
+
localKey = endpointKey(this.local);
|
|
7894
|
+
/** the host each endpoint is reached through: the first checkout naming it gives it; local's is the bare one */
|
|
7895
|
+
hosts = /* @__PURE__ */ new Map();
|
|
7896
|
+
/** the real roots of the checkouts this collector found on the daemon: they stay daemon-found for its life */
|
|
7897
|
+
adopted = /* @__PURE__ */ new Set();
|
|
7898
|
+
/** the walks that made no stack, by namespace and cwd, with the note each leaves; cleared with each registry read */
|
|
7899
|
+
misses = /* @__PURE__ */ new Map();
|
|
7900
|
+
links = /* @__PURE__ */ new Map();
|
|
7901
|
+
cache = /* @__PURE__ */ new Map();
|
|
7902
|
+
async collect(opts) {
|
|
7903
|
+
if (this.refreshes % REGISTRY_EVERY === 0) this.known = this.readKnown();
|
|
7904
|
+
this.refreshes++;
|
|
7905
|
+
const reads = /* @__PURE__ */ new Map();
|
|
7906
|
+
await this.readAll(reads);
|
|
7907
|
+
const notes = this.adopt(reads.get(this.localKey));
|
|
7908
|
+
await this.readAll(reads);
|
|
7909
|
+
const prev = /* @__PURE__ */ new Map();
|
|
7910
|
+
for (const key of reads.keys()) {
|
|
7911
|
+
const p = this.cache.get(key)?.prev;
|
|
7912
|
+
if (p) prev.set(key, p);
|
|
7913
|
+
}
|
|
7914
|
+
const built = await buildStacks(this.known, reads, {
|
|
7915
|
+
all: opts.all,
|
|
7916
|
+
hereCheck: this.deps.hereCheck,
|
|
7917
|
+
prev,
|
|
7918
|
+
orphanNotes: notes
|
|
7919
|
+
});
|
|
7920
|
+
const stacks = orderStacks(built.stacks, opts.all);
|
|
7921
|
+
const localRead = reads.get(this.localKey);
|
|
7922
|
+
const watcher = localRead?.probe?.procs.find(
|
|
7923
|
+
(p) => p.name === WATCHER_NAME && p.namespace === WATCHER_NS
|
|
7924
|
+
);
|
|
7925
|
+
const mem = localRead?.probe?.data.memory ?? null;
|
|
7926
|
+
let queue = 0;
|
|
7927
|
+
try {
|
|
7928
|
+
queue = new LeaseStore(this.deps.stateDir).list().filter((l) => l.state === "pending").length;
|
|
7929
|
+
} catch {
|
|
7930
|
+
}
|
|
7931
|
+
const answer = {
|
|
7932
|
+
host: {
|
|
7933
|
+
name: this.deps.hostname(),
|
|
7934
|
+
memory: mem ? { available_mib: mem.available_mib, total_mib: mem.total_mib } : null,
|
|
7935
|
+
watcher: watcher ? {
|
|
7936
|
+
online: watcher.status === "online",
|
|
7937
|
+
pid: watcher.pid > 0 ? watcher.pid : null
|
|
7938
|
+
} : null,
|
|
7939
|
+
queue,
|
|
7940
|
+
notes: [
|
|
7941
|
+
this.registryError ? `registry unreadable: ${this.registryError}` : "",
|
|
7942
|
+
localRead?.error ? `local pm2: ${localRead.error}` : ""
|
|
7943
|
+
].filter(Boolean)
|
|
7944
|
+
},
|
|
7945
|
+
stacks: stacks.map(({ rows: _rows, rivals: _rivals, ...s }) => s),
|
|
7946
|
+
orphans: built.orphans
|
|
7947
|
+
};
|
|
7948
|
+
const manifests = new Map(
|
|
7949
|
+
this.known.flatMap(
|
|
7950
|
+
(p) => p.manifest ? [[p.root, p.manifest]] : []
|
|
7951
|
+
)
|
|
7952
|
+
);
|
|
7953
|
+
return { answer, stacks, manifests };
|
|
7954
|
+
}
|
|
7955
|
+
/** Reads every endpoint the known checkouts need, and the local daemon: those not read yet this refresh. */
|
|
7956
|
+
async readAll(reads) {
|
|
7957
|
+
const keys = /* @__PURE__ */ new Set([
|
|
7958
|
+
this.localKey,
|
|
7959
|
+
...this.known.flatMap((p) => Object.values(p.endpoints))
|
|
7960
|
+
]);
|
|
7961
|
+
await Promise.all(
|
|
7962
|
+
[...keys].filter((key) => !reads.has(key)).map(async (key) => {
|
|
7963
|
+
const host = this.hosts.get(key);
|
|
7964
|
+
if (host) reads.set(key, await this.read(key, host));
|
|
7965
|
+
})
|
|
7966
|
+
);
|
|
7967
|
+
}
|
|
7968
|
+
/** One endpoint's answer: a fresh probe when it is due, else (or when the probe fails) the last data with its age. */
|
|
7969
|
+
async read(key, host) {
|
|
7970
|
+
const now = this.deps.now();
|
|
7971
|
+
const c = this.cache.get(key);
|
|
7972
|
+
const answer = (e) => ({
|
|
7973
|
+
key,
|
|
7974
|
+
label: endpointLabel(host),
|
|
7975
|
+
probe: e.probe,
|
|
7976
|
+
error: e.error,
|
|
7977
|
+
age_s: e.probe ? Math.round((now - e.at) / 1e3) : 0
|
|
7978
|
+
});
|
|
7979
|
+
if (c && now < c.next) return answer(c);
|
|
7980
|
+
let entry;
|
|
7981
|
+
try {
|
|
7982
|
+
let link = this.links.get(key);
|
|
7983
|
+
if (!link) {
|
|
7984
|
+
link = this.deps.connectHost(host);
|
|
7985
|
+
this.links.set(key, link);
|
|
7986
|
+
}
|
|
7987
|
+
const last = c?.probe;
|
|
7988
|
+
const probe = await this.deps.probeHost(
|
|
7989
|
+
await link,
|
|
7990
|
+
last ? ASK : FIRST_ASK
|
|
7991
|
+
);
|
|
7992
|
+
const sample = last?.data.stats.samples.at(-1);
|
|
7993
|
+
entry = {
|
|
7994
|
+
probe,
|
|
7995
|
+
prev: last && sample ? { sample, at: last.sampledAt } : void 0,
|
|
7996
|
+
error: null,
|
|
7997
|
+
at: now,
|
|
7998
|
+
next: host.local ? now : now + REMOTE_EVERY_MS
|
|
7999
|
+
};
|
|
8000
|
+
} catch (e) {
|
|
8001
|
+
this.links.delete(key);
|
|
8002
|
+
entry = {
|
|
8003
|
+
probe: c?.probe ?? null,
|
|
8004
|
+
prev: c?.prev,
|
|
8005
|
+
error: short(e),
|
|
8006
|
+
at: c?.at ?? now,
|
|
8007
|
+
next: host.local ? now : now + RETRY_MS
|
|
8008
|
+
};
|
|
8009
|
+
}
|
|
8010
|
+
this.cache.set(key, entry);
|
|
8011
|
+
return answer(entry);
|
|
8012
|
+
}
|
|
8013
|
+
/** The checkouts the registry records, one per real root; an entry whose project changed follows its manifest. */
|
|
8014
|
+
readKnown() {
|
|
8015
|
+
const { entries, error } = listProjects(this.deps.stateDir);
|
|
8016
|
+
this.registryError = error;
|
|
8017
|
+
this.hosts = /* @__PURE__ */ new Map([[this.localKey, this.local]]);
|
|
8018
|
+
this.misses.clear();
|
|
8019
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
8020
|
+
for (const e of entries) {
|
|
8021
|
+
const source = this.adopted.has(realOf(e.root)) ? "daemon" : "registry";
|
|
8022
|
+
let m;
|
|
8023
|
+
try {
|
|
8024
|
+
m = this.deps.loadManifest(e.root);
|
|
8025
|
+
} catch (err) {
|
|
8026
|
+
byRoot.set(realOf(e.root), this.broken(e.project, e.root, source, err));
|
|
8027
|
+
continue;
|
|
8028
|
+
}
|
|
8029
|
+
if (m.project !== e.project) {
|
|
8030
|
+
forgetProject(e.project, e.root, this.deps.stateDir);
|
|
8031
|
+
recordProject(m, this.deps.stateDir);
|
|
8032
|
+
}
|
|
8033
|
+
byRoot.set(realOf(m.root), this.inputOf(m, source));
|
|
8034
|
+
}
|
|
8035
|
+
return [...byRoot.values()];
|
|
8036
|
+
}
|
|
8037
|
+
/** A checkout without a usable manifest: its error, and its namespace on the local daemon for what runs. */
|
|
8038
|
+
broken(project, root, source, e) {
|
|
8039
|
+
return {
|
|
8040
|
+
project,
|
|
8041
|
+
root,
|
|
8042
|
+
source,
|
|
8043
|
+
manifest: null,
|
|
8044
|
+
error: stackError(e),
|
|
8045
|
+
endpoints: { local: this.localKey },
|
|
8046
|
+
slot: null,
|
|
8047
|
+
branch: null
|
|
8048
|
+
};
|
|
8049
|
+
}
|
|
8050
|
+
/** A loaded checkout with its hosts' endpoints and its slot; one whose endpoints cannot be built carries the error. */
|
|
8051
|
+
inputOf(m, source) {
|
|
8052
|
+
const endpoints = {};
|
|
8053
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
8054
|
+
try {
|
|
8055
|
+
for (const g of new Fleet(m, void 0).groups(Object.keys(m.apps))) {
|
|
8056
|
+
const host = pickHost(m, g.host);
|
|
8057
|
+
endpoints[g.host] = endpointKey(host);
|
|
8058
|
+
hosts.set(endpoints[g.host], host);
|
|
8059
|
+
}
|
|
8060
|
+
} catch (e) {
|
|
8061
|
+
return this.broken(m.project, m.root, source, e);
|
|
8062
|
+
}
|
|
8063
|
+
for (const [key, host] of hosts)
|
|
8064
|
+
if (!this.hosts.has(key)) this.hosts.set(key, host);
|
|
8065
|
+
let slot = m.slot;
|
|
8066
|
+
let branch = null;
|
|
8067
|
+
try {
|
|
8068
|
+
const reg = readRegistry(
|
|
8069
|
+
registryPath(this.deps.stateDir, m.base_project, m.repo)
|
|
8070
|
+
);
|
|
8071
|
+
const hit = reg && Object.entries(reg.slots).find(
|
|
8072
|
+
([, s]) => realOf(s.path) === realOf(m.root)
|
|
8073
|
+
);
|
|
8074
|
+
if (hit) {
|
|
8075
|
+
slot = Number(hit[0]);
|
|
8076
|
+
branch = hit[1].branch;
|
|
8077
|
+
}
|
|
8078
|
+
} catch {
|
|
8079
|
+
}
|
|
8080
|
+
return {
|
|
8081
|
+
project: m.project,
|
|
8082
|
+
root: m.root,
|
|
8083
|
+
source,
|
|
8084
|
+
manifest: m,
|
|
8085
|
+
error: null,
|
|
8086
|
+
endpoints,
|
|
8087
|
+
slot,
|
|
8088
|
+
branch
|
|
8089
|
+
};
|
|
8090
|
+
}
|
|
8091
|
+
/**
|
|
8092
|
+
* The local daemon's namespaces no known checkout claims: a manifest above a process's cwd that names the namespace
|
|
8093
|
+
* makes it a stack, recorded from then on. One that names another project, or does not load, leaves a note on the
|
|
8094
|
+
* orphan; a checkout known already (its apps run elsewhere) is never a second stack. A walk that made no stack is
|
|
8095
|
+
* kept with its note until the registry is read again: a refresh does not parse the same manifest twice.
|
|
8096
|
+
*/
|
|
8097
|
+
adopt(read) {
|
|
8098
|
+
const notes = /* @__PURE__ */ new Map();
|
|
8099
|
+
if (!read?.probe) return notes;
|
|
8100
|
+
const claimed = new Set(
|
|
8101
|
+
this.known.filter((p) => Object.values(p.endpoints).includes(this.localKey)).map((p) => p.project)
|
|
8102
|
+
);
|
|
8103
|
+
const roots = new Set(this.known.map((p) => realOf(p.root)));
|
|
8104
|
+
const cwds = /* @__PURE__ */ new Map();
|
|
8105
|
+
for (const p of read.probe.procs)
|
|
8106
|
+
if (p.cwd && p.namespace !== WATCHER_NS && !claimed.has(p.namespace))
|
|
8107
|
+
cwds.set(p.namespace, (cwds.get(p.namespace) ?? /* @__PURE__ */ new Set()).add(p.cwd));
|
|
8108
|
+
for (const [ns, dirs] of cwds) {
|
|
8109
|
+
const note = claimKey(this.localKey, ns);
|
|
8110
|
+
for (const cwd of dirs) {
|
|
8111
|
+
const missed = `${ns}\0${cwd}`;
|
|
8112
|
+
const r = this.misses.has(missed) ? { note: this.misses.get(missed) ?? null } : this.walk(ns, cwd, roots);
|
|
8113
|
+
if ("manifest" in r) {
|
|
8114
|
+
const real = realOf(r.manifest.root);
|
|
8115
|
+
recordProject(r.manifest, this.deps.stateDir);
|
|
8116
|
+
this.adopted.add(real);
|
|
8117
|
+
roots.add(real);
|
|
8118
|
+
this.known.push(this.inputOf(r.manifest, "daemon"));
|
|
8119
|
+
notes.delete(note);
|
|
8120
|
+
break;
|
|
8121
|
+
}
|
|
8122
|
+
this.misses.set(missed, r.note);
|
|
8123
|
+
if (r.note !== null) notes.set(note, r.note);
|
|
7165
8124
|
}
|
|
7166
|
-
}
|
|
7167
|
-
|
|
7168
|
-
}
|
|
7169
|
-
async function listFleet(fleet, names, opts = {}) {
|
|
7170
|
-
const probes = await probeFleet(
|
|
7171
|
-
fleet,
|
|
7172
|
-
names,
|
|
7173
|
-
(ctx) => snapshot(ctx, { urls: names, ...opts })
|
|
7174
|
-
);
|
|
7175
|
-
const failed = probes.filter((p) => p.error !== void 0);
|
|
7176
|
-
if (failed.length === probes.length && failed.length > 0)
|
|
7177
|
-
throw failed[0].error;
|
|
7178
|
-
const byApp = /* @__PURE__ */ new Map();
|
|
7179
|
-
for (const p of probes) {
|
|
7180
|
-
const rows = p.snap ? rowsFor(p.ctx, p.snap, p.apps) : p.apps.map(
|
|
7181
|
-
(a) => unreachableRow(fleet.manifest.apps[a], p.host, describeError(p.error))
|
|
7182
|
-
);
|
|
7183
|
-
for (const r of rows) byApp.set(r.app, r);
|
|
8125
|
+
}
|
|
8126
|
+
return notes;
|
|
7184
8127
|
}
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
}
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
app: { ...full, depends_on: full.depends_on.map(depView) },
|
|
7202
|
-
host
|
|
7203
|
-
};
|
|
7204
|
-
}
|
|
8128
|
+
/** The walk up from one cwd of namespace `ns`: the manifest that makes it a stack, or the note it leaves (null: none). */
|
|
8129
|
+
walk(ns, cwd, roots) {
|
|
8130
|
+
const found = this.deps.findManifest(cwd);
|
|
8131
|
+
if (!found) return { note: null };
|
|
8132
|
+
let m;
|
|
8133
|
+
try {
|
|
8134
|
+
m = this.deps.loadManifest(found.root);
|
|
8135
|
+
} catch (e) {
|
|
8136
|
+
return { note: `manifest at ${found.path}: ${stackError(e).code}` };
|
|
8137
|
+
}
|
|
8138
|
+
if (m.project !== ns)
|
|
8139
|
+
return { note: `manifest at ${found.path} names ${m.project}` };
|
|
8140
|
+
if (roots.has(realOf(m.root))) return { note: null };
|
|
8141
|
+
return { manifest: m };
|
|
8142
|
+
}
|
|
8143
|
+
};
|
|
7205
8144
|
|
|
7206
8145
|
// src/core/leases/watcher.ts
|
|
8146
|
+
import { spawn as spawn2 } from "child_process";
|
|
8147
|
+
import fs13 from "fs";
|
|
8148
|
+
import path17 from "path";
|
|
7207
8149
|
async function moveIf(store, id, from, to, patch = {}, also = () => true) {
|
|
7208
8150
|
return store.withLock(async () => {
|
|
7209
8151
|
const cur = store.get(id);
|
|
@@ -7396,13 +8338,13 @@ async function leaveIdle(deps2) {
|
|
|
7396
8338
|
return store.withLock(() => {
|
|
7397
8339
|
if (store.list().some((l) => l.state === "pending" || l.state === "starting"))
|
|
7398
8340
|
return false;
|
|
7399
|
-
const file =
|
|
8341
|
+
const file = path17.join(path17.dirname(store.dir), WATCHER_NOTE);
|
|
7400
8342
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
7401
|
-
|
|
8343
|
+
fs13.writeFileSync(
|
|
7402
8344
|
tmp,
|
|
7403
8345
|
JSON.stringify({ exiting_at: new Date(deps2.now()).toISOString() })
|
|
7404
8346
|
);
|
|
7405
|
-
|
|
8347
|
+
fs13.renameSync(tmp, file);
|
|
7406
8348
|
return true;
|
|
7407
8349
|
});
|
|
7408
8350
|
}
|
|
@@ -7513,7 +8455,7 @@ async function watchLeases(deps2, opts = {}) {
|
|
|
7513
8455
|
}
|
|
7514
8456
|
function leaseDirWatch(dir) {
|
|
7515
8457
|
return (cb) => {
|
|
7516
|
-
const w =
|
|
8458
|
+
const w = fs13.watch(dir, (_e, name2) => {
|
|
7517
8459
|
if (!name2?.startsWith(".")) cb();
|
|
7518
8460
|
});
|
|
7519
8461
|
return () => w.close();
|
|
@@ -7876,8 +8818,8 @@ async function runExec(ctx, opts, deps2) {
|
|
|
7876
8818
|
|
|
7877
8819
|
// src/core/worktree.ts
|
|
7878
8820
|
import { execFile } from "child_process";
|
|
7879
|
-
import
|
|
7880
|
-
import
|
|
8821
|
+
import fs14 from "fs";
|
|
8822
|
+
import path18 from "path";
|
|
7881
8823
|
var gitRunner = (args, cwd) => new Promise((resolve) => {
|
|
7882
8824
|
execFile(
|
|
7883
8825
|
"git",
|
|
@@ -7902,7 +8844,7 @@ var gitOk = async (git, args, cwd) => {
|
|
|
7902
8844
|
};
|
|
7903
8845
|
var present = (p) => {
|
|
7904
8846
|
try {
|
|
7905
|
-
|
|
8847
|
+
fs14.lstatSync(p);
|
|
7906
8848
|
return true;
|
|
7907
8849
|
} catch {
|
|
7908
8850
|
return false;
|
|
@@ -7910,15 +8852,15 @@ var present = (p) => {
|
|
|
7910
8852
|
};
|
|
7911
8853
|
async function mainCheckout(root, git) {
|
|
7912
8854
|
const common = (await gitOk(git, ["rev-parse", "--git-common-dir"], root)).trim();
|
|
7913
|
-
return
|
|
8855
|
+
return fs14.realpathSync(path18.dirname(path18.resolve(root, common)));
|
|
7914
8856
|
}
|
|
7915
8857
|
function worktreeDir(m, main) {
|
|
7916
|
-
return m.worktree.dir ?
|
|
8858
|
+
return m.worktree.dir ? path18.resolve(main, m.worktree.dir) : `${main}.worktrees`;
|
|
7917
8859
|
}
|
|
7918
8860
|
function installCommand(m, root) {
|
|
7919
8861
|
if (m.worktree.install === false) return null;
|
|
7920
8862
|
if (typeof m.worktree.install === "string") return m.worktree.install;
|
|
7921
|
-
const has = (f) =>
|
|
8863
|
+
const has = (f) => fs14.existsSync(path18.join(root, f));
|
|
7922
8864
|
if (has("bun.lock") || has("bun.lockb")) return "bun install";
|
|
7923
8865
|
if (has("yarn.lock")) return "yarn install";
|
|
7924
8866
|
if (has("pnpm-lock.yaml")) return "pnpm install";
|
|
@@ -7926,22 +8868,22 @@ function installCommand(m, root) {
|
|
|
7926
8868
|
return null;
|
|
7927
8869
|
}
|
|
7928
8870
|
function planCopies(main, target, copy, link, fsDeps = {}) {
|
|
7929
|
-
const exists = fsDeps.exists ?? ((p) =>
|
|
7930
|
-
const glob = fsDeps.glob ?? ((pattern, cwd) =>
|
|
8871
|
+
const exists = fsDeps.exists ?? ((p) => fs14.existsSync(p));
|
|
8872
|
+
const glob = fsDeps.glob ?? ((pattern, cwd) => fs14.globSync(pattern, { cwd }));
|
|
7931
8873
|
const copies = [];
|
|
7932
8874
|
const missing = [];
|
|
7933
8875
|
for (const entry of copy) {
|
|
7934
|
-
const hits2 = /[*?[]/.test(entry) ? glob(entry, main).sort() : exists(
|
|
8876
|
+
const hits2 = /[*?[]/.test(entry) ? glob(entry, main).sort() : exists(path18.join(main, entry)) ? [entry] : [];
|
|
7935
8877
|
if (hits2.length === 0) {
|
|
7936
8878
|
missing.push(entry);
|
|
7937
8879
|
continue;
|
|
7938
8880
|
}
|
|
7939
8881
|
for (const h of hits2)
|
|
7940
|
-
copies.push({ from:
|
|
8882
|
+
copies.push({ from: path18.join(main, h), to: path18.join(target, h) });
|
|
7941
8883
|
}
|
|
7942
8884
|
const links = link.map((l) => ({
|
|
7943
|
-
from:
|
|
7944
|
-
to:
|
|
8885
|
+
from: path18.join(main, l),
|
|
8886
|
+
to: path18.join(target, l)
|
|
7945
8887
|
}));
|
|
7946
8888
|
return { copies, links, missing };
|
|
7947
8889
|
}
|
|
@@ -7957,7 +8899,7 @@ function ownManifest(target, log, instead) {
|
|
|
7957
8899
|
}
|
|
7958
8900
|
}
|
|
7959
8901
|
function excludePattern(rel) {
|
|
7960
|
-
const p =
|
|
8902
|
+
const p = path18.posix.normalize(rel.split(path18.sep).join("/")).replace(/\/+$/, "");
|
|
7961
8903
|
if (p === "." || p === "" || p === ".." || p.startsWith("../") || p.startsWith("/"))
|
|
7962
8904
|
return null;
|
|
7963
8905
|
return `/${p.replace(/[\\*?[\]]/g, (c) => `\\${c}`).replace(/ $/, "\\ ")}`;
|
|
@@ -7981,13 +8923,13 @@ async function excludeLinks(target, links, git, log) {
|
|
|
7981
8923
|
);
|
|
7982
8924
|
return [];
|
|
7983
8925
|
}
|
|
7984
|
-
const file =
|
|
7985
|
-
|
|
7986
|
-
const had =
|
|
8926
|
+
const file = path18.resolve(target, r.stdout.trim());
|
|
8927
|
+
fs14.mkdirSync(path18.dirname(file), { recursive: true });
|
|
8928
|
+
const had = fs14.existsSync(file) ? fs14.readFileSync(file, "utf8") : "";
|
|
7987
8929
|
const lines = new Set(had.split("\n"));
|
|
7988
8930
|
const add2 = patterns.filter((p) => !lines.has(p));
|
|
7989
8931
|
if (add2.length)
|
|
7990
|
-
|
|
8932
|
+
fs14.appendFileSync(
|
|
7991
8933
|
file,
|
|
7992
8934
|
`${had.length && !had.endsWith("\n") ? "\n" : ""}${add2.join("\n")}
|
|
7993
8935
|
`
|
|
@@ -8003,7 +8945,7 @@ function registryFor(m, main, mainBranch, deps2) {
|
|
|
8003
8945
|
});
|
|
8004
8946
|
const { registry, freed } = reclaimGone(
|
|
8005
8947
|
read,
|
|
8006
|
-
deps2.exists ?? ((p) =>
|
|
8948
|
+
deps2.exists ?? ((p) => fs14.existsSync(p))
|
|
8007
8949
|
);
|
|
8008
8950
|
for (const s of freed) deps2.log(`slot ${s} reclaimed: its checkout is gone`);
|
|
8009
8951
|
return { file, registry };
|
|
@@ -8033,12 +8975,12 @@ async function worktreeList(main, git) {
|
|
|
8033
8975
|
}
|
|
8034
8976
|
function realAhead(p) {
|
|
8035
8977
|
const rest = [];
|
|
8036
|
-
let d =
|
|
8037
|
-
while (!present(d) &&
|
|
8038
|
-
rest.unshift(
|
|
8039
|
-
d =
|
|
8978
|
+
let d = path18.resolve(p);
|
|
8979
|
+
while (!present(d) && path18.dirname(d) !== d) {
|
|
8980
|
+
rest.unshift(path18.basename(d));
|
|
8981
|
+
d = path18.dirname(d);
|
|
8040
8982
|
}
|
|
8041
|
-
return
|
|
8983
|
+
return path18.join(realOf(d), ...rest);
|
|
8042
8984
|
}
|
|
8043
8985
|
var shPath = (p) => /^[\w./@%+:-]+$/.test(p) ? p : shq(p);
|
|
8044
8986
|
async function worktreeAdd(m, opts, deps2) {
|
|
@@ -8075,7 +9017,7 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8075
9017
|
}
|
|
8076
9018
|
);
|
|
8077
9019
|
const target = realAhead(
|
|
8078
|
-
|
|
9020
|
+
path18.join(worktreeDir(m, main), ...opts.branch.split("/"))
|
|
8079
9021
|
);
|
|
8080
9022
|
const taken = () => new McprocError("WORKTREE_EXISTS", `${target} exists already`, {
|
|
8081
9023
|
hint: "Remove it, or pick another branch name."
|
|
@@ -8098,9 +9040,9 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8098
9040
|
`project slug ${project2} is the slug of slot ${k} (${e.path})`,
|
|
8099
9041
|
{ hint: "Pass another project slug." }
|
|
8100
9042
|
);
|
|
8101
|
-
|
|
9043
|
+
fs14.mkdirSync(path18.dirname(target), { recursive: true });
|
|
8102
9044
|
try {
|
|
8103
|
-
|
|
9045
|
+
fs14.mkdirSync(target);
|
|
8104
9046
|
} catch (e) {
|
|
8105
9047
|
if (e.code === "EEXIST") throw taken();
|
|
8106
9048
|
throw e;
|
|
@@ -8135,7 +9077,7 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8135
9077
|
);
|
|
8136
9078
|
} catch (e) {
|
|
8137
9079
|
try {
|
|
8138
|
-
|
|
9080
|
+
fs14.rmdirSync(target);
|
|
8139
9081
|
} catch {
|
|
8140
9082
|
}
|
|
8141
9083
|
await store.withLock(() => {
|
|
@@ -8155,8 +9097,8 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8155
9097
|
repo: main,
|
|
8156
9098
|
vars: (probe ?? m).slot !== null ? { slot } : void 0
|
|
8157
9099
|
});
|
|
8158
|
-
const gi =
|
|
8159
|
-
if (!present(gi))
|
|
9100
|
+
const gi = path18.join(target, ".mcproc", ".gitignore");
|
|
9101
|
+
if (!present(gi)) fs14.writeFileSync(gi, "*\n");
|
|
8160
9102
|
const own2 = probe ? ownManifest(target, deps2.log, "the copies and the install") : null;
|
|
8161
9103
|
const dm = own2 ?? m;
|
|
8162
9104
|
const only = (xs, not) => [...new Set(xs)].filter((e) => !not.includes(e));
|
|
@@ -8186,15 +9128,15 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8186
9128
|
missing: [...ownPlan.missing, ...extraPlan.missing]
|
|
8187
9129
|
};
|
|
8188
9130
|
const fromOverride = [
|
|
8189
|
-
...addedCopies.map((c) =>
|
|
8190
|
-
...addedLinks.map((l) =>
|
|
9131
|
+
...addedCopies.map((c) => path18.relative(main, c.from)),
|
|
9132
|
+
...addedLinks.map((l) => path18.relative(main, l.from))
|
|
8191
9133
|
];
|
|
8192
9134
|
let step = "";
|
|
8193
9135
|
try {
|
|
8194
9136
|
for (const c of plan.copies) {
|
|
8195
9137
|
step = `copying ${c.from}`;
|
|
8196
|
-
|
|
8197
|
-
|
|
9138
|
+
fs14.mkdirSync(path18.dirname(c.to), { recursive: true });
|
|
9139
|
+
fs14.cpSync(c.from, c.to, {
|
|
8198
9140
|
recursive: true,
|
|
8199
9141
|
force: false,
|
|
8200
9142
|
errorOnExist: false
|
|
@@ -8202,8 +9144,8 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8202
9144
|
}
|
|
8203
9145
|
for (const l of plan.links) {
|
|
8204
9146
|
step = `linking ${l.from}`;
|
|
8205
|
-
|
|
8206
|
-
if (!present(l.to))
|
|
9147
|
+
fs14.mkdirSync(path18.dirname(l.to), { recursive: true });
|
|
9148
|
+
if (!present(l.to)) fs14.symlinkSync(l.from, l.to);
|
|
8207
9149
|
}
|
|
8208
9150
|
} catch (e) {
|
|
8209
9151
|
throw new McprocError(
|
|
@@ -8218,7 +9160,7 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8218
9160
|
deps2.log(`copy: ${miss} is not in ${main}, skipped`);
|
|
8219
9161
|
const excluded = await excludeLinks(
|
|
8220
9162
|
target,
|
|
8221
|
-
plan.links.map((l) =>
|
|
9163
|
+
plan.links.map((l) => path18.relative(target, l.to)),
|
|
8222
9164
|
deps2.git,
|
|
8223
9165
|
deps2.log
|
|
8224
9166
|
);
|
|
@@ -8247,8 +9189,8 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8247
9189
|
project,
|
|
8248
9190
|
repo: main,
|
|
8249
9191
|
manifest: own2 ? "worktree" : "main",
|
|
8250
|
-
copied: plan.copies.map((c) =>
|
|
8251
|
-
linked: plan.links.map((l) =>
|
|
9192
|
+
copied: plan.copies.map((c) => path18.relative(main, c.from)),
|
|
9193
|
+
linked: plan.links.map((l) => path18.relative(main, l.from)),
|
|
8252
9194
|
from_override: fromOverride,
|
|
8253
9195
|
excluded,
|
|
8254
9196
|
missing: plan.missing,
|
|
@@ -8265,7 +9207,7 @@ async function worktreeLs(m, deps2) {
|
|
|
8265
9207
|
branch: await currentBranch(main, deps2.git),
|
|
8266
9208
|
project: m.base_project
|
|
8267
9209
|
});
|
|
8268
|
-
const exists = deps2.exists ?? ((p) =>
|
|
9210
|
+
const exists = deps2.exists ?? ((p) => fs14.existsSync(p));
|
|
8269
9211
|
const branches = await worktreeList(main, deps2.git);
|
|
8270
9212
|
const procs = await deps2.jlist();
|
|
8271
9213
|
const leases = new LeaseStore(deps2.stateDir).list();
|
|
@@ -8315,7 +9257,7 @@ async function worktreeRemove(m, ref, opts, deps2) {
|
|
|
8315
9257
|
if (!(await worktreeList(main, deps2.git)).has(realOf(e.path))) {
|
|
8316
9258
|
let empty = false;
|
|
8317
9259
|
try {
|
|
8318
|
-
empty =
|
|
9260
|
+
empty = fs14.readdirSync(e.path).length === 0;
|
|
8319
9261
|
} catch {
|
|
8320
9262
|
}
|
|
8321
9263
|
if (!empty)
|
|
@@ -8324,7 +9266,7 @@ async function worktreeRemove(m, ref, opts, deps2) {
|
|
|
8324
9266
|
`${e.path} (slot ${k}) is no git worktree of ${main}`,
|
|
8325
9267
|
{ hint: "Look at what is in it, remove it by hand, then worktree ls." }
|
|
8326
9268
|
);
|
|
8327
|
-
|
|
9269
|
+
fs14.rmdirSync(e.path);
|
|
8328
9270
|
deps2.log(`${e.path} was an empty folder git never filled: removed`);
|
|
8329
9271
|
await free();
|
|
8330
9272
|
return { path: e.path, slot: Number(k), branch: e.branch, stopped: [] };
|
|
@@ -8345,14 +9287,15 @@ async function worktreeRemove(m, ref, opts, deps2) {
|
|
|
8345
9287
|
main
|
|
8346
9288
|
);
|
|
8347
9289
|
const top = realOf(worktreeDir(m, main));
|
|
8348
|
-
for (let d =
|
|
9290
|
+
for (let d = path18.dirname(e.path); d.startsWith(`${top}${path18.sep}`); d = path18.dirname(d)) {
|
|
8349
9291
|
try {
|
|
8350
|
-
|
|
9292
|
+
fs14.rmdirSync(d);
|
|
8351
9293
|
} catch {
|
|
8352
9294
|
break;
|
|
8353
9295
|
}
|
|
8354
9296
|
}
|
|
8355
9297
|
await free();
|
|
9298
|
+
forgetProject(e.project, e.path, deps2.stateDir);
|
|
8356
9299
|
return { path: e.path, slot: Number(k), branch: e.branch, stopped };
|
|
8357
9300
|
}
|
|
8358
9301
|
async function stopCheckout(root, opts) {
|
|
@@ -8412,10 +9355,10 @@ async function defaultWorktreeDeps(ctx, io, opts = {}) {
|
|
|
8412
9355
|
}
|
|
8413
9356
|
|
|
8414
9357
|
// src/core/incidents/store.ts
|
|
8415
|
-
import
|
|
9358
|
+
import fs15 from "fs";
|
|
8416
9359
|
import { createHash as createHash3, randomUUID } from "crypto";
|
|
8417
9360
|
import net from "net";
|
|
8418
|
-
import
|
|
9361
|
+
import path19 from "path";
|
|
8419
9362
|
|
|
8420
9363
|
// src/core/incidents/taken.ts
|
|
8421
9364
|
function takenPoints(s) {
|
|
@@ -8485,16 +9428,16 @@ async function pollIncidents(fleet, names, opts = {}) {
|
|
|
8485
9428
|
};
|
|
8486
9429
|
const files = targets.flatMap(
|
|
8487
9430
|
(t) => STREAMS2.map((stream) => {
|
|
8488
|
-
const
|
|
9431
|
+
const path23 = stream === "out" ? t.p.out_log : t.p.err_log;
|
|
8489
9432
|
const mark = t.prev?.[stream];
|
|
8490
|
-
const same = mark && mark.path ===
|
|
9433
|
+
const same = mark && mark.path === path23;
|
|
8491
9434
|
const emptied = opts.restart?.[markKey(g.host, t.app)]?.[stream];
|
|
8492
|
-
const restarted = !!emptied && (emptied.includes(
|
|
9435
|
+
const restarted = !!emptied && (emptied.includes(path23) || emptied.includes("*"));
|
|
8493
9436
|
const anchor = !restarted && same && mark.at !== void 0 && mark.anchor !== void 0 ? { at: mark.at, hex: mark.anchor } : null;
|
|
8494
9437
|
return {
|
|
8495
9438
|
t,
|
|
8496
9439
|
stream,
|
|
8497
|
-
path:
|
|
9440
|
+
path: path23,
|
|
8498
9441
|
offset: restarted ? 0 : same ? mark.offset : null,
|
|
8499
9442
|
anchor,
|
|
8500
9443
|
restarted
|
|
@@ -8758,7 +9701,7 @@ function processEvents(app, host, p, prev, errLines) {
|
|
|
8758
9701
|
}
|
|
8759
9702
|
|
|
8760
9703
|
// src/core/incidents/store.ts
|
|
8761
|
-
var fileClearKey = (key, stream,
|
|
9704
|
+
var fileClearKey = (key, stream, path23) => `${key}\0${stream}\0${path23}`;
|
|
8762
9705
|
var countedClearKey = (key) => `${key}\0counted`;
|
|
8763
9706
|
var isAppClearKey = (k) => !k.includes("\0");
|
|
8764
9707
|
function parseFileClearKey(k) {
|
|
@@ -8767,13 +9710,13 @@ function parseFileClearKey(k) {
|
|
|
8767
9710
|
return { key, stream, path: rest.join("\0") };
|
|
8768
9711
|
}
|
|
8769
9712
|
var LOCK_WAIT_MS2 = 3e4;
|
|
8770
|
-
var storePath = (manifestPath) =>
|
|
9713
|
+
var storePath = (manifestPath) => path19.join(path19.dirname(manifestPath), "incidents.json");
|
|
8771
9714
|
var pad = (n) => String(n).padStart(2, "0");
|
|
8772
9715
|
var localStamp = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
8773
9716
|
function readStore(file) {
|
|
8774
9717
|
try {
|
|
8775
9718
|
const s = JSON.parse(
|
|
8776
|
-
|
|
9719
|
+
fs15.readFileSync(file, "utf8")
|
|
8777
9720
|
);
|
|
8778
9721
|
const obj = (o) => typeof o === "object" && o !== null ? o : {};
|
|
8779
9722
|
if (s && s.v === 1 && typeof s.apps === "object" && s.apps !== null)
|
|
@@ -8788,12 +9731,12 @@ function readStore(file) {
|
|
|
8788
9731
|
return { v: 1, apps: {}, cleared: {}, clears: {} };
|
|
8789
9732
|
}
|
|
8790
9733
|
function lockAddress(file) {
|
|
8791
|
-
let dir =
|
|
9734
|
+
let dir = path19.dirname(path19.resolve(file));
|
|
8792
9735
|
try {
|
|
8793
|
-
dir =
|
|
9736
|
+
dir = fs15.realpathSync(dir);
|
|
8794
9737
|
} catch {
|
|
8795
9738
|
}
|
|
8796
|
-
const id = createHash3("sha256").update(
|
|
9739
|
+
const id = createHash3("sha256").update(path19.join(dir, path19.basename(file))).digest();
|
|
8797
9740
|
return process.platform === "linux" ? { path: `\0mcproc-incidents-${id.toString("hex").slice(0, 32)}` } : { host: "127.0.0.1", port: 4e4 + id.readUInt16BE(0) % 9e3 };
|
|
8798
9741
|
}
|
|
8799
9742
|
function tryLock(addr) {
|
|
@@ -8829,8 +9772,8 @@ async function withStore(file, fn, waitMs = LOCK_WAIT_MS2) {
|
|
|
8829
9772
|
try {
|
|
8830
9773
|
const { result, next } = await fn(readStore(file));
|
|
8831
9774
|
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
8832
|
-
|
|
8833
|
-
|
|
9775
|
+
fs15.writeFileSync(tmp, JSON.stringify(next));
|
|
9776
|
+
fs15.renameSync(tmp, file);
|
|
8834
9777
|
return result;
|
|
8835
9778
|
} finally {
|
|
8836
9779
|
await new Promise((r) => lock.close(r));
|
|
@@ -8897,8 +9840,8 @@ async function clearLogs(fleet, names, file = storePath(fleet.manifest.path), no
|
|
|
8897
9840
|
for (const f of await flushLogs(await fleet.ctx(g.host), g.apps)) {
|
|
8898
9841
|
const key = markKey(g.host, f.app);
|
|
8899
9842
|
const prev = s.apps[key];
|
|
8900
|
-
const mark = (
|
|
8901
|
-
path:
|
|
9843
|
+
const mark = (path23, was) => f.failed.includes(path23) ? was : {
|
|
9844
|
+
path: path23,
|
|
8902
9845
|
offset: 0,
|
|
8903
9846
|
...was?.stamp ? { stamp: was.stamp } : {}
|
|
8904
9847
|
};
|
|
@@ -8916,15 +9859,15 @@ async function clearLogs(fleet, names, file = storePath(fleet.manifest.path), no
|
|
|
8916
9859
|
const ck = countedClearKey(key);
|
|
8917
9860
|
next.clears[ck] = (s.clears[ck] ?? 0) + 1;
|
|
8918
9861
|
}
|
|
8919
|
-
for (const [stream,
|
|
9862
|
+
for (const [stream, path23] of [
|
|
8920
9863
|
["out", f.proc.out_log],
|
|
8921
9864
|
["err", f.proc.err_log]
|
|
8922
9865
|
]) {
|
|
8923
|
-
if (f.failed.includes(
|
|
8924
|
-
const fk = fileClearKey(key, stream,
|
|
9866
|
+
if (f.failed.includes(path23)) continue;
|
|
9867
|
+
const fk = fileClearKey(key, stream, path23);
|
|
8925
9868
|
next.clears[fk] = (s.clears[fk] ?? 0) + 1;
|
|
8926
9869
|
}
|
|
8927
|
-
const freed = (
|
|
9870
|
+
const freed = (path23, bytes) => f.failed.includes(path23) ? 0 : bytes;
|
|
8928
9871
|
out.push({
|
|
8929
9872
|
app: f.app,
|
|
8930
9873
|
host: g.host,
|
|
@@ -9170,22 +10113,22 @@ async function doctor(m, hostName, spawner, hereCheck) {
|
|
|
9170
10113
|
}
|
|
9171
10114
|
|
|
9172
10115
|
// src/core/init.ts
|
|
9173
|
-
import
|
|
9174
|
-
import
|
|
10116
|
+
import fs17 from "fs";
|
|
10117
|
+
import path21 from "path";
|
|
9175
10118
|
import { parse as parseYaml3 } from "yaml";
|
|
9176
10119
|
|
|
9177
10120
|
// src/core/turbo.ts
|
|
9178
|
-
import
|
|
9179
|
-
import
|
|
10121
|
+
import fs16 from "fs";
|
|
10122
|
+
import path20 from "path";
|
|
9180
10123
|
function readJson2(p) {
|
|
9181
10124
|
try {
|
|
9182
|
-
return JSON.parse(
|
|
10125
|
+
return JSON.parse(fs16.readFileSync(p, "utf8"));
|
|
9183
10126
|
} catch {
|
|
9184
10127
|
return null;
|
|
9185
10128
|
}
|
|
9186
10129
|
}
|
|
9187
10130
|
function readTurbo(root) {
|
|
9188
|
-
const raw = readJson2(
|
|
10131
|
+
const raw = readJson2(path20.join(root, "turbo.json"));
|
|
9189
10132
|
const tasks = raw?.tasks ?? raw?.pipeline;
|
|
9190
10133
|
if (!tasks || typeof tasks !== "object" || Array.isArray(tasks)) return null;
|
|
9191
10134
|
const names = Object.keys(tasks);
|
|
@@ -9206,14 +10149,14 @@ function isDistLib(pkg2) {
|
|
|
9206
10149
|
function loadWorkspace(root, dirs) {
|
|
9207
10150
|
const out = [];
|
|
9208
10151
|
for (const dir of dirs) {
|
|
9209
|
-
const pkg2 = readJson2(
|
|
10152
|
+
const pkg2 = readJson2(path20.join(dir, "package.json"));
|
|
9210
10153
|
if (!pkg2) continue;
|
|
9211
10154
|
const deps2 = /* @__PURE__ */ new Set();
|
|
9212
10155
|
for (const k of ["dependencies", "devDependencies", "peerDependencies"])
|
|
9213
10156
|
for (const d of Object.keys(pkg2[k] ?? {})) deps2.add(d);
|
|
9214
10157
|
out.push({
|
|
9215
|
-
name: String(pkg2.name ??
|
|
9216
|
-
dir:
|
|
10158
|
+
name: String(pkg2.name ?? path20.basename(dir)),
|
|
10159
|
+
dir: path20.relative(root, dir) || ".",
|
|
9217
10160
|
scripts: { ...pkg2.scripts ?? {} },
|
|
9218
10161
|
deps: [...deps2],
|
|
9219
10162
|
distLib: isDistLib(pkg2)
|
|
@@ -9246,11 +10189,11 @@ function turboBin(pm) {
|
|
|
9246
10189
|
function libsFilters(libs, globParents, all = []) {
|
|
9247
10190
|
const folded = new Set(libs.map((l) => l.name));
|
|
9248
10191
|
const shared = new Set(
|
|
9249
|
-
all.filter((p) => p.scripts.build && !folded.has(p.name)).map((p) =>
|
|
10192
|
+
all.filter((p) => p.scripts.build && !folded.has(p.name)).map((p) => path20.dirname(p.dir))
|
|
9250
10193
|
);
|
|
9251
10194
|
const out = [];
|
|
9252
10195
|
for (const l of libs) {
|
|
9253
|
-
const parent =
|
|
10196
|
+
const parent = path20.dirname(l.dir);
|
|
9254
10197
|
const f = globParents.includes(parent) && !shared.has(parent) ? `--filter='./${parent}/*'` : `--filter=${l.name}`;
|
|
9255
10198
|
if (!out.includes(f)) out.push(f);
|
|
9256
10199
|
}
|
|
@@ -9277,9 +10220,9 @@ function sanitizeName(s) {
|
|
|
9277
10220
|
return n || "app";
|
|
9278
10221
|
}
|
|
9279
10222
|
function pmFor(root) {
|
|
9280
|
-
if (
|
|
9281
|
-
if (
|
|
9282
|
-
if (
|
|
10223
|
+
if (fs17.existsSync(path21.join(root, "yarn.lock"))) return "yarn";
|
|
10224
|
+
if (fs17.existsSync(path21.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
10225
|
+
if (fs17.existsSync(path21.join(root, "bun.lock")) || fs17.existsSync(path21.join(root, "bun.lockb")))
|
|
9283
10226
|
return "bun run";
|
|
9284
10227
|
return "npm run";
|
|
9285
10228
|
}
|
|
@@ -9287,12 +10230,12 @@ function walk(dir, maxDepth, depth = 0) {
|
|
|
9287
10230
|
const out = [];
|
|
9288
10231
|
let entries;
|
|
9289
10232
|
try {
|
|
9290
|
-
entries =
|
|
10233
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
9291
10234
|
} catch {
|
|
9292
10235
|
return out;
|
|
9293
10236
|
}
|
|
9294
10237
|
for (const e of entries) {
|
|
9295
|
-
const p =
|
|
10238
|
+
const p = path21.join(dir, e.name);
|
|
9296
10239
|
if (e.isDirectory()) {
|
|
9297
10240
|
if (SKIP_DIRS.has(e.name) || e.name.startsWith(".")) continue;
|
|
9298
10241
|
if (depth < maxDepth) out.push(...walk(p, maxDepth, depth + 1));
|
|
@@ -9373,7 +10316,7 @@ function vitePort(dir, section2, config) {
|
|
|
9373
10316
|
let raw;
|
|
9374
10317
|
for (const f of config ? [config] : VITE_CONFIGS) {
|
|
9375
10318
|
try {
|
|
9376
|
-
raw =
|
|
10319
|
+
raw = fs17.readFileSync(path21.resolve(dir, f), "utf8");
|
|
9377
10320
|
break;
|
|
9378
10321
|
} catch {
|
|
9379
10322
|
}
|
|
@@ -9397,9 +10340,9 @@ function guessPort(cmd, dir) {
|
|
|
9397
10340
|
const m = /(?:\bPORT=|--port[= ]|\s-p\s)(\d{2,5})\b/.exec(cmd);
|
|
9398
10341
|
if (m) return Number(m[1]);
|
|
9399
10342
|
for (const f of [".env", ".env.local", ".env.development"]) {
|
|
9400
|
-
const p =
|
|
9401
|
-
if (!
|
|
9402
|
-
const e = /^\s*PORT=["']?(\d{2,5})/m.exec(
|
|
10343
|
+
const p = path21.join(dir, f);
|
|
10344
|
+
if (!fs17.existsSync(p)) continue;
|
|
10345
|
+
const e = /^\s*PORT=["']?(\d{2,5})/m.exec(fs17.readFileSync(p, "utf8"));
|
|
9403
10346
|
if (e) return Number(e[1]);
|
|
9404
10347
|
}
|
|
9405
10348
|
if (NEXT_RE.test(cmd)) return 3e3;
|
|
@@ -9410,10 +10353,10 @@ function guessPort(cmd, dir) {
|
|
|
9410
10353
|
const p = vitePort(dir, section2, config);
|
|
9411
10354
|
return p === null ? void 0 : p ?? (section2 === "server" ? 5173 : 4173);
|
|
9412
10355
|
}
|
|
9413
|
-
const src =
|
|
9414
|
-
if (
|
|
10356
|
+
const src = path21.join(dir, "src");
|
|
10357
|
+
if (fs17.existsSync(src)) {
|
|
9415
10358
|
for (const f of walk(src, 3).filter((x) => /\.(ts|js|mjs|cjs|tsx|jsx)$/.test(x)).slice(0, 200)) {
|
|
9416
|
-
const text =
|
|
10359
|
+
const text = fs17.readFileSync(f, "utf8");
|
|
9417
10360
|
const l = /\.listen\(\s*(\d{2,5})\b/.exec(text) ?? /\b(?:DEFAULT_)?PORT\b\s*(?::\s*number)?\s*=\s*(\d{2,5})\b/.exec(
|
|
9418
10361
|
text
|
|
9419
10362
|
) ?? /\bPORT\s*\)?\s*(?:\?\?|\|\|)\s*["']?(\d{2,5})\b/.exec(text);
|
|
@@ -9436,10 +10379,10 @@ var notAwaited = (lib) => {
|
|
|
9436
10379
|
return `${app}'s first build is not awaited; uncomment ${app}-build and add { app: ${app}-build, condition: completed } to its depends_on to wait for it`;
|
|
9437
10380
|
};
|
|
9438
10381
|
function packageCandidates(root, pkgDir, pm, turbo) {
|
|
9439
|
-
const pkg2 = readJson2(
|
|
10382
|
+
const pkg2 = readJson2(path21.join(pkgDir, "package.json"));
|
|
9440
10383
|
if (!pkg2?.scripts) return [];
|
|
9441
|
-
const rel =
|
|
9442
|
-
const appName = pkgAppName(String(pkg2.name ??
|
|
10384
|
+
const rel = path21.relative(root, pkgDir) || ".";
|
|
10385
|
+
const appName = pkgAppName(String(pkg2.name ?? path21.basename(pkgDir)));
|
|
9443
10386
|
const prefix = rel === "." ? "" : `${appName}-`;
|
|
9444
10387
|
const file = `${rel === "." ? "" : `${rel}/`}package.json`;
|
|
9445
10388
|
const out = [];
|
|
@@ -9482,21 +10425,21 @@ function expandWorkspaces(root, patterns) {
|
|
|
9482
10425
|
for (const pat of patterns) {
|
|
9483
10426
|
const glob = /^(.*?)\/\*\*?$/.exec(pat);
|
|
9484
10427
|
if (glob) {
|
|
9485
|
-
const base =
|
|
9486
|
-
globParents.push(
|
|
10428
|
+
const base = path21.join(root, glob[1]);
|
|
10429
|
+
globParents.push(path21.relative(root, base));
|
|
9487
10430
|
let names = [];
|
|
9488
10431
|
try {
|
|
9489
|
-
names =
|
|
10432
|
+
names = fs17.readdirSync(base);
|
|
9490
10433
|
} catch {
|
|
9491
10434
|
}
|
|
9492
10435
|
names.sort();
|
|
9493
10436
|
for (const d of names)
|
|
9494
|
-
if (
|
|
9495
|
-
dirs.add(
|
|
10437
|
+
if (fs17.existsSync(path21.join(base, d, "package.json")))
|
|
10438
|
+
dirs.add(path21.join(base, d));
|
|
9496
10439
|
} else if (pat.includes("*")) {
|
|
9497
10440
|
unsupported.push(pat);
|
|
9498
|
-
} else if (
|
|
9499
|
-
dirs.add(
|
|
10441
|
+
} else if (fs17.existsSync(path21.join(root, pat, "package.json"))) {
|
|
10442
|
+
dirs.add(path21.join(root, pat));
|
|
9500
10443
|
}
|
|
9501
10444
|
}
|
|
9502
10445
|
return { dirs: [...dirs], unsupported, globParents };
|
|
@@ -9505,7 +10448,7 @@ function libsBuilders(folded, all, globParents, pm, version) {
|
|
|
9505
10448
|
const bin = turboBin(pm);
|
|
9506
10449
|
const ui = version === 2 ? " --ui=stream" : "";
|
|
9507
10450
|
const filters = libsFilters(folded, globParents, all).join(" ");
|
|
9508
|
-
const parents = [...new Set(folded.map((p) =>
|
|
10451
|
+
const parents = [...new Set(folded.map((p) => path21.dirname(p.dir)))].join(
|
|
9509
10452
|
", "
|
|
9510
10453
|
);
|
|
9511
10454
|
const libs = {
|
|
@@ -9564,12 +10507,12 @@ function discover(root) {
|
|
|
9564
10507
|
const candidates = [];
|
|
9565
10508
|
const notes = [];
|
|
9566
10509
|
const pm = pmFor(root);
|
|
9567
|
-
const pkg2 = readJson2(
|
|
10510
|
+
const pkg2 = readJson2(path21.join(root, "package.json"));
|
|
9568
10511
|
const ws = Array.isArray(pkg2?.workspaces) ? [...pkg2.workspaces] : Array.isArray(pkg2?.workspaces?.packages) ? [...pkg2.workspaces.packages] : [];
|
|
9569
|
-
const pnpmWs =
|
|
9570
|
-
if (
|
|
10512
|
+
const pnpmWs = path21.join(root, "pnpm-workspace.yaml");
|
|
10513
|
+
if (fs17.existsSync(pnpmWs)) {
|
|
9571
10514
|
try {
|
|
9572
|
-
const y = parseYaml3(
|
|
10515
|
+
const y = parseYaml3(fs17.readFileSync(pnpmWs, "utf8"));
|
|
9573
10516
|
if (Array.isArray(y?.packages)) ws.push(...y.packages);
|
|
9574
10517
|
} catch {
|
|
9575
10518
|
notes.push("pnpm-workspace.yaml could not be parsed");
|
|
@@ -9577,8 +10520,8 @@ function discover(root) {
|
|
|
9577
10520
|
}
|
|
9578
10521
|
const expanded = expandWorkspaces(root, ws);
|
|
9579
10522
|
const turbo = readTurbo(root);
|
|
9580
|
-
const turboFile =
|
|
9581
|
-
if (!turbo &&
|
|
10523
|
+
const turboFile = path21.join(root, "turbo.json");
|
|
10524
|
+
if (!turbo && fs17.existsSync(turboFile) && readJson2(turboFile) === null)
|
|
9582
10525
|
notes.push(
|
|
9583
10526
|
"turbo.json could not be parsed (comments are not read); drafted without turbo's tasks"
|
|
9584
10527
|
);
|
|
@@ -9604,7 +10547,7 @@ function discover(root) {
|
|
|
9604
10547
|
}
|
|
9605
10548
|
candidates.push(...packageCandidates(root, root, pm));
|
|
9606
10549
|
for (const dir of expanded.dirs) {
|
|
9607
|
-
const p = liveTask ? pkgs.find((x) => x.dir ===
|
|
10550
|
+
const p = liveTask ? pkgs.find((x) => x.dir === path21.relative(root, dir)) : void 0;
|
|
9608
10551
|
candidates.push(
|
|
9609
10552
|
...packageCandidates(
|
|
9610
10553
|
root,
|
|
@@ -9625,20 +10568,20 @@ function discover(root) {
|
|
|
9625
10568
|
`workspace pattern "${pat}" is not expanded by init; add its packages' scripts by hand`
|
|
9626
10569
|
);
|
|
9627
10570
|
for (const f of walk(root, 4).filter(
|
|
9628
|
-
(p) =>
|
|
10571
|
+
(p) => path21.basename(p) === "project.json"
|
|
9629
10572
|
)) {
|
|
9630
10573
|
const pj = readJson2(f);
|
|
9631
10574
|
if (!pj?.targets) continue;
|
|
9632
|
-
const name2 = String(pj.name ??
|
|
10575
|
+
const name2 = String(pj.name ?? path21.basename(path21.dirname(f)));
|
|
9633
10576
|
for (const t of ["serve", "dev", "build"]) {
|
|
9634
10577
|
if (!pj.targets[t]) continue;
|
|
9635
10578
|
candidates.push({
|
|
9636
10579
|
name: sanitizeName(`${name2}-${t}`),
|
|
9637
|
-
source: `${
|
|
10580
|
+
source: `${path21.relative(root, f)}:${t}`,
|
|
9638
10581
|
run: `npx nx run ${name2}:${t}${t === "build" ? " --watch" : ""}`,
|
|
9639
10582
|
cwd: ".",
|
|
9640
10583
|
kind: t === "build" ? "build" : "server",
|
|
9641
|
-
port: t === "build" ? void 0 : guessPort("",
|
|
10584
|
+
port: t === "build" ? void 0 : guessPort("", path21.dirname(f))
|
|
9642
10585
|
});
|
|
9643
10586
|
}
|
|
9644
10587
|
}
|
|
@@ -9653,7 +10596,7 @@ function discover(root) {
|
|
|
9653
10596
|
kind: guessKind(t, "")
|
|
9654
10597
|
});
|
|
9655
10598
|
}
|
|
9656
|
-
const eco = readJson2(
|
|
10599
|
+
const eco = readJson2(path21.join(root, "ecosystem.json"));
|
|
9657
10600
|
if (Array.isArray(eco?.apps)) {
|
|
9658
10601
|
for (const a of eco.apps) {
|
|
9659
10602
|
if (!a?.name) continue;
|
|
@@ -9674,14 +10617,14 @@ function discover(root) {
|
|
|
9674
10617
|
"ecosystem.config.cjs",
|
|
9675
10618
|
"ecosystem.config.mjs"
|
|
9676
10619
|
]) {
|
|
9677
|
-
if (
|
|
10620
|
+
if (fs17.existsSync(path21.join(root, f)))
|
|
9678
10621
|
notes.push(
|
|
9679
10622
|
`${f} found: JavaScript ecosystem files are not executed by init; port its apps by hand`
|
|
9680
10623
|
);
|
|
9681
10624
|
}
|
|
9682
|
-
const procfile =
|
|
9683
|
-
if (
|
|
9684
|
-
for (const line of
|
|
10625
|
+
const procfile = path21.join(root, "Procfile");
|
|
10626
|
+
if (fs17.existsSync(procfile)) {
|
|
10627
|
+
for (const line of fs17.readFileSync(procfile, "utf8").split("\n")) {
|
|
9685
10628
|
const m = /^\s*([A-Za-z0-9_-]+):\s*(.+)$/.exec(line);
|
|
9686
10629
|
if (m) {
|
|
9687
10630
|
const kind = guessKind(m[1], m[2]);
|
|
@@ -9815,20 +10758,20 @@ function localReadonly(text) {
|
|
|
9815
10758
|
}
|
|
9816
10759
|
}
|
|
9817
10760
|
function initProject(rootIn, opts = {}) {
|
|
9818
|
-
const root =
|
|
9819
|
-
if (!
|
|
10761
|
+
const root = path21.resolve(rootIn);
|
|
10762
|
+
if (!fs17.existsSync(root))
|
|
9820
10763
|
throw new McprocError(
|
|
9821
10764
|
"MANIFEST_NOT_FOUND",
|
|
9822
10765
|
`Project directory does not exist: ${root}`
|
|
9823
10766
|
);
|
|
9824
|
-
const dir =
|
|
9825
|
-
const file =
|
|
9826
|
-
if (
|
|
10767
|
+
const dir = path21.join(root, ".mcproc");
|
|
10768
|
+
const file = path21.join(root, MANIFEST_REL);
|
|
10769
|
+
if (fs17.existsSync(file) && !opts.force) {
|
|
9827
10770
|
throw new McprocError("ALREADY_EXISTS", `${file} already exists`, {
|
|
9828
10771
|
hint: "Overwrite the draft with force (README and .gitignore are kept), or keep it and adopt the running apps' ports into it."
|
|
9829
10772
|
});
|
|
9830
10773
|
}
|
|
9831
|
-
if (
|
|
10774
|
+
if (fs17.existsSync(file) && localReadonly(fs17.readFileSync(file, "utf8"))) {
|
|
9832
10775
|
throw new McprocError(
|
|
9833
10776
|
"READONLY_HOST",
|
|
9834
10777
|
`${file} declares the local host readonly; init does not replace it, not even with force`,
|
|
@@ -9837,20 +10780,20 @@ function initProject(rootIn, opts = {}) {
|
|
|
9837
10780
|
}
|
|
9838
10781
|
);
|
|
9839
10782
|
}
|
|
9840
|
-
|
|
10783
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
9841
10784
|
const created = [];
|
|
9842
|
-
const gi =
|
|
9843
|
-
if (!
|
|
9844
|
-
|
|
10785
|
+
const gi = path21.join(dir, ".gitignore");
|
|
10786
|
+
if (!fs17.existsSync(gi) || fs17.readFileSync(gi, "utf8").trim() !== "*") {
|
|
10787
|
+
fs17.writeFileSync(gi, "*\n");
|
|
9845
10788
|
created.push(gi);
|
|
9846
10789
|
}
|
|
9847
|
-
const readme =
|
|
9848
|
-
if (!
|
|
9849
|
-
|
|
10790
|
+
const readme = path21.join(dir, "README.md");
|
|
10791
|
+
if (!fs17.existsSync(readme)) {
|
|
10792
|
+
fs17.writeFileSync(readme, README_TEMPLATE);
|
|
9850
10793
|
created.push(readme);
|
|
9851
10794
|
}
|
|
9852
10795
|
const d = discover(root);
|
|
9853
|
-
|
|
10796
|
+
fs17.writeFileSync(file, renderDraft(root, defaultProject(root), d));
|
|
9854
10797
|
created.push(file);
|
|
9855
10798
|
const by_source = {};
|
|
9856
10799
|
for (const c of d.candidates) {
|
|
@@ -9872,7 +10815,7 @@ function initProject(rootIn, opts = {}) {
|
|
|
9872
10815
|
}
|
|
9873
10816
|
|
|
9874
10817
|
// src/core/adopt.ts
|
|
9875
|
-
import
|
|
10818
|
+
import fs18 from "fs";
|
|
9876
10819
|
import { isMap, isScalar, isSeq, parse, parseDocument } from "yaml";
|
|
9877
10820
|
var EPHEMERAL_FROM = 32768;
|
|
9878
10821
|
function planAdoption(rows, m) {
|
|
@@ -10155,7 +11098,7 @@ async function adoptPorts(fleet) {
|
|
|
10155
11098
|
hint: "Edit the manifest by hand, or remove hosts.local.readonly first."
|
|
10156
11099
|
}
|
|
10157
11100
|
);
|
|
10158
|
-
const before =
|
|
11101
|
+
const before = fs18.readFileSync(m.path, "utf8");
|
|
10159
11102
|
const current = parseManifest(before, m.root, m.path, loadOverride(m.root));
|
|
10160
11103
|
const { rows, errors } = await listFleet(fleet, fleet.select(["all"]), {
|
|
10161
11104
|
cpu: "none",
|
|
@@ -10185,13 +11128,13 @@ async function adoptPorts(fleet) {
|
|
|
10185
11128
|
}
|
|
10186
11129
|
);
|
|
10187
11130
|
}
|
|
10188
|
-
if (
|
|
11131
|
+
if (fs18.readFileSync(m.path, "utf8") !== before)
|
|
10189
11132
|
throw new McprocError(
|
|
10190
11133
|
"ALREADY_EXISTS",
|
|
10191
11134
|
`${m.path} changed while the ports were being probed; nothing written`,
|
|
10192
11135
|
{ hint: "Run the adoption again." }
|
|
10193
11136
|
);
|
|
10194
|
-
|
|
11137
|
+
fs18.writeFileSync(m.path, splice.text);
|
|
10195
11138
|
return {
|
|
10196
11139
|
path: m.path,
|
|
10197
11140
|
written: true,
|
|
@@ -10648,6 +11591,28 @@ function registerReadTools(server, deps2) {
|
|
|
10648
11591
|
};
|
|
10649
11592
|
})
|
|
10650
11593
|
);
|
|
11594
|
+
server.registerTool(
|
|
11595
|
+
"stacks",
|
|
11596
|
+
{
|
|
11597
|
+
title: "Stacks",
|
|
11598
|
+
description: "Every mcproc stack on this host, not only this project: each checkout any mcproc call has seen or found in the local pm2 daemon, with its root, hosts, apps online/total, errors, memory, CPU and listening ports, plus pm2 processes no manifest knows. Use it to see what else runs here (another checkout holding a port or memory); act on another stack by passing its root as `project` to the other tools.",
|
|
11599
|
+
inputSchema: {
|
|
11600
|
+
all: z4.boolean().optional().describe("also the recorded checkouts with nothing in pm2")
|
|
11601
|
+
},
|
|
11602
|
+
annotations: RO
|
|
11603
|
+
},
|
|
11604
|
+
(args) => runTool(async () => {
|
|
11605
|
+
const deps22 = defaultCollectorDeps(deps2.spawner);
|
|
11606
|
+
if (deps2.hereCheck) deps22.hereCheck = deps2.hereCheck;
|
|
11607
|
+
const v = await new StacksCollector(deps22).collect({
|
|
11608
|
+
all: Boolean(args.all)
|
|
11609
|
+
});
|
|
11610
|
+
return {
|
|
11611
|
+
text: renderStacks(v.answer),
|
|
11612
|
+
data: v.answer
|
|
11613
|
+
};
|
|
11614
|
+
})
|
|
11615
|
+
);
|
|
10651
11616
|
server.registerTool(
|
|
10652
11617
|
"incidents",
|
|
10653
11618
|
{
|
|
@@ -11181,7 +12146,8 @@ function initialState(p) {
|
|
|
11181
12146
|
incidentsCleared: null,
|
|
11182
12147
|
incidentsErrors: [],
|
|
11183
12148
|
incidentsTaken: {},
|
|
11184
|
-
hideTaken: false
|
|
12149
|
+
hideTaken: false,
|
|
12150
|
+
toStacks: p.toStacks ?? false
|
|
11185
12151
|
};
|
|
11186
12152
|
}
|
|
11187
12153
|
function visibleIncidents(s) {
|
|
@@ -11405,181 +12371,64 @@ function keyIntent(key, s, now = Date.now()) {
|
|
|
11405
12371
|
return confirms("clear") ? { kind: "clear", apps } : {
|
|
11406
12372
|
kind: "action",
|
|
11407
12373
|
action: { type: "arm", at: now, what: "clear", apps }
|
|
11408
|
-
};
|
|
11409
|
-
}
|
|
11410
|
-
case "S-r": {
|
|
11411
|
-
const apps = s.rows.map((r) => r.app);
|
|
11412
|
-
if (s.busy || apps.length === 0) return null;
|
|
11413
|
-
return confirms("reload") ? { kind: "reload-all" } : {
|
|
11414
|
-
kind: "action",
|
|
11415
|
-
action: { type: "arm", at: now, what: "reload", apps }
|
|
11416
|
-
};
|
|
11417
|
-
}
|
|
11418
|
-
case "q":
|
|
11419
|
-
case "C-c":
|
|
11420
|
-
case "escape":
|
|
11421
|
-
return { kind: "quit" };
|
|
11422
|
-
default:
|
|
11423
|
-
return null;
|
|
11424
|
-
}
|
|
11425
|
-
}
|
|
11426
|
-
function selectedRow(s) {
|
|
11427
|
-
return s.rows[s.selected] ?? null;
|
|
11428
|
-
}
|
|
11429
|
-
function visibleLogs(s) {
|
|
11430
|
-
const all = s.logs[selectedApp(s) ?? ""] ?? [];
|
|
11431
|
-
const lines = s.stream === "both" ? all : all.filter((l) => l.stream === s.stream);
|
|
11432
|
-
return s.stream === "both" ? sortByStamp(lines) : lines;
|
|
11433
|
-
}
|
|
11434
|
-
function visibleFlat(s) {
|
|
11435
|
-
const hostOf = new Map(s.rows.map((r) => [r.app, r.host]));
|
|
11436
|
-
const entries = Object.entries(s.logs).filter(
|
|
11437
|
-
([, lines]) => lines.length > 0
|
|
11438
|
-
);
|
|
11439
|
-
const hosts = [...new Set(entries.map(([app]) => hostOf.get(app)))];
|
|
11440
|
-
const key = `${s.stream}|${hosts.join(",")}`;
|
|
11441
|
-
if (flatMemo && flatMemo.logs === s.logs && flatMemo.key === key)
|
|
11442
|
-
return flatMemo.out;
|
|
11443
|
-
const all = entries.flatMap(
|
|
11444
|
-
([app, lines]) => lines.map((l) => flatLineOf(app, l))
|
|
11445
|
-
);
|
|
11446
|
-
const shown = s.stream === "both" ? all : all.filter((l) => l.stream === s.stream);
|
|
11447
|
-
const byArrival = shown.sort((a, b) => a.seq - b.seq);
|
|
11448
|
-
const out = hosts.length <= 1 ? sortByStamp(byArrival) : byArrival;
|
|
11449
|
-
flatMemo = { logs: s.logs, key, out };
|
|
11450
|
-
return out;
|
|
11451
|
-
}
|
|
11452
|
-
var flatLines = /* @__PURE__ */ new WeakMap();
|
|
11453
|
-
function flatLineOf(app, l) {
|
|
11454
|
-
let f = flatLines.get(l);
|
|
11455
|
-
if (!f) {
|
|
11456
|
-
f = { app, stream: l.stream, text: l.text, seq: l.seq };
|
|
11457
|
-
flatLines.set(l, f);
|
|
11458
|
-
}
|
|
11459
|
-
return f;
|
|
11460
|
-
}
|
|
11461
|
-
var flatMemo = null;
|
|
11462
|
-
|
|
11463
|
-
// src/monit/screentext.ts
|
|
11464
|
-
import { charWidth, chars } from "@pm2/blessed/lib/unicode.js";
|
|
11465
|
-
var segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
11466
|
-
var EMOJI_PRESENTATION = new RegExp("\\p{Emoji_Presentation}", "u");
|
|
11467
|
-
var PICTOGRAPH = new RegExp("\\p{Extended_Pictographic}", "u");
|
|
11468
|
-
var REGIONAL = new RegExp("\\p{Regional_Indicator}", "u");
|
|
11469
|
-
var KEYCAP = /^[0-9#*]\uFE0F?\u20E3$/;
|
|
11470
|
-
var INVISIBLE = /[\p{Cc}\p{Cf}]/u;
|
|
11471
|
-
var LINE_BREAK = /\r\n|[\n\v\f\u0085\u2028\u2029]/g;
|
|
11472
|
-
var MARK2 = new RegExp("\\p{M}", "u");
|
|
11473
|
-
var SPACING_MARK = new RegExp("\\p{Mc}", "u");
|
|
11474
|
-
var BLESSED_WIDE = new RegExp(chars.wide.source);
|
|
11475
|
-
var NARROW = {
|
|
11476
|
-
"\u2705": "\u2714",
|
|
11477
|
-
"\u274C": "\u2716",
|
|
11478
|
-
"\u274E": "\u2716",
|
|
11479
|
-
"\u2757": "!",
|
|
11480
|
-
"\u2755": "!",
|
|
11481
|
-
"\u2753": "?",
|
|
11482
|
-
"\u2754": "?"
|
|
11483
|
-
};
|
|
11484
|
-
var TAB = 8;
|
|
11485
|
-
function displayWidth(s) {
|
|
11486
|
-
let w = 0;
|
|
11487
|
-
for (const ch of s.includes("\x1B") ? s.replace(SGR_RE, "") : s)
|
|
11488
|
-
w += charWidth(ch.codePointAt(0)) === 2 ? 2 : 1;
|
|
11489
|
-
return w;
|
|
11490
|
-
}
|
|
11491
|
-
function columns(s) {
|
|
11492
|
-
let w = 0;
|
|
11493
|
-
for (const ch of s) w += charWidth(ch.codePointAt(0));
|
|
11494
|
-
return w;
|
|
11495
|
-
}
|
|
11496
|
-
function keepMark(ch, cp) {
|
|
11497
|
-
return cp <= 65535 && charWidth(cp) === (SPACING_MARK.test(ch) ? 1 : 0);
|
|
11498
|
-
}
|
|
11499
|
-
function cluster(c) {
|
|
11500
|
-
if (c === " ") return c;
|
|
11501
|
-
const first = String.fromCodePoint(c.codePointAt(0));
|
|
11502
|
-
if (REGIONAL.test(first))
|
|
11503
|
-
return [...c].filter((ch) => REGIONAL.test(ch)).map((ch) => String.fromCharCode(ch.codePointAt(0) - 127462 + 65)).join("");
|
|
11504
|
-
if (KEYCAP.test(c)) return c[0];
|
|
11505
|
-
if (PICTOGRAPH.test(first) || EMOJI_PRESENTATION.test(first)) {
|
|
11506
|
-
if (NARROW[first]) return NARROW[first];
|
|
11507
|
-
return !EMOJI_PRESENTATION.test(first) && first.length === 1 ? first : "?";
|
|
11508
|
-
}
|
|
11509
|
-
let out = "";
|
|
11510
|
-
for (const ch of c) {
|
|
11511
|
-
if (INVISIBLE.test(ch)) continue;
|
|
11512
|
-
const cp = ch.codePointAt(0);
|
|
11513
|
-
if (MARK2.test(ch)) {
|
|
11514
|
-
if (keepMark(ch, cp)) out += ch;
|
|
11515
|
-
continue;
|
|
11516
|
-
}
|
|
11517
|
-
if (cp > 65535 || cp >= 55296 && cp <= 57343) {
|
|
11518
|
-
out += "?";
|
|
11519
|
-
continue;
|
|
11520
|
-
}
|
|
11521
|
-
out += charWidth(cp) === 2 === BLESSED_WIDE.test(ch) ? ch : "?";
|
|
11522
|
-
}
|
|
11523
|
-
return out;
|
|
11524
|
-
}
|
|
11525
|
-
function blessedSgr(code) {
|
|
11526
|
-
if (!code.includes(":")) return code;
|
|
11527
|
-
const groups = code.slice(2, -1).split(";").flatMap((g) => {
|
|
11528
|
-
if (!g.includes(":")) return [g];
|
|
11529
|
-
const [kind, mode, ...rest] = g.split(":");
|
|
11530
|
-
if (kind === "4") return ["4"];
|
|
11531
|
-
if (!["38", "48", "58"].includes(kind)) return [];
|
|
11532
|
-
if (mode === "5" && rest[0]) return [`${kind};5;${rest[0]}`];
|
|
11533
|
-
if (mode === "2" && rest.length >= 3)
|
|
11534
|
-
return [`${kind};2;${rest.slice(-3).join(";")}`];
|
|
11535
|
-
return [];
|
|
11536
|
-
});
|
|
11537
|
-
return groups.length ? `\x1B[${groups.join(";")}m` : "";
|
|
11538
|
-
}
|
|
11539
|
-
var SGR_SPLIT = /(\x1b\[[0-9;:]*m)/;
|
|
11540
|
-
var visible = (t) => t.replace(SGR_RE, "");
|
|
11541
|
-
function screenText(input, opts = {}) {
|
|
11542
|
-
const parts = (opts.colors ? keepSgr(input) : stripAnsi(input)).replace(/\r\n/g, "\n").split("\r");
|
|
11543
|
-
let last = parts.length - 1;
|
|
11544
|
-
while (last > 0 && visible(parts[last]) === "") last--;
|
|
11545
|
-
const carried = parts.slice(0, last).flatMap((p) => p.match(SGR_RE) ?? []).join("");
|
|
11546
|
-
let out = "";
|
|
11547
|
-
let col = 0;
|
|
11548
|
-
for (const piece of (carried + parts[last]).split(SGR_SPLIT)) {
|
|
11549
|
-
if (SGR_SPLIT.test(piece) && piece.startsWith("\x1B")) {
|
|
11550
|
-
out += blessedSgr(piece);
|
|
11551
|
-
continue;
|
|
11552
|
-
}
|
|
11553
|
-
for (const { segment } of segmenter.segment(
|
|
11554
|
-
piece.replace(LINE_BREAK, " ").normalize("NFC")
|
|
11555
|
-
)) {
|
|
11556
|
-
const c = cluster(segment);
|
|
11557
|
-
if (c === " ") {
|
|
11558
|
-
const n = TAB - col % TAB;
|
|
11559
|
-
out += " ".repeat(n);
|
|
11560
|
-
col += n;
|
|
11561
|
-
continue;
|
|
11562
|
-
}
|
|
11563
|
-
out += c;
|
|
11564
|
-
col += columns(c);
|
|
12374
|
+
};
|
|
12375
|
+
}
|
|
12376
|
+
case "S-r": {
|
|
12377
|
+
const apps = s.rows.map((r) => r.app);
|
|
12378
|
+
if (s.busy || apps.length === 0) return null;
|
|
12379
|
+
return confirms("reload") ? { kind: "reload-all" } : {
|
|
12380
|
+
kind: "action",
|
|
12381
|
+
action: { type: "arm", at: now, what: "reload", apps }
|
|
12382
|
+
};
|
|
11565
12383
|
}
|
|
12384
|
+
case "q":
|
|
12385
|
+
case "C-c":
|
|
12386
|
+
return { kind: "quit" };
|
|
12387
|
+
case "escape":
|
|
12388
|
+
return s.toStacks ? { kind: "back" } : { kind: "quit" };
|
|
12389
|
+
default:
|
|
12390
|
+
return null;
|
|
11566
12391
|
}
|
|
12392
|
+
}
|
|
12393
|
+
function selectedRow(s) {
|
|
12394
|
+
return s.rows[s.selected] ?? null;
|
|
12395
|
+
}
|
|
12396
|
+
function visibleLogs(s) {
|
|
12397
|
+
const all = s.logs[selectedApp(s) ?? ""] ?? [];
|
|
12398
|
+
const lines = s.stream === "both" ? all : all.filter((l) => l.stream === s.stream);
|
|
12399
|
+
return s.stream === "both" ? sortByStamp(lines) : lines;
|
|
12400
|
+
}
|
|
12401
|
+
function visibleFlat(s) {
|
|
12402
|
+
const hostOf = new Map(s.rows.map((r) => [r.app, r.host]));
|
|
12403
|
+
const entries = Object.entries(s.logs).filter(
|
|
12404
|
+
([, lines]) => lines.length > 0
|
|
12405
|
+
);
|
|
12406
|
+
const hosts = [...new Set(entries.map(([app]) => hostOf.get(app)))];
|
|
12407
|
+
const key = `${s.stream}|${hosts.join(",")}`;
|
|
12408
|
+
if (flatMemo && flatMemo.logs === s.logs && flatMemo.key === key)
|
|
12409
|
+
return flatMemo.out;
|
|
12410
|
+
const all = entries.flatMap(
|
|
12411
|
+
([app, lines]) => lines.map((l) => flatLineOf(app, l))
|
|
12412
|
+
);
|
|
12413
|
+
const shown = s.stream === "both" ? all : all.filter((l) => l.stream === s.stream);
|
|
12414
|
+
const byArrival = shown.sort((a, b) => a.seq - b.seq);
|
|
12415
|
+
const out = hosts.length <= 1 ? sortByStamp(byArrival) : byArrival;
|
|
12416
|
+
flatMemo = { logs: s.logs, key, out };
|
|
11567
12417
|
return out;
|
|
11568
12418
|
}
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
let
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
if (w + cw > width - 1) break;
|
|
11576
|
-
out += ch;
|
|
11577
|
-
w += cw;
|
|
12419
|
+
var flatLines = /* @__PURE__ */ new WeakMap();
|
|
12420
|
+
function flatLineOf(app, l) {
|
|
12421
|
+
let f = flatLines.get(l);
|
|
12422
|
+
if (!f) {
|
|
12423
|
+
f = { app, stream: l.stream, text: l.text, seq: l.seq };
|
|
12424
|
+
flatLines.set(l, f);
|
|
11578
12425
|
}
|
|
11579
|
-
return
|
|
12426
|
+
return f;
|
|
11580
12427
|
}
|
|
12428
|
+
var flatMemo = null;
|
|
11581
12429
|
|
|
11582
12430
|
// src/monit/frame.ts
|
|
12431
|
+
var stacksKey = (s) => s.toStacks ? "esc stacks " : "";
|
|
11583
12432
|
function pane(entries, scroll, shift, logH, rowsOf) {
|
|
11584
12433
|
let filled = 0;
|
|
11585
12434
|
let k = 0;
|
|
@@ -11814,7 +12663,7 @@ function renderFrame(s, size) {
|
|
|
11814
12663
|
spec2.rowsOf
|
|
11815
12664
|
);
|
|
11816
12665
|
const empty = !s.incidentsPolled ? "reading incidents\u2026" : s.incidents.length ? "every incident here is taken (t shows them)" : `no incidents in the last ${DEFAULT_SINCE_MIN} min`;
|
|
11817
|
-
const keys2 =
|
|
12666
|
+
const keys2 = `${stacksKey(s)}\u2191\u2193 scroll i back f flat t ${s.hideTaken ? "show" : "hide"} taken c clear R reload all h host q quit`;
|
|
11818
12667
|
return {
|
|
11819
12668
|
header,
|
|
11820
12669
|
table: [],
|
|
@@ -11837,7 +12686,7 @@ function renderFrame(s, size) {
|
|
|
11837
12686
|
spec2.logH,
|
|
11838
12687
|
spec2.rowsOf
|
|
11839
12688
|
);
|
|
11840
|
-
const keys2 =
|
|
12689
|
+
const keys2 = `${stacksKey(s)}\u2191\u2193 scroll c clear R reload all f dashboard o/e/b logs i incidents h host q quit`;
|
|
11841
12690
|
return {
|
|
11842
12691
|
header,
|
|
11843
12692
|
table: [],
|
|
@@ -11849,32 +12698,37 @@ function renderFrame(s, size) {
|
|
|
11849
12698
|
};
|
|
11850
12699
|
}
|
|
11851
12700
|
const multi = new Set(s.rows.map((r2) => r2.host)).size > 1;
|
|
11852
|
-
const
|
|
12701
|
+
const o = multi ? 1 : 0;
|
|
12702
|
+
const tableLines = table(
|
|
11853
12703
|
[
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
12704
|
+
[
|
|
12705
|
+
"",
|
|
12706
|
+
"APP",
|
|
12707
|
+
...multi ? ["HOST"] : [],
|
|
12708
|
+
"STATUS",
|
|
12709
|
+
"UP",
|
|
12710
|
+
"MEM",
|
|
12711
|
+
"CPU",
|
|
12712
|
+
"PORTS",
|
|
12713
|
+
"URL",
|
|
12714
|
+
"RUNTIME"
|
|
12715
|
+
],
|
|
12716
|
+
...s.rows.map((r2, i) => [
|
|
12717
|
+
i === s.selected ? "\u25B6" : " ",
|
|
12718
|
+
r2.app,
|
|
12719
|
+
...multi ? [r2.host] : [],
|
|
12720
|
+
r2.status,
|
|
12721
|
+
fmtUptime(r2.uptime_ms),
|
|
12722
|
+
r2.memory ? fmtBytes(r2.memory) : "-",
|
|
12723
|
+
r2.status === "online" ? fmtCpu(r2.cpu) : "-",
|
|
12724
|
+
screenText(fmtPorts(r2.ports)),
|
|
12725
|
+
screenText(fmtUrl(r2)),
|
|
12726
|
+
r2.runtime ? screenText(r2.runtime) : "-"
|
|
12727
|
+
])
|
|
11864
12728
|
],
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
...multi ? [r2.host] : [],
|
|
11869
|
-
r2.status,
|
|
11870
|
-
fmtUptime(r2.uptime_ms),
|
|
11871
|
-
r2.memory ? fmtBytes(r2.memory) : "-",
|
|
11872
|
-
r2.status === "online" ? fmtCpu(r2.cpu) : "-",
|
|
11873
|
-
cutWidth(screenText(fmtPorts(r2.ports)), 22),
|
|
11874
|
-
cutWidth(screenText(fmtUrl(r2)), 36),
|
|
11875
|
-
r2.runtime ? cutWidth(screenText(r2.runtime), 40) : "-"
|
|
11876
|
-
])
|
|
11877
|
-
]).split("\n").map((l) => cutWidth(l.replace(/^(.)\s\s/, "$1"), w));
|
|
12729
|
+
// +2: the marker column's gutter is dropped below, so the table may use those two columns
|
|
12730
|
+
{ width: w + 2, flex: [6 + o, 7 + o, 8 + o], mode: "fit" }
|
|
12731
|
+
).split("\n").map((l) => cutWidth(l.replace(/^(.)\s\s/, "$1"), w));
|
|
11878
12732
|
const spec = paneSpec(s, size);
|
|
11879
12733
|
const p = pane(
|
|
11880
12734
|
spec.entries,
|
|
@@ -11906,7 +12760,7 @@ function renderFrame(s, size) {
|
|
|
11906
12760
|
w
|
|
11907
12761
|
)
|
|
11908
12762
|
] : ["no app selected"];
|
|
11909
|
-
const keys =
|
|
12763
|
+
const keys = `${stacksKey(s)}j/k app \u2191\u2193 scroll r restart R reload all s start x stop c clear o/e/b logs f flat i incidents h host q quit`;
|
|
11910
12764
|
const footer = cutWidth(right ? `${right} \u2502 ${keys}` : keys, w);
|
|
11911
12765
|
return {
|
|
11912
12766
|
header,
|
|
@@ -12011,31 +12865,31 @@ async function monitOnce(opts, deps2 = {}) {
|
|
|
12011
12865
|
}
|
|
12012
12866
|
|
|
12013
12867
|
// src/cli/install.ts
|
|
12014
|
-
import
|
|
12015
|
-
import
|
|
12016
|
-
import
|
|
12868
|
+
import fs19 from "fs";
|
|
12869
|
+
import os9 from "os";
|
|
12870
|
+
import path22 from "path";
|
|
12017
12871
|
var LAUNCHER_MARK = "# written by mcproc install";
|
|
12018
12872
|
function rcFor(home, shell) {
|
|
12019
|
-
const name2 =
|
|
12020
|
-
return
|
|
12873
|
+
const name2 = path22.basename(shell || "");
|
|
12874
|
+
return path22.join(
|
|
12021
12875
|
home,
|
|
12022
12876
|
name2 === "zsh" ? ".zshrc" : name2 === "bash" ? ".bashrc" : ".profile"
|
|
12023
12877
|
);
|
|
12024
12878
|
}
|
|
12025
12879
|
function installShortcut(o = {}) {
|
|
12026
|
-
const home = o.home ??
|
|
12027
|
-
const binDir =
|
|
12028
|
-
const cli =
|
|
12029
|
-
o.cliPath ??
|
|
12880
|
+
const home = o.home ?? os9.homedir();
|
|
12881
|
+
const binDir = path22.resolve(o.binDir ?? path22.join(home, ".local", "bin"));
|
|
12882
|
+
const cli = path22.resolve(
|
|
12883
|
+
o.cliPath ?? path22.join(findPackageRoot(), "dist", "cli.js")
|
|
12030
12884
|
);
|
|
12031
|
-
const launcher =
|
|
12885
|
+
const launcher = path22.join(binDir, "mcproc");
|
|
12032
12886
|
const pathEnv = o.pathEnv ?? process.env.PATH ?? "";
|
|
12033
|
-
const onPath = pathEnv.split(":").some((p) => p &&
|
|
12887
|
+
const onPath = pathEnv.split(":").some((p) => p && path22.resolve(p.replace(/^~(?=\/|$)/, home)) === binDir);
|
|
12034
12888
|
const next = [];
|
|
12035
12889
|
if (o.uninstall) {
|
|
12036
|
-
const exists =
|
|
12037
|
-
const ours = exists &&
|
|
12038
|
-
if (ours)
|
|
12890
|
+
const exists = fs19.existsSync(launcher);
|
|
12891
|
+
const ours = exists && fs19.readFileSync(launcher, "utf8").includes(LAUNCHER_MARK);
|
|
12892
|
+
if (ours) fs19.unlinkSync(launcher);
|
|
12039
12893
|
else if (exists)
|
|
12040
12894
|
next.push(`${launcher} was not written by mcproc install; left in place`);
|
|
12041
12895
|
else next.push(`nothing at ${launcher}`);
|
|
@@ -12051,43 +12905,43 @@ function installShortcut(o = {}) {
|
|
|
12051
12905
|
next
|
|
12052
12906
|
};
|
|
12053
12907
|
}
|
|
12054
|
-
|
|
12908
|
+
fs19.mkdirSync(binDir, { recursive: true });
|
|
12055
12909
|
let existing = null;
|
|
12056
12910
|
try {
|
|
12057
|
-
existing =
|
|
12911
|
+
existing = fs19.lstatSync(launcher);
|
|
12058
12912
|
} catch {
|
|
12059
12913
|
}
|
|
12060
12914
|
if (existing?.isSymbolicLink()) {
|
|
12061
12915
|
throw new McprocError(
|
|
12062
12916
|
"BAD_ARGUMENT",
|
|
12063
|
-
`${launcher} is a symlink (to ${
|
|
12917
|
+
`${launcher} is a symlink (to ${fs19.readlinkSync(launcher)}); refusing to write through it`,
|
|
12064
12918
|
{ hint: "Remove it yourself, or pass another --bin-dir." }
|
|
12065
12919
|
);
|
|
12066
12920
|
}
|
|
12067
|
-
if (existing && !
|
|
12921
|
+
if (existing && !fs19.readFileSync(launcher, "utf8").includes(LAUNCHER_MARK)) {
|
|
12068
12922
|
throw new McprocError(
|
|
12069
12923
|
"BAD_ARGUMENT",
|
|
12070
12924
|
`${launcher} exists and was not written by mcproc install; refusing to overwrite it`,
|
|
12071
12925
|
{ hint: "Remove it yourself, or pass another --bin-dir." }
|
|
12072
12926
|
);
|
|
12073
12927
|
}
|
|
12074
|
-
|
|
12928
|
+
fs19.writeFileSync(
|
|
12075
12929
|
launcher,
|
|
12076
12930
|
`#!/bin/sh
|
|
12077
|
-
${LAUNCHER_MARK}: runs the mcproc at ${
|
|
12931
|
+
${LAUNCHER_MARK}: runs the mcproc at ${path22.dirname(path22.dirname(cli))}
|
|
12078
12932
|
exec node ${shq(cli)} "$@"
|
|
12079
12933
|
`
|
|
12080
12934
|
);
|
|
12081
|
-
|
|
12935
|
+
fs19.chmodSync(launcher, 493);
|
|
12082
12936
|
let rcFile = null;
|
|
12083
12937
|
let rcUpdated = false;
|
|
12084
12938
|
if (!onPath) {
|
|
12085
12939
|
rcFile = rcFor(home, o.shell ?? process.env.SHELL ?? "");
|
|
12086
|
-
const shown = binDir.startsWith(home +
|
|
12940
|
+
const shown = binDir.startsWith(home + path22.sep) ? `$HOME/${path22.relative(home, binDir)}` : binDir;
|
|
12087
12941
|
const line = `export PATH="${shown}:$PATH"`;
|
|
12088
|
-
const current =
|
|
12942
|
+
const current = fs19.existsSync(rcFile) ? fs19.readFileSync(rcFile, "utf8") : "";
|
|
12089
12943
|
if (!current.includes(line)) {
|
|
12090
|
-
|
|
12944
|
+
fs19.appendFileSync(
|
|
12091
12945
|
rcFile,
|
|
12092
12946
|
`${current === "" || current.endsWith("\n") ? "" : "\n"}
|
|
12093
12947
|
# mcproc install: the shortcut lives in ${shown}
|
|
@@ -12195,6 +13049,7 @@ var MonitLoop = class {
|
|
|
12195
13049
|
inc = freshView();
|
|
12196
13050
|
/** One refresh of every host of the view (or of `hosts`); a host whose refresh of this generation is still in flight shares it. */
|
|
12197
13051
|
refresh(hosts) {
|
|
13052
|
+
if (this.closed) return Promise.resolve();
|
|
12198
13053
|
this.readCleared();
|
|
12199
13054
|
this.syncWatches();
|
|
12200
13055
|
const gen = this.gen;
|
|
@@ -12304,6 +13159,7 @@ var MonitLoop = class {
|
|
|
12304
13159
|
const probed = this.losses;
|
|
12305
13160
|
try {
|
|
12306
13161
|
const ctx = await this.fleet.ctx(g.host);
|
|
13162
|
+
if (gen !== this.gen) return;
|
|
12307
13163
|
const snap = await this.deps.snapshot(ctx, {
|
|
12308
13164
|
cpu: "none",
|
|
12309
13165
|
prev: this.prev.get(g.host),
|
|
@@ -12513,6 +13369,8 @@ var MonitLoop = class {
|
|
|
12513
13369
|
/** The screen is going away: no follower wakes it any more, and a running reload stops waiting for dependencies. */
|
|
12514
13370
|
close() {
|
|
12515
13371
|
this.closed = true;
|
|
13372
|
+
this.gen++;
|
|
13373
|
+
this.switchSeq++;
|
|
12516
13374
|
this.reloading?.abort();
|
|
12517
13375
|
for (const q of this.wakes.values()) {
|
|
12518
13376
|
if (q.timer) clearTimeout(q.timer);
|
|
@@ -12666,10 +13524,10 @@ var MonitLoop = class {
|
|
|
12666
13524
|
this.names.map((a) => markKey(this.fleet.hostOf(a), a))
|
|
12667
13525
|
);
|
|
12668
13526
|
const st = this.inc;
|
|
12669
|
-
const add2 = (key, stream,
|
|
13527
|
+
const add2 = (key, stream, path23) => {
|
|
12670
13528
|
const by = st.pending[key] ?? {};
|
|
12671
13529
|
const paths = by[stream] ?? /* @__PURE__ */ new Set();
|
|
12672
|
-
paths.add(
|
|
13530
|
+
paths.add(path23);
|
|
12673
13531
|
by[stream] = paths;
|
|
12674
13532
|
st.pending[key] = by;
|
|
12675
13533
|
};
|
|
@@ -12812,9 +13670,11 @@ async function runMonit(opts) {
|
|
|
12812
13670
|
project: manifest.project,
|
|
12813
13671
|
host: viewLabel(fleet.requested),
|
|
12814
13672
|
hosts: views.map(viewLabel),
|
|
12815
|
-
apps: appInfos(manifest)
|
|
13673
|
+
apps: appInfos(manifest),
|
|
13674
|
+
toStacks: Boolean(opts.back)
|
|
12816
13675
|
});
|
|
12817
|
-
const
|
|
13676
|
+
const own2 = !opts.screen;
|
|
13677
|
+
const screen = opts.screen ?? blessed.screen({
|
|
12818
13678
|
smartCSR: true,
|
|
12819
13679
|
title: "mcproc monit",
|
|
12820
13680
|
fullUnicode: true
|
|
@@ -12833,7 +13693,7 @@ async function runMonit(opts) {
|
|
|
12833
13693
|
const detailsTitle = box({ bottom: 7, height: 1 });
|
|
12834
13694
|
const detailsBox = box({ bottom: 1, height: 6 });
|
|
12835
13695
|
const footer = box({ bottom: 0, height: 1 });
|
|
12836
|
-
|
|
13696
|
+
const boxes = [
|
|
12837
13697
|
header,
|
|
12838
13698
|
appsTitle,
|
|
12839
13699
|
tableBox,
|
|
@@ -12842,8 +13702,8 @@ async function runMonit(opts) {
|
|
|
12842
13702
|
detailsTitle,
|
|
12843
13703
|
detailsBox,
|
|
12844
13704
|
footer
|
|
12845
|
-
]
|
|
12846
|
-
|
|
13705
|
+
];
|
|
13706
|
+
for (const b of boxes) screen.append(b);
|
|
12847
13707
|
const appColor = new Map(
|
|
12848
13708
|
Object.keys(manifest.apps).map((a, i) => [a, colorFor(i)])
|
|
12849
13709
|
);
|
|
@@ -12856,7 +13716,9 @@ async function runMonit(opts) {
|
|
|
12856
13716
|
width: screen.width,
|
|
12857
13717
|
height: screen.height
|
|
12858
13718
|
});
|
|
13719
|
+
let left = false;
|
|
12859
13720
|
const show = (f) => {
|
|
13721
|
+
if (left) return;
|
|
12860
13722
|
const { width: w, height: h } = size();
|
|
12861
13723
|
header.setContent(f.header);
|
|
12862
13724
|
footer.setContent(f.footer);
|
|
@@ -12905,44 +13767,511 @@ async function runMonit(opts) {
|
|
|
12905
13767
|
const timer = setInterval(() => {
|
|
12906
13768
|
void loop.refresh();
|
|
12907
13769
|
}, opts.intervalMs);
|
|
12908
|
-
const
|
|
13770
|
+
const leave = (why) => {
|
|
13771
|
+
left = true;
|
|
12909
13772
|
clearInterval(timer);
|
|
12910
13773
|
loop.close();
|
|
13774
|
+
screen.removeListener("keypress", onKey);
|
|
13775
|
+
screen.removeListener("resize", paint);
|
|
13776
|
+
if (own2) screen.destroy();
|
|
13777
|
+
else for (const b of boxes) b.detach();
|
|
13778
|
+
resolve(why);
|
|
13779
|
+
};
|
|
13780
|
+
const onKey = (_ch, key) => {
|
|
13781
|
+
const intent = keyIntent(key.full ?? key.name ?? "", state);
|
|
13782
|
+
if (state.armedAt !== null && !(intent?.kind === "action" && intent.action.type === "arm"))
|
|
13783
|
+
dispatch({ type: "arm", at: null });
|
|
13784
|
+
if (!intent) return;
|
|
13785
|
+
if (intent.kind === "quit") return leave("quit");
|
|
13786
|
+
if (intent.kind === "back") return leave("back");
|
|
13787
|
+
if (intent.kind === "action") {
|
|
13788
|
+
if (intent.action.type === "arm" && intent.action.at !== null) {
|
|
13789
|
+
const { at, what } = intent.action;
|
|
13790
|
+
setTimeout(() => {
|
|
13791
|
+
if (state.armedAt === at && state.armed === what)
|
|
13792
|
+
dispatch({ type: "arm", at: null });
|
|
13793
|
+
}, CONFIRM_MS + 50).unref();
|
|
13794
|
+
}
|
|
13795
|
+
if (intent.action.type === "select")
|
|
13796
|
+
loop.dispatchSelect(intent.action.delta);
|
|
13797
|
+
else {
|
|
13798
|
+
dispatch(intent.action);
|
|
13799
|
+
if (intent.action.type === "view") void loop.refresh();
|
|
13800
|
+
}
|
|
13801
|
+
return;
|
|
13802
|
+
}
|
|
13803
|
+
if (intent.kind === "host-next") return void loop.nextView();
|
|
13804
|
+
if (intent.kind === "clear") return void loop.runClear(intent.apps);
|
|
13805
|
+
if (intent.kind === "reload-all") return void loop.reloadAll();
|
|
13806
|
+
void loop.runCommand(intent.cmd, intent.app);
|
|
13807
|
+
};
|
|
13808
|
+
screen.on("keypress", onKey);
|
|
13809
|
+
screen.on("resize", paint);
|
|
13810
|
+
void loop.refresh().then(paint);
|
|
13811
|
+
});
|
|
13812
|
+
}
|
|
13813
|
+
|
|
13814
|
+
// src/monit/stacks-tui.ts
|
|
13815
|
+
import blessed2 from "@pm2/blessed";
|
|
13816
|
+
|
|
13817
|
+
// src/core/stacks/stop.ts
|
|
13818
|
+
async function stopStack(m, connector = defaultConnector()) {
|
|
13819
|
+
const out = { stopped: [], readonly: [], failed: [] };
|
|
13820
|
+
try {
|
|
13821
|
+
const fleet = new Fleet(m, void 0, connector);
|
|
13822
|
+
const ro = /* @__PURE__ */ new Map();
|
|
13823
|
+
const names = fleet.select(["all"]).filter((a) => {
|
|
13824
|
+
const h = fleet.hostOf(a);
|
|
13825
|
+
if (!pickHost(m, h).readonly) return true;
|
|
13826
|
+
ro.set(h, [...ro.get(h) ?? [], a]);
|
|
13827
|
+
return false;
|
|
13828
|
+
});
|
|
13829
|
+
out.readonly = [...ro].map(([host, apps]) => ({ host, apps }));
|
|
13830
|
+
const mine = await ownApps(m, fleet, names, out);
|
|
13831
|
+
if (mine.length === 0) return out;
|
|
13832
|
+
for (const r of await actOn(fleet, mine, "stop"))
|
|
13833
|
+
if (r.error)
|
|
13834
|
+
out.failed.push({ host: r.host, message: `${r.app}: ${r.error}` });
|
|
13835
|
+
else out.stopped.push(r.app);
|
|
13836
|
+
} catch (e) {
|
|
13837
|
+
out.failed.push({ host: "-", message: describeError(e) });
|
|
13838
|
+
}
|
|
13839
|
+
return out;
|
|
13840
|
+
}
|
|
13841
|
+
async function ownApps(m, fleet, names, out) {
|
|
13842
|
+
const keep = [];
|
|
13843
|
+
for (const g of fleet.groups(names)) {
|
|
13844
|
+
let procs;
|
|
13845
|
+
let host;
|
|
13846
|
+
try {
|
|
13847
|
+
const ctx = await fleet.ctx(g.host);
|
|
13848
|
+
host = ctx.host;
|
|
13849
|
+
procs = await ctx.pm2.jlist();
|
|
13850
|
+
} catch (e) {
|
|
13851
|
+
for (const a of g.apps)
|
|
13852
|
+
out.failed.push({ host: g.host, message: `${a}: ${describeError(e)}` });
|
|
13853
|
+
continue;
|
|
13854
|
+
}
|
|
13855
|
+
for (const a of g.apps) {
|
|
13856
|
+
const p = findProcess(procs, m.project, a);
|
|
13857
|
+
const own2 = appCwd(host, m.apps[a]);
|
|
13858
|
+
if (p?.cwd && p.cwd !== own2)
|
|
13859
|
+
out.failed.push({
|
|
13860
|
+
host: g.host,
|
|
13861
|
+
message: `${a}: runs from another checkout (${p.cwd}), left alone`
|
|
13862
|
+
});
|
|
13863
|
+
else keep.push(a);
|
|
13864
|
+
}
|
|
13865
|
+
}
|
|
13866
|
+
return names.filter((a) => keep.includes(a));
|
|
13867
|
+
}
|
|
13868
|
+
|
|
13869
|
+
// src/monit/stacks-state.ts
|
|
13870
|
+
var orphanKey = (o) => `orphan:${o.endpoint}:${o.namespace}`;
|
|
13871
|
+
function overviewRows(s) {
|
|
13872
|
+
if (!s.view) return [];
|
|
13873
|
+
return [
|
|
13874
|
+
...s.view.stacks.map((stack) => ({
|
|
13875
|
+
kind: "stack",
|
|
13876
|
+
key: stack.root,
|
|
13877
|
+
stack
|
|
13878
|
+
})),
|
|
13879
|
+
...s.view.answer.orphans.map((orphan) => ({
|
|
13880
|
+
kind: "orphan",
|
|
13881
|
+
key: orphanKey(orphan),
|
|
13882
|
+
orphan
|
|
13883
|
+
}))
|
|
13884
|
+
];
|
|
13885
|
+
}
|
|
13886
|
+
function selectedOverviewRow(s) {
|
|
13887
|
+
return overviewRows(s)[s.index] ?? null;
|
|
13888
|
+
}
|
|
13889
|
+
function initialOverview(opts) {
|
|
13890
|
+
return {
|
|
13891
|
+
view: null,
|
|
13892
|
+
selected: opts.selected ?? null,
|
|
13893
|
+
index: 0,
|
|
13894
|
+
all: false,
|
|
13895
|
+
armedAt: null,
|
|
13896
|
+
notice: null,
|
|
13897
|
+
error: null,
|
|
13898
|
+
busy: false,
|
|
13899
|
+
updatedAt: null
|
|
13900
|
+
};
|
|
13901
|
+
}
|
|
13902
|
+
var running2 = (st) => st.rows.filter(
|
|
13903
|
+
(r) => r.pm_id !== null && r.status !== "stopped" && r.status !== "done"
|
|
13904
|
+
).length;
|
|
13905
|
+
function stopPrompt(st) {
|
|
13906
|
+
const n = running2(st);
|
|
13907
|
+
return `press x again to stop ${n} app${n === 1 ? "" : "s"} of ${st.project}`;
|
|
13908
|
+
}
|
|
13909
|
+
var unloaded = (st) => `${st.project}: the manifest did not load (${st.manifest_error?.code})`;
|
|
13910
|
+
function reduceOverview(s, a) {
|
|
13911
|
+
switch (a.type) {
|
|
13912
|
+
case "view": {
|
|
13913
|
+
const next = { ...s, view: a.view, updatedAt: a.at, error: null };
|
|
13914
|
+
const rows = overviewRows(next);
|
|
13915
|
+
const found = rows.findIndex((r) => r.key === s.selected);
|
|
13916
|
+
const index = found >= 0 ? found : Math.min(s.index, Math.max(0, rows.length - 1));
|
|
13917
|
+
const selected = rows[index]?.key ?? null;
|
|
13918
|
+
const moved = s.armedAt !== null && selected !== s.selected;
|
|
13919
|
+
return {
|
|
13920
|
+
...next,
|
|
13921
|
+
index,
|
|
13922
|
+
selected,
|
|
13923
|
+
...moved ? { armedAt: null, notice: null } : {}
|
|
13924
|
+
};
|
|
13925
|
+
}
|
|
13926
|
+
case "select": {
|
|
13927
|
+
const rows = overviewRows(s);
|
|
13928
|
+
if (rows.length === 0) return s;
|
|
13929
|
+
const index = Math.max(0, Math.min(rows.length - 1, s.index + a.delta));
|
|
13930
|
+
return { ...s, index, selected: rows[index].key, armedAt: null };
|
|
13931
|
+
}
|
|
13932
|
+
case "arm": {
|
|
13933
|
+
const r = selectedOverviewRow(s);
|
|
13934
|
+
if (a.at === null)
|
|
13935
|
+
return {
|
|
13936
|
+
...s,
|
|
13937
|
+
armedAt: null,
|
|
13938
|
+
notice: s.armedAt !== null ? null : s.notice
|
|
13939
|
+
};
|
|
13940
|
+
return {
|
|
13941
|
+
...s,
|
|
13942
|
+
armedAt: a.at,
|
|
13943
|
+
notice: r?.kind === "stack" ? stopPrompt(r.stack) : s.notice
|
|
13944
|
+
};
|
|
13945
|
+
}
|
|
13946
|
+
case "toggle-all":
|
|
13947
|
+
return { ...s, all: !s.all };
|
|
13948
|
+
case "notice":
|
|
13949
|
+
return { ...s, notice: a.message, armedAt: null };
|
|
13950
|
+
case "error":
|
|
13951
|
+
return { ...s, error: a.message };
|
|
13952
|
+
case "busy":
|
|
13953
|
+
return { ...s, busy: a.busy };
|
|
13954
|
+
}
|
|
13955
|
+
}
|
|
13956
|
+
function sharedSlug(st, what) {
|
|
13957
|
+
const n = st.rivals.length + 1;
|
|
13958
|
+
return `${st.project}: ${n} checkouts share this slug on one pm2, so ${what === "open" ? "its monit" : "a stop"} could reach another one's processes; give each checkout its own project slug`;
|
|
13959
|
+
}
|
|
13960
|
+
function overviewKey(key, s, now = Date.now()) {
|
|
13961
|
+
const r = selectedOverviewRow(s);
|
|
13962
|
+
const notice = (message) => ({
|
|
13963
|
+
kind: "action",
|
|
13964
|
+
action: { type: "notice", message }
|
|
13965
|
+
});
|
|
13966
|
+
switch (key) {
|
|
13967
|
+
case "up":
|
|
13968
|
+
case "k":
|
|
13969
|
+
return { kind: "action", action: { type: "select", delta: -1 } };
|
|
13970
|
+
case "down":
|
|
13971
|
+
case "j":
|
|
13972
|
+
return { kind: "action", action: { type: "select", delta: 1 } };
|
|
13973
|
+
case "enter":
|
|
13974
|
+
case "return": {
|
|
13975
|
+
if (!r) return null;
|
|
13976
|
+
if (r.kind === "orphan")
|
|
13977
|
+
return notice("not an mcproc stack: no manifest to open");
|
|
13978
|
+
if (r.stack.manifest_error) return notice(unloaded(r.stack));
|
|
13979
|
+
if (r.stack.rivals?.length) return notice(sharedSlug(r.stack, "open"));
|
|
13980
|
+
if (!r.stack.hosts.some((h) => h.reachable)) {
|
|
13981
|
+
const why = r.stack.hosts.find((h) => h.error);
|
|
13982
|
+
return notice(
|
|
13983
|
+
`${r.stack.project}: no host of this stack answers${why ? ` (${why.name}: ${why.error})` : ""}`
|
|
13984
|
+
);
|
|
13985
|
+
}
|
|
13986
|
+
return { kind: "open", root: r.stack.root };
|
|
13987
|
+
}
|
|
13988
|
+
case "x": {
|
|
13989
|
+
if (!r || s.busy) return null;
|
|
13990
|
+
if (r.kind === "orphan") return notice("not an mcproc stack: read-only");
|
|
13991
|
+
if (r.stack.manifest_error) return notice(unloaded(r.stack));
|
|
13992
|
+
if (r.stack.rivals?.length) return notice(sharedSlug(r.stack, "stop"));
|
|
13993
|
+
if (s.armedAt !== null && now - s.armedAt <= CONFIRM_MS)
|
|
13994
|
+
return { kind: "stop", root: r.stack.root };
|
|
13995
|
+
return { kind: "action", action: { type: "arm", at: now } };
|
|
13996
|
+
}
|
|
13997
|
+
case "a":
|
|
13998
|
+
return { kind: "action", action: { type: "toggle-all" } };
|
|
13999
|
+
case "q":
|
|
14000
|
+
case "C-c":
|
|
14001
|
+
case "escape":
|
|
14002
|
+
return { kind: "quit" };
|
|
14003
|
+
default:
|
|
14004
|
+
return null;
|
|
14005
|
+
}
|
|
14006
|
+
}
|
|
14007
|
+
|
|
14008
|
+
// src/monit/stacks-frame.ts
|
|
14009
|
+
var KEYS = (all) => `j/k stack enter open x stop stack a ${all ? "running only" : "all known"} q quit`;
|
|
14010
|
+
var CHROME = 4;
|
|
14011
|
+
var MIN_APPS = 4;
|
|
14012
|
+
function stackLines(st, width) {
|
|
14013
|
+
const e = st.manifest_error;
|
|
14014
|
+
const lines = e ? [`${e.code}: ${e.message}`, ...e.hint ? [e.hint] : []] : table(
|
|
14015
|
+
[
|
|
14016
|
+
["APP", "HOST", "STATUS", "UP", "MEM", "CPU", "PORTS"],
|
|
14017
|
+
...st.rows.map((r) => [
|
|
14018
|
+
r.app,
|
|
14019
|
+
r.host,
|
|
14020
|
+
r.status,
|
|
14021
|
+
fmtUptime(r.uptime_ms),
|
|
14022
|
+
r.memory ? fmtBytes(r.memory) : "-",
|
|
14023
|
+
r.status === "online" ? fmtCpu(r.cpu) : "-",
|
|
14024
|
+
fmtPorts(r.ports)
|
|
14025
|
+
])
|
|
14026
|
+
].map((r) => r.map((c) => screenText(c))),
|
|
14027
|
+
{ width, flex: [6], mode: "fit" }
|
|
14028
|
+
).split("\n");
|
|
14029
|
+
for (const n of st.notes) lines.push(n);
|
|
14030
|
+
for (const h of st.hosts) if (h.error) lines.push(`${h.name}: ${h.error}`);
|
|
14031
|
+
return lines;
|
|
14032
|
+
}
|
|
14033
|
+
function renderOverview(s, size) {
|
|
14034
|
+
const w = size.width;
|
|
14035
|
+
const a = s.view?.answer;
|
|
14036
|
+
const at = s.updatedAt ? ` \xB7 ${s.updatedAt.toTimeString().slice(0, 8)}` : "";
|
|
14037
|
+
const header = cutWidth(
|
|
14038
|
+
screenText(
|
|
14039
|
+
a ? `mcproc \xB7 ${stacksHostLine(a.host)}${at}${a.host.notes.map((n) => ` \xB7 ${n}`).join("")}` : "mcproc \xB7 reading the stacks\u2026"
|
|
14040
|
+
),
|
|
14041
|
+
w
|
|
14042
|
+
);
|
|
14043
|
+
const rows = overviewRows(s);
|
|
14044
|
+
const listed = new Set(s.view?.stacks.map((st) => st.project));
|
|
14045
|
+
const cells = rows.map((r, i) => {
|
|
14046
|
+
const mark = i === s.index ? "\u25B6" : " ";
|
|
14047
|
+
if (r.kind === "orphan") return [mark, ...orphanCells(r.orphan)];
|
|
14048
|
+
const st = r.stack;
|
|
14049
|
+
const nested = st.base_project !== st.project && listed.has(st.base_project);
|
|
14050
|
+
return [
|
|
14051
|
+
mark,
|
|
14052
|
+
stackLabel(st, nested),
|
|
14053
|
+
tildePath(st.root),
|
|
14054
|
+
st.hosts.map((h) => h.reachable ? h.name : `${h.name}\u2717`).join(","),
|
|
14055
|
+
st.manifest_error ? "manifest error" : stackApps(st),
|
|
14056
|
+
String(st.apps.errored),
|
|
14057
|
+
st.memory ? fmtBytes(st.memory) : "-",
|
|
14058
|
+
st.cpu === null ? "-" : `${Math.round(st.cpu)}%`,
|
|
14059
|
+
stackPorts(st)
|
|
14060
|
+
];
|
|
14061
|
+
});
|
|
14062
|
+
const lines = table(
|
|
14063
|
+
[[" ", ...STACK_HEADER], ...cells].map((r) => r.map((c) => screenText(c))),
|
|
14064
|
+
{ width: w + 2, flex: [2, 8], cutStart: [2], mode: "fit" }
|
|
14065
|
+
).split("\n").map((l) => cutWidth(l.replace(/^(.)\s\s/, "$1"), w));
|
|
14066
|
+
const budget = Math.max(2, size.height - CHROME - MIN_APPS);
|
|
14067
|
+
const shown = budget - 1;
|
|
14068
|
+
const start = Math.max(0, Math.min(s.index + 1 - shown, rows.length - shown));
|
|
14069
|
+
const tableShown = [lines[0], ...lines.slice(1 + start, 1 + start + shown)];
|
|
14070
|
+
const sel = selectedOverviewRow(s);
|
|
14071
|
+
let appsTitle = "apps";
|
|
14072
|
+
let apps = [];
|
|
14073
|
+
if (sel?.kind === "stack") {
|
|
14074
|
+
appsTitle = `apps \xB7 ${sel.stack.project}`;
|
|
14075
|
+
apps = stackLines(sel.stack, w);
|
|
14076
|
+
} else if (sel?.kind === "orphan") {
|
|
14077
|
+
const o = sel.orphan;
|
|
14078
|
+
appsTitle = `processes \xB7 pm2 \xB7 ${o.namespace}`;
|
|
14079
|
+
apps = table(
|
|
14080
|
+
[
|
|
14081
|
+
["NAME", "STATUS", "MEM"],
|
|
14082
|
+
...o.processes.map((p) => [
|
|
14083
|
+
p.name,
|
|
14084
|
+
p.status,
|
|
14085
|
+
p.memory ? fmtBytes(p.memory) : "-"
|
|
14086
|
+
])
|
|
14087
|
+
].map((r) => r.map((c) => screenText(c))),
|
|
14088
|
+
{ width: w, flex: [0], mode: "fit" }
|
|
14089
|
+
).split("\n");
|
|
14090
|
+
if (o.note) apps.push(o.note);
|
|
14091
|
+
} else if (a) {
|
|
14092
|
+
apps = [
|
|
14093
|
+
...a.host.notes,
|
|
14094
|
+
`no stacks running on this host \xB7 a: ${s.all ? "running only" : "all known"}`
|
|
14095
|
+
];
|
|
14096
|
+
}
|
|
14097
|
+
const appsH = Math.max(1, size.height - CHROME - tableShown.length);
|
|
14098
|
+
const right = screenText(
|
|
14099
|
+
s.error ? `ERROR: ${s.error}` : s.notice ?? (s.busy ? "working\u2026" : "")
|
|
14100
|
+
);
|
|
14101
|
+
const footer = cutWidth(right ? `${right} \u2502 ${KEYS(s.all)}` : KEYS(s.all), w);
|
|
14102
|
+
return {
|
|
14103
|
+
header,
|
|
14104
|
+
title: paneTitle("stacks", w),
|
|
14105
|
+
table: tableShown,
|
|
14106
|
+
appsTitle: paneTitle(screenText(appsTitle), w),
|
|
14107
|
+
apps: apps.slice(0, appsH).map((l) => cutWidth(screenText(l), w)),
|
|
14108
|
+
footer
|
|
14109
|
+
};
|
|
14110
|
+
}
|
|
14111
|
+
function overviewToText(f) {
|
|
14112
|
+
return [f.header, "", ...f.table, "", ...f.apps, "", f.footer].join("\n");
|
|
14113
|
+
}
|
|
14114
|
+
|
|
14115
|
+
// src/monit/stacks-tui.ts
|
|
14116
|
+
function stopNotice(project, r) {
|
|
14117
|
+
const n = r.stopped.length;
|
|
14118
|
+
const parts = [`stopped ${n} app${n === 1 ? "" : "s"} of ${project}`];
|
|
14119
|
+
for (const x of r.readonly)
|
|
14120
|
+
parts.push(`readonly: ${x.host} (${x.apps.length})`);
|
|
14121
|
+
for (const f of r.failed) parts.push(`failed: ${f.host} ${f.message}`);
|
|
14122
|
+
return parts.join(" \xB7 ");
|
|
14123
|
+
}
|
|
14124
|
+
async function overviewOnce(opts) {
|
|
14125
|
+
const view = await new StacksCollector().collect({
|
|
14126
|
+
all: Boolean(opts.all)
|
|
14127
|
+
});
|
|
14128
|
+
const s = reduceOverview(initialOverview({}), {
|
|
14129
|
+
type: "view",
|
|
14130
|
+
view,
|
|
14131
|
+
at: /* @__PURE__ */ new Date()
|
|
14132
|
+
});
|
|
14133
|
+
return overviewToText(
|
|
14134
|
+
renderOverview(s, { width: opts.width ?? 120, height: 1e3 })
|
|
14135
|
+
);
|
|
14136
|
+
}
|
|
14137
|
+
async function runOverview(opts) {
|
|
14138
|
+
const screen = blessed2.screen({
|
|
14139
|
+
smartCSR: true,
|
|
14140
|
+
title: "mcproc monit",
|
|
14141
|
+
fullUnicode: true
|
|
14142
|
+
});
|
|
14143
|
+
const box = (o) => blessed2.box({ left: 0, width: "100%", wrap: false, ...o });
|
|
14144
|
+
const header = box({ top: 0, height: 1 });
|
|
14145
|
+
const title = box({ top: 1, height: 1 });
|
|
14146
|
+
const tableBox = box({ top: 2, height: 1 });
|
|
14147
|
+
const appsTitle = box({ top: 3, height: 1 });
|
|
14148
|
+
const appsBox = box({ top: 4, height: 1 });
|
|
14149
|
+
const footer = box({ bottom: 0, height: 1 });
|
|
14150
|
+
const boxes = [header, title, tableBox, appsTitle, appsBox, footer];
|
|
14151
|
+
const attach = () => {
|
|
14152
|
+
for (const b of boxes) screen.append(b);
|
|
14153
|
+
};
|
|
14154
|
+
const detach = () => {
|
|
14155
|
+
for (const b of boxes) b.detach();
|
|
14156
|
+
};
|
|
14157
|
+
attach();
|
|
14158
|
+
const collector = new StacksCollector();
|
|
14159
|
+
let state = initialOverview({ selected: opts.selected });
|
|
14160
|
+
let active = true;
|
|
14161
|
+
let closed = false;
|
|
14162
|
+
let armTimer = null;
|
|
14163
|
+
const paint = () => {
|
|
14164
|
+
if (!active || closed) return;
|
|
14165
|
+
const f = renderOverview(state, {
|
|
14166
|
+
width: screen.width,
|
|
14167
|
+
height: screen.height
|
|
14168
|
+
});
|
|
14169
|
+
header.setContent(f.header);
|
|
14170
|
+
title.setContent(f.title);
|
|
14171
|
+
tableBox.height = f.table.length;
|
|
14172
|
+
tableBox.setContent(f.table.join("\n"));
|
|
14173
|
+
appsTitle.top = 2 + f.table.length;
|
|
14174
|
+
appsTitle.setContent(f.appsTitle);
|
|
14175
|
+
appsBox.top = 3 + f.table.length;
|
|
14176
|
+
appsBox.height = Math.max(
|
|
14177
|
+
1,
|
|
14178
|
+
screen.height - appsBox.top - 1
|
|
14179
|
+
);
|
|
14180
|
+
appsBox.setContent(f.apps.join("\n"));
|
|
14181
|
+
footer.setContent(f.footer);
|
|
14182
|
+
screen.render();
|
|
14183
|
+
};
|
|
14184
|
+
const act = (a) => {
|
|
14185
|
+
state = reduceOverview(state, a);
|
|
14186
|
+
paint();
|
|
14187
|
+
};
|
|
14188
|
+
let inflight = false;
|
|
14189
|
+
const refresh = async () => {
|
|
14190
|
+
if (inflight || closed) return;
|
|
14191
|
+
inflight = true;
|
|
14192
|
+
try {
|
|
14193
|
+
const view = await collector.collect({ all: state.all });
|
|
14194
|
+
act({ type: "view", view, at: /* @__PURE__ */ new Date() });
|
|
14195
|
+
} catch (e) {
|
|
14196
|
+
act({ type: "error", message: describeError(e) });
|
|
14197
|
+
} finally {
|
|
14198
|
+
inflight = false;
|
|
14199
|
+
}
|
|
14200
|
+
};
|
|
14201
|
+
let timer = setInterval(() => void refresh(), opts.intervalMs);
|
|
14202
|
+
return new Promise((resolve) => {
|
|
14203
|
+
const quit = () => {
|
|
14204
|
+
closed = true;
|
|
14205
|
+
clearInterval(timer);
|
|
14206
|
+
if (armTimer) clearTimeout(armTimer);
|
|
12911
14207
|
screen.destroy();
|
|
12912
14208
|
resolve();
|
|
12913
14209
|
};
|
|
14210
|
+
const onKey = async (key) => {
|
|
14211
|
+
const intent = overviewKey(key, state);
|
|
14212
|
+
if (state.armedAt !== null && intent?.kind !== "stop" && !(intent?.kind === "action" && intent.action.type === "arm"))
|
|
14213
|
+
act({ type: "arm", at: null });
|
|
14214
|
+
if (!intent) return;
|
|
14215
|
+
if (intent.kind === "quit") return quit();
|
|
14216
|
+
if (intent.kind === "action") {
|
|
14217
|
+
act(intent.action);
|
|
14218
|
+
if (intent.action.type === "arm" && intent.action.at !== null) {
|
|
14219
|
+
const at = intent.action.at;
|
|
14220
|
+
if (armTimer) clearTimeout(armTimer);
|
|
14221
|
+
armTimer = setTimeout(() => {
|
|
14222
|
+
if (state.armedAt === at) act({ type: "arm", at: null });
|
|
14223
|
+
}, CONFIRM_MS + 50);
|
|
14224
|
+
armTimer.unref();
|
|
14225
|
+
}
|
|
14226
|
+
if (intent.action.type === "toggle-all") void refresh();
|
|
14227
|
+
return;
|
|
14228
|
+
}
|
|
14229
|
+
const m = state.view?.manifests.get(intent.root);
|
|
14230
|
+
if (!m) {
|
|
14231
|
+
act({ type: "notice", message: "that stack is gone; refreshing" });
|
|
14232
|
+
return void refresh();
|
|
14233
|
+
}
|
|
14234
|
+
if (intent.kind === "stop") {
|
|
14235
|
+
act({ type: "busy", busy: true });
|
|
14236
|
+
const r = await stopStack(m);
|
|
14237
|
+
if (closed) return;
|
|
14238
|
+
act({ type: "busy", busy: false });
|
|
14239
|
+
act({ type: "notice", message: stopNotice(m.project, r) });
|
|
14240
|
+
return void refresh();
|
|
14241
|
+
}
|
|
14242
|
+
active = false;
|
|
14243
|
+
clearInterval(timer);
|
|
14244
|
+
detach();
|
|
14245
|
+
const why = await runMonit({
|
|
14246
|
+
project: intent.root,
|
|
14247
|
+
intervalMs: opts.intervalMs,
|
|
14248
|
+
lines: opts.lines,
|
|
14249
|
+
screen,
|
|
14250
|
+
back: true
|
|
14251
|
+
}).catch((e) => {
|
|
14252
|
+
state = reduceOverview(state, {
|
|
14253
|
+
type: "error",
|
|
14254
|
+
message: describeError(e)
|
|
14255
|
+
});
|
|
14256
|
+
return "back";
|
|
14257
|
+
});
|
|
14258
|
+
if (why === "quit") return quit();
|
|
14259
|
+
attach();
|
|
14260
|
+
active = true;
|
|
14261
|
+
timer = setInterval(() => void refresh(), opts.intervalMs);
|
|
14262
|
+
paint();
|
|
14263
|
+
void refresh();
|
|
14264
|
+
};
|
|
12914
14265
|
screen.on(
|
|
12915
14266
|
"keypress",
|
|
12916
14267
|
(_ch, key) => {
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
dispatch({ type: "arm", at: null });
|
|
12920
|
-
if (!intent) return;
|
|
12921
|
-
if (intent.kind === "quit") return quit();
|
|
12922
|
-
if (intent.kind === "action") {
|
|
12923
|
-
if (intent.action.type === "arm" && intent.action.at !== null) {
|
|
12924
|
-
const { at, what } = intent.action;
|
|
12925
|
-
setTimeout(() => {
|
|
12926
|
-
if (state.armedAt === at && state.armed === what)
|
|
12927
|
-
dispatch({ type: "arm", at: null });
|
|
12928
|
-
}, CONFIRM_MS + 50).unref();
|
|
12929
|
-
}
|
|
12930
|
-
if (intent.action.type === "select")
|
|
12931
|
-
loop.dispatchSelect(intent.action.delta);
|
|
12932
|
-
else {
|
|
12933
|
-
dispatch(intent.action);
|
|
12934
|
-
if (intent.action.type === "view") void loop.refresh();
|
|
12935
|
-
}
|
|
12936
|
-
return;
|
|
12937
|
-
}
|
|
12938
|
-
if (intent.kind === "host-next") return void loop.nextView();
|
|
12939
|
-
if (intent.kind === "clear") return void loop.runClear(intent.apps);
|
|
12940
|
-
if (intent.kind === "reload-all") return void loop.reloadAll();
|
|
12941
|
-
void loop.runCommand(intent.cmd, intent.app);
|
|
14268
|
+
if (!active) return;
|
|
14269
|
+
void onKey(key.full ?? key.name ?? "");
|
|
12942
14270
|
}
|
|
12943
14271
|
);
|
|
12944
14272
|
screen.on("resize", paint);
|
|
12945
|
-
|
|
14273
|
+
paint();
|
|
14274
|
+
void refresh();
|
|
12946
14275
|
});
|
|
12947
14276
|
}
|
|
12948
14277
|
|
|
@@ -12951,7 +14280,8 @@ var defaultIo = {
|
|
|
12951
14280
|
out: (s) => process.stdout.write(`${s}
|
|
12952
14281
|
`),
|
|
12953
14282
|
err: (s) => process.stderr.write(`${s}
|
|
12954
|
-
`)
|
|
14283
|
+
`),
|
|
14284
|
+
columns: () => process.stdout.isTTY ? process.stdout.columns || void 0 : void 0
|
|
12955
14285
|
};
|
|
12956
14286
|
async function printSince(fleet, apps, since, how, io, signal) {
|
|
12957
14287
|
const marks = new LogMarks();
|
|
@@ -13003,10 +14333,20 @@ async function printLs(fleet, names, how, io) {
|
|
|
13003
14333
|
how.reloads
|
|
13004
14334
|
);
|
|
13005
14335
|
io.out(
|
|
13006
|
-
how.json ? JSON.stringify(rows, null, 2) : renderLs(rows, fleet.fallback, unread)
|
|
14336
|
+
how.json ? JSON.stringify(rows, null, 2) : renderLs(rows, fleet.fallback, unread, io.columns?.())
|
|
13007
14337
|
);
|
|
13008
14338
|
if (errors.length) process.exitCode = 1;
|
|
13009
14339
|
}
|
|
14340
|
+
function monitMode(g, all, env = process.env, cwd = process.cwd()) {
|
|
14341
|
+
const named = g.project || env.MCPROC_PROJECT || env.CLAUDE_PROJECT_DIR;
|
|
14342
|
+
const found = findManifestPath(
|
|
14343
|
+
named ? resolveProjectDir(g.project, [], env) : cwd
|
|
14344
|
+
);
|
|
14345
|
+
if (all)
|
|
14346
|
+
return found ? { mode: "overview", selected: found.root } : { mode: "overview" };
|
|
14347
|
+
if (!named && !found) return { mode: "overview" };
|
|
14348
|
+
return { mode: "project" };
|
|
14349
|
+
}
|
|
13010
14350
|
function buildProgram(io = defaultIo) {
|
|
13011
14351
|
const program = new Command().name("mcproc").description(
|
|
13012
14352
|
"pm2 process control for agents: one manifest per project, local and remote hosts, per-app ports"
|
|
@@ -13015,6 +14355,7 @@ function buildProgram(io = defaultIo) {
|
|
|
13015
14355
|
"project root (default: MCPROC_PROJECT, CLAUDE_PROJECT_DIR, cwd)"
|
|
13016
14356
|
).option("--host <name>", "host from the manifest (default: local)").option("--json", "print JSON instead of text");
|
|
13017
14357
|
const emit = (g, text, data) => io.out(g.json ? JSON.stringify(data, null, 2) : text);
|
|
14358
|
+
const width = (g) => g.json ? void 0 : io.columns?.();
|
|
13018
14359
|
const wrap = (fn) => async (...all) => {
|
|
13019
14360
|
const cmd = all[all.length - 1];
|
|
13020
14361
|
const opts = all[all.length - 2] ?? {};
|
|
@@ -13037,7 +14378,13 @@ Hint: ${e.hint}` : ""}`
|
|
|
13037
14378
|
const fleet = fleetOf(g);
|
|
13038
14379
|
const waits = [];
|
|
13039
14380
|
const rows = await actOn(fleet, target, act, { ...opts, waits });
|
|
13040
|
-
const { text, failed } = renderMutation(
|
|
14381
|
+
const { text, failed } = renderMutation(
|
|
14382
|
+
rows,
|
|
14383
|
+
fleet.fallback,
|
|
14384
|
+
act,
|
|
14385
|
+
waits,
|
|
14386
|
+
width(g)
|
|
14387
|
+
);
|
|
13041
14388
|
emit(g, text, { ...hostsOf(rows, fleet.fallback), rows, waits });
|
|
13042
14389
|
if (failed) process.exitCode = 1;
|
|
13043
14390
|
};
|
|
@@ -13066,7 +14413,7 @@ ${r.next}`, r);
|
|
|
13066
14413
|
wrap(async (g) => {
|
|
13067
14414
|
const { manifest } = prepare({ project: g.project });
|
|
13068
14415
|
const h = hostsInfo(manifest);
|
|
13069
|
-
emit(g, renderHosts(h), h);
|
|
14416
|
+
emit(g, renderHosts(h, width(g)), h);
|
|
13070
14417
|
})
|
|
13071
14418
|
);
|
|
13072
14419
|
program.command("doctor").description(
|
|
@@ -13099,7 +14446,7 @@ ${r.next}`, r);
|
|
|
13099
14446
|
const fleet = fleetOf(g);
|
|
13100
14447
|
if (!app) {
|
|
13101
14448
|
const d2 = describeProject(fleet.manifest);
|
|
13102
|
-
emit(g, renderProject(d2), d2);
|
|
14449
|
+
emit(g, renderProject(d2, width(g)), d2);
|
|
13103
14450
|
return;
|
|
13104
14451
|
}
|
|
13105
14452
|
const d = await describeFleetApp(fleet, app);
|
|
@@ -13109,7 +14456,7 @@ ${r.next}`, r);
|
|
|
13109
14456
|
`${d.app.name} [${d.app.kind}]: ${d.app.description}`,
|
|
13110
14457
|
`run: ${d.app.run}`,
|
|
13111
14458
|
`cwd: ${d.app.cwd}`,
|
|
13112
|
-
renderLs([d.row], d.host),
|
|
14459
|
+
renderLs([d.row], d.host, [], width(g)),
|
|
13113
14460
|
urlNote(d.row),
|
|
13114
14461
|
`logs: ${d.logs ? `${d.logs.out} | ${d.logs.err}` : "not started yet"}`
|
|
13115
14462
|
].filter(Boolean).join("\n"),
|
|
@@ -13311,7 +14658,7 @@ ${r.next}`, r);
|
|
|
13311
14658
|
wrap(async (g, _o, [port]) => {
|
|
13312
14659
|
const ctx = await ctxOf(g);
|
|
13313
14660
|
const r = await portsReport(ctx, port ? Number(port) : void 0);
|
|
13314
|
-
emit(g, renderPorts(r, ctx.host.name), r);
|
|
14661
|
+
emit(g, renderPorts(r, ctx.host.name, width(g)), r);
|
|
13315
14662
|
})
|
|
13316
14663
|
);
|
|
13317
14664
|
program.command("start <target>").description("start an app or profile (dependencies first)").option("--reload", "restart apps that are already online").option("--note <text>", "what this start is for, kept on its lease").addOption(new Option("--lease <id>").hideHelp()).action(
|
|
@@ -13337,7 +14684,7 @@ ${r.next}`, r);
|
|
|
13337
14684
|
ctx = void 0;
|
|
13338
14685
|
}
|
|
13339
14686
|
const v = await leasesView(void 0, ctx);
|
|
13340
|
-
emit(g, renderLeases(v), v);
|
|
14687
|
+
emit(g, renderLeases(v, width(g)), v);
|
|
13341
14688
|
})
|
|
13342
14689
|
);
|
|
13343
14690
|
leases.command("cancel <id>").description("cancel a pending lease").action(
|
|
@@ -13346,6 +14693,22 @@ ${r.next}`, r);
|
|
|
13346
14693
|
emit(g, `cancelled ${l.id} (${l.project} ${l.target})`, l);
|
|
13347
14694
|
})
|
|
13348
14695
|
);
|
|
14696
|
+
program.command("stacks").description(
|
|
14697
|
+
"every mcproc stack on this host: the checkouts any mcproc call has seen and those found in the local pm2, with their hosts, apps, memory and ports"
|
|
14698
|
+
).option("--all", "also the recorded checkouts with nothing in pm2").action(
|
|
14699
|
+
wrap(async (g, o) => {
|
|
14700
|
+
if (g.host)
|
|
14701
|
+
throw new McprocError(
|
|
14702
|
+
"BAD_ARGUMENT",
|
|
14703
|
+
"stacks reads every host each stack declares; --host does not apply",
|
|
14704
|
+
{
|
|
14705
|
+
hint: "Drop --host."
|
|
14706
|
+
}
|
|
14707
|
+
);
|
|
14708
|
+
const v = await new StacksCollector().collect({ all: Boolean(o.all) });
|
|
14709
|
+
emit(g, renderStacks(v.answer, width(g)), v.answer);
|
|
14710
|
+
})
|
|
14711
|
+
);
|
|
13349
14712
|
program.command("exec <command...>").description(
|
|
13350
14713
|
"run a one-off command from the project root with the manifest's exec.env; with --memory, under a lease in the host's queue (use -- before the command)"
|
|
13351
14714
|
).option(
|
|
@@ -13452,7 +14815,7 @@ ${r.next}`, r);
|
|
|
13452
14815
|
wrap(async (g) => {
|
|
13453
14816
|
const { manifest, deps: deps2 } = await worktreeCtx(g);
|
|
13454
14817
|
const rows = await worktreeLs(manifest, deps2);
|
|
13455
|
-
emit(g, renderWorktrees(rows), rows);
|
|
14818
|
+
emit(g, renderWorktrees(rows, width(g)), rows);
|
|
13456
14819
|
})
|
|
13457
14820
|
);
|
|
13458
14821
|
worktree.command("remove <ref>").description(
|
|
@@ -13477,7 +14840,7 @@ ${r.next}`, r);
|
|
|
13477
14840
|
const stop = () => ac.abort();
|
|
13478
14841
|
process.once("SIGINT", stop);
|
|
13479
14842
|
process.once("SIGTERM", stop);
|
|
13480
|
-
|
|
14843
|
+
fs20.mkdirSync(deps2.store.dir, { recursive: true });
|
|
13481
14844
|
deps2.watch = leaseDirWatch(deps2.store.dir);
|
|
13482
14845
|
const r = await watchLeases(deps2, { signal: ac.signal });
|
|
13483
14846
|
io.out(`watcher ${r}`);
|
|
@@ -13493,25 +14856,36 @@ ${r.next}`, r);
|
|
|
13493
14856
|
})
|
|
13494
14857
|
);
|
|
13495
14858
|
program.command("monit").description(
|
|
13496
|
-
"terminal dashboard: apps with real memory/cpu/ports, log tail, details (--once prints one frame)"
|
|
14859
|
+
"terminal dashboard: apps with real memory/cpu/ports, log tail, details (--once prints one frame); outside a project, or with --all, every stack of this host"
|
|
14860
|
+
).option(
|
|
14861
|
+
"--all",
|
|
14862
|
+
"the stacks overview: every stack of this host, Enter opens one"
|
|
13497
14863
|
).option("--once", "print one frame as text and exit").option("--interval <ms>", "refresh interval", "2000").option("--lines <n>", "log lines of history", "200").action(
|
|
13498
14864
|
wrap(async (g, o) => {
|
|
14865
|
+
const lines = Number(o.lines) || 200;
|
|
14866
|
+
const intervalMs = Math.max(500, Number(o.interval) || 2e3);
|
|
14867
|
+
const m = monitMode(g, Boolean(o.all));
|
|
14868
|
+
if (m.mode === "overview") {
|
|
14869
|
+
if (g.host)
|
|
14870
|
+
throw new McprocError(
|
|
14871
|
+
"BAD_ARGUMENT",
|
|
14872
|
+
"--host names a host of one manifest; the overview reads every stack",
|
|
14873
|
+
{
|
|
14874
|
+
hint: "Drop --host, or open the project: mcproc --project <root> monit --host <name>."
|
|
14875
|
+
}
|
|
14876
|
+
);
|
|
14877
|
+
if (o.once) {
|
|
14878
|
+
io.out(await overviewOnce({ width: io.columns?.() }));
|
|
14879
|
+
return;
|
|
14880
|
+
}
|
|
14881
|
+
await runOverview({ intervalMs, lines, selected: m.selected });
|
|
14882
|
+
return;
|
|
14883
|
+
}
|
|
13499
14884
|
if (o.once) {
|
|
13500
|
-
io.out(
|
|
13501
|
-
await monitOnce({
|
|
13502
|
-
project: g.project,
|
|
13503
|
-
host: g.host,
|
|
13504
|
-
lines: Number(o.lines) || 200
|
|
13505
|
-
})
|
|
13506
|
-
);
|
|
14885
|
+
io.out(await monitOnce({ project: g.project, host: g.host, lines }));
|
|
13507
14886
|
return;
|
|
13508
14887
|
}
|
|
13509
|
-
await runMonit({
|
|
13510
|
-
project: g.project,
|
|
13511
|
-
host: g.host,
|
|
13512
|
-
intervalMs: Math.max(500, Number(o.interval) || 2e3),
|
|
13513
|
-
lines: Number(o.lines) || 200
|
|
13514
|
-
});
|
|
14888
|
+
await runMonit({ project: g.project, host: g.host, intervalMs, lines });
|
|
13515
14889
|
})
|
|
13516
14890
|
);
|
|
13517
14891
|
program.command("unpack").description(
|