mcproc 0.14.3 → 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 +2117 -742
- 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) => {
|
|
@@ -2731,95 +2902,11 @@ function buildEcosystem(m, host) {
|
|
|
2731
2902
|
return { apps };
|
|
2732
2903
|
}
|
|
2733
2904
|
|
|
2734
|
-
// src/core/slots.ts
|
|
2735
|
-
import crypto from "crypto";
|
|
2736
|
-
import fs6 from "fs";
|
|
2737
|
-
import path10 from "path";
|
|
2738
|
-
function registryKey(baseProject, repo) {
|
|
2739
|
-
return `${baseProject}-${crypto.createHash("sha256").update(realOf(repo)).digest("hex").slice(0, 8)}`;
|
|
2740
|
-
}
|
|
2741
|
-
function registryPath(stateDir2, baseProject, repo) {
|
|
2742
|
-
return path10.join(stateDir2, "slots", `${registryKey(baseProject, repo)}.json`);
|
|
2743
|
-
}
|
|
2744
|
-
function emptyRegistry(repo) {
|
|
2745
|
-
return { repo, slots: {} };
|
|
2746
|
-
}
|
|
2747
|
-
function readRegistry(file) {
|
|
2748
|
-
if (!fs6.existsSync(file)) return null;
|
|
2749
|
-
return JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
2750
|
-
}
|
|
2751
|
-
function writeRegistry(file, r) {
|
|
2752
|
-
fs6.mkdirSync(path10.dirname(file), { recursive: true });
|
|
2753
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
2754
|
-
fs6.writeFileSync(tmp, JSON.stringify(r, null, 2));
|
|
2755
|
-
fs6.renameSync(tmp, file);
|
|
2756
|
-
}
|
|
2757
|
-
function withMain(r, entry) {
|
|
2758
|
-
return { ...r, slots: { ...r.slots, "0": entry } };
|
|
2759
|
-
}
|
|
2760
|
-
function claimSlot(r, opts) {
|
|
2761
|
-
if (opts.want !== void 0) {
|
|
2762
|
-
if (opts.want < 1 || opts.want > opts.max)
|
|
2763
|
-
throw new McprocError(
|
|
2764
|
-
"BAD_ARGUMENT",
|
|
2765
|
-
`slot ${opts.want} is outside 1..${opts.max} (slot 0 is the main checkout)`
|
|
2766
|
-
);
|
|
2767
|
-
if (Object.hasOwn(r.slots, String(opts.want)))
|
|
2768
|
-
throw new McprocError(
|
|
2769
|
-
"SLOT_TAKEN",
|
|
2770
|
-
`slot ${opts.want} is taken by ${r.slots[String(opts.want)].path}`,
|
|
2771
|
-
{ hint: "Pick another slot, or remove that worktree first." }
|
|
2772
|
-
);
|
|
2773
|
-
return {
|
|
2774
|
-
registry: { ...r, slots: { ...r.slots, [opts.want]: opts.entry } },
|
|
2775
|
-
slot: opts.want
|
|
2776
|
-
};
|
|
2777
|
-
}
|
|
2778
|
-
for (let s = 1; s <= opts.max; s++)
|
|
2779
|
-
if (!Object.hasOwn(r.slots, String(s)))
|
|
2780
|
-
return {
|
|
2781
|
-
registry: { ...r, slots: { ...r.slots, [s]: opts.entry } },
|
|
2782
|
-
slot: s
|
|
2783
|
-
};
|
|
2784
|
-
throw new McprocError(
|
|
2785
|
-
"NO_FREE_SLOT",
|
|
2786
|
-
`all ${opts.max} worktree slots of ${r.repo} are taken`,
|
|
2787
|
-
{
|
|
2788
|
-
hint: "Remove a worktree (worktree remove), or raise worktree.slots in the manifest."
|
|
2789
|
-
}
|
|
2790
|
-
);
|
|
2791
|
-
}
|
|
2792
|
-
function releaseSlot(r, slot) {
|
|
2793
|
-
const slots = { ...r.slots };
|
|
2794
|
-
delete slots[String(slot)];
|
|
2795
|
-
return { ...r, slots };
|
|
2796
|
-
}
|
|
2797
|
-
function realOf(p) {
|
|
2798
|
-
try {
|
|
2799
|
-
return fs6.realpathSync(p);
|
|
2800
|
-
} catch {
|
|
2801
|
-
return path10.resolve(p);
|
|
2802
|
-
}
|
|
2803
|
-
}
|
|
2804
|
-
function reclaimGone(r, exists) {
|
|
2805
|
-
const freed = [];
|
|
2806
|
-
let out = r;
|
|
2807
|
-
for (const [k, e] of Object.entries(r.slots)) {
|
|
2808
|
-
if (k === "0" || exists(realOf(e.path))) continue;
|
|
2809
|
-
out = releaseSlot(out, Number(k));
|
|
2810
|
-
freed.push(Number(k));
|
|
2811
|
-
}
|
|
2812
|
-
return { registry: out, freed: freed.sort((a, b) => a - b) };
|
|
2813
|
-
}
|
|
2814
|
-
function siblingsOf(r, root) {
|
|
2815
|
-
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);
|
|
2816
|
-
}
|
|
2817
|
-
|
|
2818
2905
|
// src/core/leases/store.ts
|
|
2819
2906
|
import crypto2 from "crypto";
|
|
2820
|
-
import
|
|
2821
|
-
import
|
|
2822
|
-
import
|
|
2907
|
+
import fs8 from "fs";
|
|
2908
|
+
import os6 from "os";
|
|
2909
|
+
import path12 from "path";
|
|
2823
2910
|
var ALLOWED = {
|
|
2824
2911
|
pending: ["starting", "running", "cancelled"],
|
|
2825
2912
|
starting: ["running", "failed", "cancelled"],
|
|
@@ -2860,7 +2947,7 @@ function parseHeld(raw) {
|
|
|
2860
2947
|
}
|
|
2861
2948
|
function readHeld(file) {
|
|
2862
2949
|
try {
|
|
2863
|
-
return parseHeld(
|
|
2950
|
+
return parseHeld(fs8.readFileSync(file, "utf8"));
|
|
2864
2951
|
} catch {
|
|
2865
2952
|
return null;
|
|
2866
2953
|
}
|
|
@@ -2875,13 +2962,13 @@ var LeaseStore = class {
|
|
|
2875
2962
|
* `beforeUnlink` (tests only) runs while a breaker holds the break guard, before the re-read that decides the unlink.
|
|
2876
2963
|
*/
|
|
2877
2964
|
constructor(stateDir2, deps2 = {}) {
|
|
2878
|
-
this.dir =
|
|
2965
|
+
this.dir = path12.join(stateDir2, "leases");
|
|
2879
2966
|
this.now = deps2.now ?? (() => Date.now());
|
|
2880
2967
|
this.pid = deps2.pid ?? process.pid;
|
|
2881
2968
|
this.beforeUnlink = deps2.beforeUnlink;
|
|
2882
2969
|
}
|
|
2883
2970
|
file(l) {
|
|
2884
|
-
return
|
|
2971
|
+
return path12.join(
|
|
2885
2972
|
this.dir,
|
|
2886
2973
|
`${String(l.seq).padStart(6, "0")}-${l.id}.json`
|
|
2887
2974
|
);
|
|
@@ -2890,11 +2977,11 @@ var LeaseStore = class {
|
|
|
2890
2977
|
return new Date(this.now()).toISOString();
|
|
2891
2978
|
}
|
|
2892
2979
|
write(l) {
|
|
2893
|
-
|
|
2980
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
2894
2981
|
const f = this.file(l);
|
|
2895
2982
|
const tmp = `${f}.${this.pid}.tmp`;
|
|
2896
|
-
|
|
2897
|
-
|
|
2983
|
+
fs8.writeFileSync(tmp, JSON.stringify(l, null, 2));
|
|
2984
|
+
fs8.renameSync(tmp, f);
|
|
2898
2985
|
return l;
|
|
2899
2986
|
}
|
|
2900
2987
|
/**
|
|
@@ -2904,13 +2991,13 @@ var LeaseStore = class {
|
|
|
2904
2991
|
* breakIfStale), never by moving it aside, so the lock path is never left free while a live holder has it.
|
|
2905
2992
|
*/
|
|
2906
2993
|
async withLock(fn) {
|
|
2907
|
-
|
|
2908
|
-
const lock =
|
|
2994
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
2995
|
+
const lock = path12.join(this.dir, ".lock");
|
|
2909
2996
|
const token = `${this.pid}-${crypto2.randomBytes(6).toString("hex")}`;
|
|
2910
|
-
const tmp =
|
|
2997
|
+
const tmp = path12.join(this.dir, `.lock.${token}.tmp`);
|
|
2911
2998
|
const writeToken = () => {
|
|
2912
|
-
|
|
2913
|
-
|
|
2999
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
3000
|
+
fs8.writeFileSync(
|
|
2914
3001
|
tmp,
|
|
2915
3002
|
JSON.stringify({ pid: this.pid, at: this.now(), token })
|
|
2916
3003
|
);
|
|
@@ -2941,7 +3028,7 @@ var LeaseStore = class {
|
|
|
2941
3028
|
await new Promise((r2) => setTimeout(r2, 50));
|
|
2942
3029
|
}
|
|
2943
3030
|
} finally {
|
|
2944
|
-
|
|
3031
|
+
fs8.rmSync(tmp, { force: true });
|
|
2945
3032
|
}
|
|
2946
3033
|
LIVE_TOKENS.add(token);
|
|
2947
3034
|
try {
|
|
@@ -2956,7 +3043,7 @@ var LeaseStore = class {
|
|
|
2956
3043
|
}
|
|
2957
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. */
|
|
2958
3045
|
heldStale(held) {
|
|
2959
|
-
if (held.at < this.now() -
|
|
3046
|
+
if (held.at < this.now() - os6.uptime() * 1e3) return true;
|
|
2960
3047
|
if (held.pid === process.pid && !LIVE_TOKENS.has(held.token)) return true;
|
|
2961
3048
|
return this.now() - held.at > LOCK_STALE_MS && !pidAlive(held.pid);
|
|
2962
3049
|
}
|
|
@@ -2967,15 +3054,15 @@ var LeaseStore = class {
|
|
|
2967
3054
|
sweepStrays() {
|
|
2968
3055
|
let names;
|
|
2969
3056
|
try {
|
|
2970
|
-
names =
|
|
3057
|
+
names = fs8.readdirSync(this.dir);
|
|
2971
3058
|
} catch {
|
|
2972
3059
|
return;
|
|
2973
3060
|
}
|
|
2974
3061
|
for (const n of names) {
|
|
2975
3062
|
if (!STRAY.test(n)) continue;
|
|
2976
|
-
const f =
|
|
3063
|
+
const f = path12.join(this.dir, n);
|
|
2977
3064
|
if (this.mtimeAgeMs(f) > LOCK_STALE_MS)
|
|
2978
|
-
|
|
3065
|
+
fs8.rmSync(f, { recursive: true, force: true });
|
|
2979
3066
|
}
|
|
2980
3067
|
}
|
|
2981
3068
|
/**
|
|
@@ -2986,12 +3073,12 @@ var LeaseStore = class {
|
|
|
2986
3073
|
tryLink(tmp, lock, rewrite) {
|
|
2987
3074
|
for (let attempt = 0; ; attempt++) {
|
|
2988
3075
|
try {
|
|
2989
|
-
|
|
3076
|
+
fs8.linkSync(tmp, lock);
|
|
2990
3077
|
return true;
|
|
2991
3078
|
} catch (e) {
|
|
2992
3079
|
const code = e.code;
|
|
2993
3080
|
if (code === "EEXIST") return false;
|
|
2994
|
-
if (code === "ENOENT" && attempt === 0 && !
|
|
3081
|
+
if (code === "ENOENT" && attempt === 0 && !fs8.existsSync(tmp)) {
|
|
2995
3082
|
rewrite();
|
|
2996
3083
|
continue;
|
|
2997
3084
|
}
|
|
@@ -3003,7 +3090,7 @@ var LeaseStore = class {
|
|
|
3003
3090
|
stillStale(lock, judged) {
|
|
3004
3091
|
let raw;
|
|
3005
3092
|
try {
|
|
3006
|
-
raw =
|
|
3093
|
+
raw = fs8.readFileSync(lock, "utf8");
|
|
3007
3094
|
} catch (e) {
|
|
3008
3095
|
if (e.code === "ENOENT") return false;
|
|
3009
3096
|
throw e;
|
|
@@ -3016,7 +3103,7 @@ var LeaseStore = class {
|
|
|
3016
3103
|
* or, with no readable holder, the directory's mtime is older than LOCK_STALE_MS.
|
|
3017
3104
|
*/
|
|
3018
3105
|
guardStale(guard) {
|
|
3019
|
-
const held = readHeld(
|
|
3106
|
+
const held = readHeld(path12.join(guard, "holder"));
|
|
3020
3107
|
if (held)
|
|
3021
3108
|
return this.now() - held.at > LOCK_STALE_MS && !pidAlive(held.pid);
|
|
3022
3109
|
return this.mtimeAgeMs(guard) > LOCK_STALE_MS;
|
|
@@ -3034,7 +3121,7 @@ var LeaseStore = class {
|
|
|
3034
3121
|
breakIfStale(lock, token, take) {
|
|
3035
3122
|
let raw;
|
|
3036
3123
|
try {
|
|
3037
|
-
raw =
|
|
3124
|
+
raw = fs8.readFileSync(lock, "utf8");
|
|
3038
3125
|
} catch (e) {
|
|
3039
3126
|
if (e.code === "ENOENT") return "retry";
|
|
3040
3127
|
throw e;
|
|
@@ -3044,57 +3131,57 @@ var LeaseStore = class {
|
|
|
3044
3131
|
if (!stale) return "wait";
|
|
3045
3132
|
const guard = `${lock}.break`;
|
|
3046
3133
|
try {
|
|
3047
|
-
|
|
3134
|
+
fs8.mkdirSync(guard);
|
|
3048
3135
|
} catch (e) {
|
|
3049
3136
|
if (e.code !== "EEXIST") throw e;
|
|
3050
3137
|
if (!this.guardStale(guard)) return "wait";
|
|
3051
3138
|
const moved = `${guard}.${token}.stale`;
|
|
3052
3139
|
try {
|
|
3053
|
-
|
|
3140
|
+
fs8.renameSync(guard, moved);
|
|
3054
3141
|
} catch (err) {
|
|
3055
3142
|
if (err.code === "ENOENT") return "retry";
|
|
3056
3143
|
throw err;
|
|
3057
3144
|
}
|
|
3058
|
-
|
|
3145
|
+
fs8.rmSync(moved, { recursive: true, force: true });
|
|
3059
3146
|
return "retry";
|
|
3060
3147
|
}
|
|
3061
3148
|
try {
|
|
3062
|
-
|
|
3063
|
-
|
|
3149
|
+
fs8.writeFileSync(
|
|
3150
|
+
path12.join(guard, "holder"),
|
|
3064
3151
|
JSON.stringify({ pid: this.pid, at: this.now(), token })
|
|
3065
3152
|
);
|
|
3066
3153
|
this.beforeUnlink?.();
|
|
3067
3154
|
if (!this.stillStale(lock, held)) return "retry";
|
|
3068
3155
|
try {
|
|
3069
|
-
|
|
3156
|
+
fs8.unlinkSync(lock);
|
|
3070
3157
|
} catch (e) {
|
|
3071
3158
|
if (e.code !== "ENOENT") throw e;
|
|
3072
3159
|
}
|
|
3073
3160
|
return take() ? "taken" : "retry";
|
|
3074
3161
|
} finally {
|
|
3075
|
-
|
|
3162
|
+
fs8.rmSync(guard, { recursive: true, force: true });
|
|
3076
3163
|
}
|
|
3077
3164
|
}
|
|
3078
3165
|
/** Age of a lock file by its modification time on the file system's clock; 0 when it is gone. */
|
|
3079
3166
|
mtimeAgeMs(lock) {
|
|
3080
3167
|
try {
|
|
3081
|
-
return Date.now() -
|
|
3168
|
+
return Date.now() - fs8.statSync(lock).mtimeMs;
|
|
3082
3169
|
} catch {
|
|
3083
3170
|
return 0;
|
|
3084
3171
|
}
|
|
3085
3172
|
}
|
|
3086
3173
|
/** Unlinks the lock only when it still carries this holder's token; a lock it does not own is never removed. */
|
|
3087
3174
|
release(lock, token) {
|
|
3088
|
-
if (readHeld(lock)?.token === token)
|
|
3175
|
+
if (readHeld(lock)?.token === token) fs8.rmSync(lock, { force: true });
|
|
3089
3176
|
}
|
|
3090
3177
|
/** Every readable lease, in sequence order; a half-written file is skipped. */
|
|
3091
3178
|
list() {
|
|
3092
|
-
if (!
|
|
3093
|
-
return
|
|
3179
|
+
if (!fs8.existsSync(this.dir)) return [];
|
|
3180
|
+
return fs8.readdirSync(this.dir).filter((f) => f.endsWith(".json")).sort().flatMap((f) => {
|
|
3094
3181
|
try {
|
|
3095
3182
|
return [
|
|
3096
3183
|
JSON.parse(
|
|
3097
|
-
|
|
3184
|
+
fs8.readFileSync(path12.join(this.dir, f), "utf8")
|
|
3098
3185
|
)
|
|
3099
3186
|
];
|
|
3100
3187
|
} catch {
|
|
@@ -3107,21 +3194,21 @@ var LeaseStore = class {
|
|
|
3107
3194
|
return this.list().find((l) => l.id === id) ?? null;
|
|
3108
3195
|
}
|
|
3109
3196
|
nextSeq() {
|
|
3110
|
-
const f =
|
|
3197
|
+
const f = path12.join(this.dir, ".seq");
|
|
3111
3198
|
const onDisk = Math.max(
|
|
3112
3199
|
0,
|
|
3113
|
-
...
|
|
3200
|
+
...fs8.readdirSync(this.dir).filter((name2) => name2.endsWith(".json")).map((name2) => Number(name2.split("-")[0])).filter(Number.isFinite)
|
|
3114
3201
|
);
|
|
3115
|
-
const parsed =
|
|
3202
|
+
const parsed = fs8.existsSync(f) ? Number(fs8.readFileSync(f, "utf8")) : 0;
|
|
3116
3203
|
const n = Math.max(Number.isFinite(parsed) ? parsed : 0, onDisk);
|
|
3117
3204
|
const tmp = `${f}.${this.pid}.tmp`;
|
|
3118
|
-
|
|
3119
|
-
|
|
3205
|
+
fs8.writeFileSync(tmp, String(n + 1));
|
|
3206
|
+
fs8.renameSync(tmp, f);
|
|
3120
3207
|
return n + 1;
|
|
3121
3208
|
}
|
|
3122
3209
|
/** A new lease, pending (queued) or running (admitted at once), with `child_pid` the process running its start; call it under the lock. */
|
|
3123
3210
|
async create(input, state, opts = {}) {
|
|
3124
|
-
|
|
3211
|
+
fs8.mkdirSync(this.dir, { recursive: true });
|
|
3125
3212
|
const at = this.stamp();
|
|
3126
3213
|
const l = {
|
|
3127
3214
|
...input,
|
|
@@ -3203,7 +3290,7 @@ var LeaseStore = class {
|
|
|
3203
3290
|
for (const l of this.list()) {
|
|
3204
3291
|
if (!ENDED.has(l.state) || !l.ended_at) continue;
|
|
3205
3292
|
if (this.now() - Date.parse(l.ended_at) > maxAgeMs) {
|
|
3206
|
-
|
|
3293
|
+
fs8.rmSync(this.file(l), { force: true });
|
|
3207
3294
|
n++;
|
|
3208
3295
|
}
|
|
3209
3296
|
}
|
|
@@ -3221,7 +3308,7 @@ var EXIT_POLL_MS = 250;
|
|
|
3221
3308
|
function exitingAt(dir) {
|
|
3222
3309
|
try {
|
|
3223
3310
|
const v = JSON.parse(
|
|
3224
|
-
|
|
3311
|
+
fs9.readFileSync(path13.join(dir, WATCHER_NOTE), "utf8")
|
|
3225
3312
|
);
|
|
3226
3313
|
const at = typeof v.exiting_at === "string" ? Date.parse(v.exiting_at) : Number.NaN;
|
|
3227
3314
|
return Number.isFinite(at) ? at : null;
|
|
@@ -3230,7 +3317,7 @@ function exitingAt(dir) {
|
|
|
3230
3317
|
}
|
|
3231
3318
|
}
|
|
3232
3319
|
function watcherEntry(env = process.env, argv = process.argv) {
|
|
3233
|
-
if (env.MCPROC_CLI_ENTRY) return
|
|
3320
|
+
if (env.MCPROC_CLI_ENTRY) return path13.resolve(env.MCPROC_CLI_ENTRY);
|
|
3234
3321
|
const own2 = argv[1] ?? "";
|
|
3235
3322
|
const real = own2 ? realOf(own2) : "";
|
|
3236
3323
|
if (real.endsWith("cli.js")) return real;
|
|
@@ -3265,10 +3352,10 @@ async function lookAndLaunch(ctx, dir, leaving) {
|
|
|
3265
3352
|
if (await watcherOnline(ctx) && (leaving === null || exitingAt(dir) !== leaving))
|
|
3266
3353
|
return "online";
|
|
3267
3354
|
const entry = watcherEntry();
|
|
3268
|
-
const file =
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
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(
|
|
3272
3359
|
file,
|
|
3273
3360
|
JSON.stringify(
|
|
3274
3361
|
{
|
|
@@ -3304,7 +3391,7 @@ async function lookAndLaunch(ctx, dir, leaving) {
|
|
|
3304
3391
|
// src/core/leases/view.ts
|
|
3305
3392
|
async function leasesView(dir = stateDir(), ctx) {
|
|
3306
3393
|
const store = new LeaseStore(dir);
|
|
3307
|
-
if (
|
|
3394
|
+
if (fs10.existsSync(store.dir))
|
|
3308
3395
|
await store.withLock(() => store.prune()).catch(() => {
|
|
3309
3396
|
});
|
|
3310
3397
|
const leases = store.list().map((l) => ({ ...l, position: store.positionOf(l.id) }));
|
|
@@ -3359,17 +3446,12 @@ function cancelLease(id, dir = stateDir()) {
|
|
|
3359
3446
|
});
|
|
3360
3447
|
}
|
|
3361
3448
|
|
|
3362
|
-
// src/core/
|
|
3363
|
-
import
|
|
3364
|
-
import fs12 from "fs";
|
|
3365
|
-
import path16 from "path";
|
|
3366
|
-
|
|
3367
|
-
// src/core/actions.ts
|
|
3368
|
-
import path15 from "path";
|
|
3449
|
+
// src/core/stacks/collect.ts
|
|
3450
|
+
import os8 from "os";
|
|
3369
3451
|
|
|
3370
3452
|
// src/core/list.ts
|
|
3371
|
-
import
|
|
3372
|
-
import
|
|
3453
|
+
import fs11 from "fs";
|
|
3454
|
+
import path14 from "path";
|
|
3373
3455
|
|
|
3374
3456
|
// src/core/metrics.ts
|
|
3375
3457
|
function cpuPercent(pids, before, after, clkTck, intervalSec) {
|
|
@@ -3557,7 +3639,7 @@ function parseRead(stdout, id, offset, opts = {}) {
|
|
|
3557
3639
|
}
|
|
3558
3640
|
const skipped = offset !== null && offset >= 0 && size >= offset ? Math.max(0, start - offset) : 0;
|
|
3559
3641
|
const nextOffset = Math.max(start, size - partial);
|
|
3560
|
-
const
|
|
3642
|
+
const short2 = buf.length < size - ctxStart;
|
|
3561
3643
|
const anchor = hex && HEX.test(hex) ? { at: size, hex } : null;
|
|
3562
3644
|
return {
|
|
3563
3645
|
size,
|
|
@@ -3566,17 +3648,17 @@ function parseRead(stdout, id, offset, opts = {}) {
|
|
|
3566
3648
|
lines,
|
|
3567
3649
|
contextOnly,
|
|
3568
3650
|
nextOffset,
|
|
3569
|
-
short,
|
|
3651
|
+
short: short2,
|
|
3570
3652
|
anchor,
|
|
3571
|
-
nextAnchor: anchorAt(buf, nextOffset, { size, ctxStart, short, anchor })
|
|
3653
|
+
nextAnchor: anchorAt(buf, nextOffset, { size, ctxStart, short: short2, anchor })
|
|
3572
3654
|
};
|
|
3573
3655
|
}
|
|
3574
3656
|
function anchorAt(buf, at, read) {
|
|
3575
|
-
const { size, ctxStart, short, anchor } = read;
|
|
3657
|
+
const { size, ctxStart, short: short2, anchor } = read;
|
|
3576
3658
|
if (at === size) return anchor;
|
|
3577
3659
|
const n = Math.min(16, at);
|
|
3578
3660
|
const end = buf.length - (size - at);
|
|
3579
|
-
if (!anchor ||
|
|
3661
|
+
if (!anchor || short2 || n <= 0 || at - n < ctxStart || end - n < 0)
|
|
3580
3662
|
return null;
|
|
3581
3663
|
const tail = buf.subarray(end - n);
|
|
3582
3664
|
if (tail.includes(REPLACEMENT)) return null;
|
|
@@ -3720,8 +3802,8 @@ var LogMarks = class {
|
|
|
3720
3802
|
this.m.set(key, e);
|
|
3721
3803
|
}
|
|
3722
3804
|
};
|
|
3723
|
-
var sinceMark = (
|
|
3724
|
-
path:
|
|
3805
|
+
var sinceMark = (path23, r) => ({
|
|
3806
|
+
path: path23,
|
|
3725
3807
|
offset: r.nextOffset,
|
|
3726
3808
|
...r.nextAnchor ? { at: r.nextAnchor.at, anchor: r.nextAnchor.hex } : {}
|
|
3727
3809
|
});
|
|
@@ -3752,15 +3834,15 @@ async function readSince(fleet, names, opts) {
|
|
|
3752
3834
|
);
|
|
3753
3835
|
const files = targets.flatMap(
|
|
3754
3836
|
(t) => streams2.map((stream2) => {
|
|
3755
|
-
const
|
|
3837
|
+
const path23 = stream2 === "out" ? t.p.out_log : t.p.err_log;
|
|
3756
3838
|
const key = LogMarks.key(fleet.manifest.path, g.host, t.app);
|
|
3757
3839
|
const mark = spec.kind === "last" ? opts.marks?.get(key)?.[stream2] : void 0;
|
|
3758
|
-
const same = mark !== void 0 && mark.path ===
|
|
3840
|
+
const same = mark !== void 0 && mark.path === path23;
|
|
3759
3841
|
const anchor = same && mark.at !== void 0 && mark.anchor !== void 0 ? { at: mark.at, hex: mark.anchor } : null;
|
|
3760
3842
|
return {
|
|
3761
3843
|
app: t.app,
|
|
3762
3844
|
stream: stream2,
|
|
3763
|
-
path:
|
|
3845
|
+
path: path23,
|
|
3764
3846
|
key,
|
|
3765
3847
|
offset: same ? mark.offset : null,
|
|
3766
3848
|
anchor,
|
|
@@ -4193,38 +4275,14 @@ function urlFields(u) {
|
|
|
4193
4275
|
url_reason: null
|
|
4194
4276
|
};
|
|
4195
4277
|
}
|
|
4196
|
-
async function
|
|
4197
|
-
const withMetrics = opts.metrics !== "none";
|
|
4198
|
-
const cpuSample = (opts.cpu ?? "inline") === "inline" && withMetrics;
|
|
4199
|
-
const which = opts.urls ?? "check";
|
|
4200
|
-
const asked = which === "none" ? [] : Object.values(ctx.manifest.apps).flatMap(
|
|
4201
|
-
(a) => a.url !== null && (which === "check" || which.includes(a.name)) ? [{ name: a.name, url: a.url, url_check: a.url_check }] : []
|
|
4202
|
-
);
|
|
4203
|
-
const urls = [
|
|
4204
|
-
...new Set(
|
|
4205
|
-
asked.filter((a) => ctx.host.local || a.url_check !== "here").map((a) => a.url)
|
|
4206
|
-
)
|
|
4207
|
-
];
|
|
4208
|
-
const containers = withMetrics && opts.containers === "probe" ? [
|
|
4209
|
-
...new Set(
|
|
4210
|
-
Object.values(ctx.manifest.apps).flatMap((a) => a.containers)
|
|
4211
|
-
)
|
|
4212
|
-
] : [];
|
|
4278
|
+
async function probeHost(link, ask) {
|
|
4213
4279
|
const res = ensureOk(
|
|
4214
|
-
await
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
}),
|
|
4221
|
-
{
|
|
4222
|
-
env: ctx.pm2.env(),
|
|
4223
|
-
// each url adds its bounded lookup and request: many on a host whose resolver drops packets outlast the default
|
|
4224
|
-
timeoutMs: probeTimeoutMs(urls.length)
|
|
4225
|
-
}
|
|
4226
|
-
),
|
|
4227
|
-
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,
|
|
4228
4286
|
"probe"
|
|
4229
4287
|
);
|
|
4230
4288
|
const sampledAt = Date.now();
|
|
@@ -4232,9 +4290,9 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4232
4290
|
const tail = (res.stderr || res.stdout).trim().split("\n").slice(-5).join("\n");
|
|
4233
4291
|
throw new McprocError(
|
|
4234
4292
|
"TRANSPORT_FAILED",
|
|
4235
|
-
`the shell on ${
|
|
4293
|
+
`the shell on ${link.transport.describe()} did not run the probe (exit ${res.code}): ${tail}`,
|
|
4236
4294
|
{
|
|
4237
|
-
hint: `Check hosts.${
|
|
4295
|
+
hint: `Check hosts.${link.host.name}.shell (default /bin/bash -lc) exists on that host.`
|
|
4238
4296
|
}
|
|
4239
4297
|
);
|
|
4240
4298
|
}
|
|
@@ -4242,27 +4300,37 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4242
4300
|
if (data.jlistRc === 127)
|
|
4243
4301
|
throw new McprocError(
|
|
4244
4302
|
"PM2_NOT_FOUND",
|
|
4245
|
-
`pm2 not found on ${
|
|
4303
|
+
`pm2 not found on ${link.transport.describe()}`,
|
|
4246
4304
|
{ hint: PM2_NOT_FOUND_HINT }
|
|
4247
4305
|
);
|
|
4248
4306
|
if (data.jlistRc !== 0)
|
|
4249
4307
|
throw new McprocError(
|
|
4250
4308
|
"PM2_FAILED",
|
|
4251
|
-
`pm2 jlist exited ${data.jlistRc} on ${
|
|
4309
|
+
`pm2 jlist exited ${data.jlistRc} on ${link.transport.describe()}`,
|
|
4252
4310
|
{ details: res.stderr.slice(-500) }
|
|
4253
4311
|
);
|
|
4254
|
-
|
|
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);
|
|
4255
4323
|
const pids = appPids(
|
|
4256
|
-
|
|
4324
|
+
view.manifest.apps,
|
|
4257
4325
|
procs,
|
|
4258
4326
|
data.tree,
|
|
4259
|
-
|
|
4327
|
+
view.manifest.project
|
|
4260
4328
|
);
|
|
4261
4329
|
const metricOpts = opts.prev ? { prev: opts.prev.sample, intervalSec: (sampledAt - opts.prev.at) / 1e3 } : {};
|
|
4262
4330
|
const metrics = {};
|
|
4263
4331
|
for (const [name2, set] of pids)
|
|
4264
4332
|
metrics[name2] = treeMetrics(set, data.stats, data.listeners, metricOpts);
|
|
4265
|
-
for (const app of Object.values(
|
|
4333
|
+
for (const app of Object.values(view.manifest.apps)) {
|
|
4266
4334
|
const m = metrics[app.name];
|
|
4267
4335
|
if (!m || app.containers.length === 0) continue;
|
|
4268
4336
|
const mib = app.containers.reduce(
|
|
@@ -4278,13 +4346,13 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4278
4346
|
}
|
|
4279
4347
|
const urlChecks = await settleUrls(
|
|
4280
4348
|
asked,
|
|
4281
|
-
{ host:
|
|
4349
|
+
{ host: view.host.name, local: view.host.local },
|
|
4282
4350
|
data,
|
|
4283
|
-
|
|
4351
|
+
view.hereCheck
|
|
4284
4352
|
);
|
|
4285
4353
|
return {
|
|
4286
4354
|
procs,
|
|
4287
|
-
ports: joinPorts(
|
|
4355
|
+
ports: joinPorts(view.manifest.apps, procs, data, view.manifest.project),
|
|
4288
4356
|
listeners: data.listeners,
|
|
4289
4357
|
tree: data.tree,
|
|
4290
4358
|
stats: data.stats,
|
|
@@ -4293,9 +4361,30 @@ async function snapshot(ctx, opts = {}) {
|
|
|
4293
4361
|
urlChecks,
|
|
4294
4362
|
zone: data.zone,
|
|
4295
4363
|
memory: data.memory,
|
|
4296
|
-
leases:
|
|
4364
|
+
leases: view.host.local && opts.leases !== "none" ? leasesCovering(stateDir(), view.manifest) : []
|
|
4297
4365
|
};
|
|
4298
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
|
+
}
|
|
4299
4388
|
var SIGNAL_EXITS = /* @__PURE__ */ new Set([130, 143]);
|
|
4300
4389
|
function statusWord(p, app) {
|
|
4301
4390
|
if (!p) return "not-started";
|
|
@@ -4430,8 +4519,8 @@ var depView = (d) => ({
|
|
|
4430
4519
|
timeout_s: d.timeoutMs / 1e3
|
|
4431
4520
|
});
|
|
4432
4521
|
function describeProject(m) {
|
|
4433
|
-
const readmePath =
|
|
4434
|
-
const readme =
|
|
4522
|
+
const readmePath = path14.join(m.root, ".mcproc", "README.md");
|
|
4523
|
+
const readme = fs11.existsSync(readmePath) ? fs11.readFileSync(readmePath, "utf8") : null;
|
|
4435
4524
|
return {
|
|
4436
4525
|
project: m.project,
|
|
4437
4526
|
root: m.root,
|
|
@@ -4462,9 +4551,12 @@ function describeProject(m) {
|
|
|
4462
4551
|
};
|
|
4463
4552
|
}
|
|
4464
4553
|
|
|
4554
|
+
// src/core/actions.ts
|
|
4555
|
+
import path16 from "path";
|
|
4556
|
+
|
|
4465
4557
|
// src/core/logfollow.ts
|
|
4466
4558
|
import { open } from "fs/promises";
|
|
4467
|
-
import
|
|
4559
|
+
import path15 from "path";
|
|
4468
4560
|
|
|
4469
4561
|
// src/core/logwatch.ts
|
|
4470
4562
|
import { watch as fsWatch } from "fs";
|
|
@@ -4807,8 +4899,8 @@ function hostStreamFor(t) {
|
|
|
4807
4899
|
var MAX_PENDING = 65536;
|
|
4808
4900
|
var MAX_READY = 1e4;
|
|
4809
4901
|
function chunkScript(reqs) {
|
|
4810
|
-
return reqs.map(({ path:
|
|
4811
|
-
const f = shq(
|
|
4902
|
+
return reqs.map(({ path: path23, off, anchor }, i) => {
|
|
4903
|
+
const f = shq(path23);
|
|
4812
4904
|
const tail = `st=$((sz>${HISTORY_BYTES}?sz-${HISTORY_BYTES}:0))`;
|
|
4813
4905
|
const replaced = off !== null && anchor ? ` || [ "${hex16(f, String(off))}" != '${anchor}' ]` : "";
|
|
4814
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`;
|
|
@@ -4958,8 +5050,8 @@ var LogFollower = class {
|
|
|
4958
5050
|
}
|
|
4959
5051
|
const byDir = /* @__PURE__ */ new Map();
|
|
4960
5052
|
for (const p of [this.paths.out, this.paths.err]) {
|
|
4961
|
-
const dir =
|
|
4962
|
-
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)));
|
|
4963
5055
|
}
|
|
4964
5056
|
for (const [dir, names] of byDir)
|
|
4965
5057
|
this.unwatch.push(
|
|
@@ -5332,6 +5424,9 @@ function detectReloads(input, rules, opts) {
|
|
|
5332
5424
|
return [...t.push(input, 0), ...t.flush()];
|
|
5333
5425
|
}
|
|
5334
5426
|
|
|
5427
|
+
// src/mcp/render.ts
|
|
5428
|
+
import os7 from "os";
|
|
5429
|
+
|
|
5335
5430
|
// src/core/logstream.ts
|
|
5336
5431
|
var WAKE_GAP_MS = 50;
|
|
5337
5432
|
function signals() {
|
|
@@ -5527,6 +5622,135 @@ async function streamLogs(sources, opts, deps2) {
|
|
|
5527
5622
|
}
|
|
5528
5623
|
}
|
|
5529
5624
|
|
|
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
|
+
|
|
5530
5754
|
// src/mcp/render.ts
|
|
5531
5755
|
function fmtUptime(ms) {
|
|
5532
5756
|
if (ms === null) return "-";
|
|
@@ -5556,13 +5780,84 @@ function fmtPorts(p) {
|
|
|
5556
5780
|
for (const u of p.undeclared) parts.push(`:${u} (undeclared)`);
|
|
5557
5781
|
return parts.length ? parts.join(", ") : "-";
|
|
5558
5782
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
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))))
|
|
5562
5840
|
);
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
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");
|
|
5566
5861
|
}
|
|
5567
5862
|
function fmtCpu(c) {
|
|
5568
5863
|
return c === null ? "-" : `${c.toFixed(1)}%`;
|
|
@@ -5590,7 +5885,7 @@ function fmtRestarts(r) {
|
|
|
5590
5885
|
const n = r.reloads;
|
|
5591
5886
|
return n && n.count > 0 ? `${r.restarts} (+${n.partial ? "\u2265" : ""}${n.count})` : String(r.restarts);
|
|
5592
5887
|
}
|
|
5593
|
-
function renderLs(rows, host, errors = []) {
|
|
5888
|
+
function renderLs(rows, host, errors = [], width) {
|
|
5594
5889
|
const hosts = [...new Set(rows.map((r) => r.host))];
|
|
5595
5890
|
const multi = hosts.length > 1;
|
|
5596
5891
|
const head = multi ? `hosts: ${hosts.join(", ")}` : `host: ${hosts[0] ?? host}`;
|
|
@@ -5599,40 +5894,45 @@ function renderLs(rows, host, errors = []) {
|
|
|
5599
5894
|
rows.filter((r) => r.error).map((r) => [r.host, `${r.host}: ${r.error}`])
|
|
5600
5895
|
).values()
|
|
5601
5896
|
];
|
|
5897
|
+
const o = multi ? 1 : 0;
|
|
5898
|
+
const cap = (s, n) => width === void 0 ? cut2(s, n) : s;
|
|
5602
5899
|
return [
|
|
5603
5900
|
head,
|
|
5604
|
-
table(
|
|
5901
|
+
table(
|
|
5605
5902
|
[
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
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
|
+
])
|
|
5619
5933
|
],
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
...multi ? [r.host] : [],
|
|
5623
|
-
r.kind,
|
|
5624
|
-
r.status,
|
|
5625
|
-
r.pid === null ? "-" : String(r.pid),
|
|
5626
|
-
fmtUptime(r.uptime_ms),
|
|
5627
|
-
fmtRestarts(r),
|
|
5628
|
-
r.memory ? fmtBytes(r.memory) : "-",
|
|
5629
|
-
r.status === "online" ? fmtCpu(r.cpu) : "-",
|
|
5630
|
-
fmtPorts(r.ports),
|
|
5631
|
-
fmtUrl(r),
|
|
5632
|
-
r.runtime ? cut2(r.runtime, 32) : "-",
|
|
5633
|
-
cut2(r.description.split("\n")[0], 40)
|
|
5634
|
-
])
|
|
5635
|
-
]),
|
|
5934
|
+
{ width, flex: [8 + o, 9 + o, 10 + o, 11 + o], mode: "wrap" }
|
|
5935
|
+
),
|
|
5636
5936
|
...unreachable,
|
|
5637
5937
|
...errors.map((e) => `${e.host}: ${e.message}`)
|
|
5638
5938
|
].join("\n");
|
|
@@ -5649,36 +5949,42 @@ function startNote(rows) {
|
|
|
5649
5949
|
);
|
|
5650
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` : "";
|
|
5651
5951
|
}
|
|
5652
|
-
function renderPorts(rep, host) {
|
|
5952
|
+
function renderPorts(rep, host, width) {
|
|
5653
5953
|
if (rep.listeners.length === 0)
|
|
5654
5954
|
return `host: ${host}
|
|
5655
5955
|
${rep.port ? `nothing listens on port ${rep.port}` : "no listening sockets visible"}`;
|
|
5656
5956
|
return `host: ${host}
|
|
5657
|
-
` + table(
|
|
5658
|
-
[
|
|
5659
|
-
|
|
5660
|
-
l
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
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
|
+
);
|
|
5669
5972
|
}
|
|
5670
|
-
function renderHosts(info) {
|
|
5671
|
-
return table(
|
|
5672
|
-
[
|
|
5673
|
-
|
|
5674
|
-
h
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
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
|
+
);
|
|
5682
5988
|
}
|
|
5683
5989
|
function fmtEnv(e) {
|
|
5684
5990
|
const kind = e.container === "none" ? "no container" : `container (${e.container})`;
|
|
@@ -5826,7 +6132,7 @@ function fmtDeps(deps2) {
|
|
|
5826
6132
|
(d) => d.condition === "started" ? d.app : `${d.app} (${d.condition})`
|
|
5827
6133
|
).join(", ");
|
|
5828
6134
|
}
|
|
5829
|
-
function renderProject(d) {
|
|
6135
|
+
function renderProject(d, width) {
|
|
5830
6136
|
const L = [`project: ${d.project} (${d.root})`];
|
|
5831
6137
|
const w = d.mcproc.manifest;
|
|
5832
6138
|
if (w)
|
|
@@ -5850,7 +6156,7 @@ function renderProject(d) {
|
|
|
5850
6156
|
L.push(` ${p}: ${apps.join(", ")}`);
|
|
5851
6157
|
L.push(
|
|
5852
6158
|
"hosts:",
|
|
5853
|
-
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")
|
|
5854
6160
|
);
|
|
5855
6161
|
if (d.readme) L.push("", "README:", d.readme.trim());
|
|
5856
6162
|
return L.join("\n");
|
|
@@ -5873,9 +6179,9 @@ function incidentLines(i, opts = {}) {
|
|
|
5873
6179
|
function renderIncidents(a, opts = {}) {
|
|
5874
6180
|
const tail = [];
|
|
5875
6181
|
if (a.more > 0) {
|
|
5876
|
-
const
|
|
6182
|
+
const short2 = a.more_by === "size" ? " to keep this answer short" : "";
|
|
5877
6183
|
tail.push(
|
|
5878
|
-
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)`
|
|
5879
6185
|
);
|
|
5880
6186
|
}
|
|
5881
6187
|
for (const s of a.skipped)
|
|
@@ -5924,7 +6230,7 @@ function queuedNote(rows) {
|
|
|
5924
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)`;
|
|
5925
6231
|
}).join("\n");
|
|
5926
6232
|
}
|
|
5927
|
-
function renderLeases(v) {
|
|
6233
|
+
function renderLeases(v, width) {
|
|
5928
6234
|
const head = [
|
|
5929
6235
|
`state: ${v.state_dir}`,
|
|
5930
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)",
|
|
@@ -5944,23 +6250,26 @@ function renderLeases(v) {
|
|
|
5944
6250
|
]);
|
|
5945
6251
|
return [
|
|
5946
6252
|
...head,
|
|
5947
|
-
table(
|
|
6253
|
+
table(
|
|
5948
6254
|
[
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
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
|
|
5958
6267
|
],
|
|
5959
|
-
|
|
5960
|
-
|
|
6268
|
+
{ width, flex: [4, 7], mode: "wrap" }
|
|
6269
|
+
)
|
|
5961
6270
|
].join("\n");
|
|
5962
6271
|
}
|
|
5963
|
-
function renderMutation(rows, fallback, act, waits = []) {
|
|
6272
|
+
function renderMutation(rows, fallback, act, waits = [], width) {
|
|
5964
6273
|
const bad3 = rows.filter((r) => r.error);
|
|
5965
6274
|
const notes = [
|
|
5966
6275
|
act === "start" || act === "restart" ? startNote(rows) : "",
|
|
@@ -5971,7 +6280,7 @@ function renderMutation(rows, fallback, act, waits = []) {
|
|
|
5971
6280
|
const head = bad3.length ? `${act} partly failed: ${bad3.map((r) => `${r.app} (${r.host}): ${r.error}`).join("; ")}
|
|
5972
6281
|
` : "";
|
|
5973
6282
|
return {
|
|
5974
|
-
text: head + renderLs(rows, fallback) + notes.map((n) => `
|
|
6283
|
+
text: head + renderLs(rows, fallback, [], width) + notes.map((n) => `
|
|
5975
6284
|
${n}`).join(""),
|
|
5976
6285
|
failed: bad3.length > 0
|
|
5977
6286
|
};
|
|
@@ -6025,22 +6334,123 @@ function renderWorktreeAdded(r) {
|
|
|
6025
6334
|
L.push(`next: ${r.next}`);
|
|
6026
6335
|
return L.join("\n");
|
|
6027
6336
|
}
|
|
6028
|
-
function renderWorktrees(rows) {
|
|
6029
|
-
return table(
|
|
6030
|
-
[
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
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
|
+
);
|
|
6040
6352
|
}
|
|
6041
6353
|
function renderWorktreeRemoved(r) {
|
|
6042
6354
|
return `removed ${r.path}: slot ${r.slot} is free, branch ${r.branch ?? "(detached)"} kept; stopped ${r.stopped.length ? r.stopped.join(", ") : "nothing"}`;
|
|
6043
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
|
+
}
|
|
6044
6454
|
|
|
6045
6455
|
// src/core/wait.ts
|
|
6046
6456
|
var WAIT_TIMEOUT_DEFAULT_MS = 6e4;
|
|
@@ -6416,7 +6826,7 @@ function assertNoCollision(ctx, procs, names) {
|
|
|
6416
6826
|
const name2 = pm2Name(ctx.manifest.project, n);
|
|
6417
6827
|
const expectedCwd = appCwd(ctx.host, ctx.manifest.apps[n]);
|
|
6418
6828
|
const other = procs.find(
|
|
6419
|
-
(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)
|
|
6420
6830
|
);
|
|
6421
6831
|
if (other && other.namespace !== ctx.manifest.project) {
|
|
6422
6832
|
throw new McprocError(
|
|
@@ -6443,7 +6853,7 @@ async function shipEcosystem(ctx) {
|
|
|
6443
6853
|
const eco = buildEcosystem(ctx.manifest, ctx.host);
|
|
6444
6854
|
const file = ecosystemPath(ctx.host);
|
|
6445
6855
|
const dir = generatedDir(ctx.host);
|
|
6446
|
-
const gi =
|
|
6856
|
+
const gi = path16.posix.join(path16.posix.dirname(dir), ".gitignore");
|
|
6447
6857
|
const cmd = `mkdir -p ${shq(dir)} && { [ -e ${shq(gi)} ] || printf '*\\n' > ${shq(gi)}; } && cat > ${shq(file)}`;
|
|
6448
6858
|
const res = ensureOk(
|
|
6449
6859
|
await ctx.transport.exec(cmd, { stdin: JSON.stringify(eco, null, 2) }),
|
|
@@ -6620,7 +7030,7 @@ function admit(opts) {
|
|
|
6620
7030
|
}
|
|
6621
7031
|
|
|
6622
7032
|
// src/core/leases/siblings.ts
|
|
6623
|
-
import
|
|
7033
|
+
import fs12 from "fs";
|
|
6624
7034
|
var MIB = 1024 * 1024;
|
|
6625
7035
|
var repoOf = (m) => m.repo;
|
|
6626
7036
|
function registryOf(m, dir = stateDir()) {
|
|
@@ -6630,7 +7040,7 @@ function ownsManifest(root) {
|
|
|
6630
7040
|
const found = findManifestPath(root);
|
|
6631
7041
|
if (!found) return false;
|
|
6632
7042
|
try {
|
|
6633
|
-
return
|
|
7043
|
+
return fs12.realpathSync(found.root) === fs12.realpathSync(root);
|
|
6634
7044
|
} catch {
|
|
6635
7045
|
return false;
|
|
6636
7046
|
}
|
|
@@ -6648,7 +7058,7 @@ async function listCheckout(root, spawner) {
|
|
|
6648
7058
|
async function measureSiblings(m, deps2) {
|
|
6649
7059
|
const out = /* @__PURE__ */ new Map();
|
|
6650
7060
|
if (!deps2.registry) return out;
|
|
6651
|
-
const exists = deps2.exists ?? ((p) =>
|
|
7061
|
+
const exists = deps2.exists ?? ((p) => fs12.existsSync(p));
|
|
6652
7062
|
const read = await Promise.all(
|
|
6653
7063
|
siblingsOf(deps2.registry, m.root).filter(({ entry }) => exists(entry.path)).map(async ({ slot, entry }) => {
|
|
6654
7064
|
try {
|
|
@@ -7039,10 +7449,18 @@ function failedRows(fleet, r, e) {
|
|
|
7039
7449
|
);
|
|
7040
7450
|
}
|
|
7041
7451
|
async function actOn(fleet, target, act, opts = {}) {
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
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]);
|
|
7461
|
+
const names = act === "start" ? fleet.withDeps(named) : named;
|
|
7462
|
+
const runs = fleet.runs(names);
|
|
7463
|
+
assertWritableRuns(fleet, runs, named);
|
|
7046
7464
|
if (act === "start") assertLocalBudgets(fleet, runs);
|
|
7047
7465
|
const rows = [];
|
|
7048
7466
|
let failed = null;
|
|
@@ -7081,6 +7499,7 @@ async function actOn(fleet, target, act, opts = {}) {
|
|
|
7081
7499
|
r.apps,
|
|
7082
7500
|
{
|
|
7083
7501
|
target,
|
|
7502
|
+
// a start of a list is refused above
|
|
7084
7503
|
host: fleet.requested ?? null,
|
|
7085
7504
|
reload: opts.reload,
|
|
7086
7505
|
note: opts.note,
|
|
@@ -7202,7 +7621,531 @@ async function describeFleetApp(fleet, app) {
|
|
|
7202
7621
|
};
|
|
7203
7622
|
}
|
|
7204
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);
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
8126
|
+
return notes;
|
|
8127
|
+
}
|
|
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
|
+
};
|
|
8144
|
+
|
|
7205
8145
|
// src/core/leases/watcher.ts
|
|
8146
|
+
import { spawn as spawn2 } from "child_process";
|
|
8147
|
+
import fs13 from "fs";
|
|
8148
|
+
import path17 from "path";
|
|
7206
8149
|
async function moveIf(store, id, from, to, patch = {}, also = () => true) {
|
|
7207
8150
|
return store.withLock(async () => {
|
|
7208
8151
|
const cur = store.get(id);
|
|
@@ -7395,13 +8338,13 @@ async function leaveIdle(deps2) {
|
|
|
7395
8338
|
return store.withLock(() => {
|
|
7396
8339
|
if (store.list().some((l) => l.state === "pending" || l.state === "starting"))
|
|
7397
8340
|
return false;
|
|
7398
|
-
const file =
|
|
8341
|
+
const file = path17.join(path17.dirname(store.dir), WATCHER_NOTE);
|
|
7399
8342
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
7400
|
-
|
|
8343
|
+
fs13.writeFileSync(
|
|
7401
8344
|
tmp,
|
|
7402
8345
|
JSON.stringify({ exiting_at: new Date(deps2.now()).toISOString() })
|
|
7403
8346
|
);
|
|
7404
|
-
|
|
8347
|
+
fs13.renameSync(tmp, file);
|
|
7405
8348
|
return true;
|
|
7406
8349
|
});
|
|
7407
8350
|
}
|
|
@@ -7512,7 +8455,7 @@ async function watchLeases(deps2, opts = {}) {
|
|
|
7512
8455
|
}
|
|
7513
8456
|
function leaseDirWatch(dir) {
|
|
7514
8457
|
return (cb) => {
|
|
7515
|
-
const w =
|
|
8458
|
+
const w = fs13.watch(dir, (_e, name2) => {
|
|
7516
8459
|
if (!name2?.startsWith(".")) cb();
|
|
7517
8460
|
});
|
|
7518
8461
|
return () => w.close();
|
|
@@ -7875,8 +8818,8 @@ async function runExec(ctx, opts, deps2) {
|
|
|
7875
8818
|
|
|
7876
8819
|
// src/core/worktree.ts
|
|
7877
8820
|
import { execFile } from "child_process";
|
|
7878
|
-
import
|
|
7879
|
-
import
|
|
8821
|
+
import fs14 from "fs";
|
|
8822
|
+
import path18 from "path";
|
|
7880
8823
|
var gitRunner = (args, cwd) => new Promise((resolve) => {
|
|
7881
8824
|
execFile(
|
|
7882
8825
|
"git",
|
|
@@ -7901,7 +8844,7 @@ var gitOk = async (git, args, cwd) => {
|
|
|
7901
8844
|
};
|
|
7902
8845
|
var present = (p) => {
|
|
7903
8846
|
try {
|
|
7904
|
-
|
|
8847
|
+
fs14.lstatSync(p);
|
|
7905
8848
|
return true;
|
|
7906
8849
|
} catch {
|
|
7907
8850
|
return false;
|
|
@@ -7909,15 +8852,15 @@ var present = (p) => {
|
|
|
7909
8852
|
};
|
|
7910
8853
|
async function mainCheckout(root, git) {
|
|
7911
8854
|
const common = (await gitOk(git, ["rev-parse", "--git-common-dir"], root)).trim();
|
|
7912
|
-
return
|
|
8855
|
+
return fs14.realpathSync(path18.dirname(path18.resolve(root, common)));
|
|
7913
8856
|
}
|
|
7914
8857
|
function worktreeDir(m, main) {
|
|
7915
|
-
return m.worktree.dir ?
|
|
8858
|
+
return m.worktree.dir ? path18.resolve(main, m.worktree.dir) : `${main}.worktrees`;
|
|
7916
8859
|
}
|
|
7917
8860
|
function installCommand(m, root) {
|
|
7918
8861
|
if (m.worktree.install === false) return null;
|
|
7919
8862
|
if (typeof m.worktree.install === "string") return m.worktree.install;
|
|
7920
|
-
const has = (f) =>
|
|
8863
|
+
const has = (f) => fs14.existsSync(path18.join(root, f));
|
|
7921
8864
|
if (has("bun.lock") || has("bun.lockb")) return "bun install";
|
|
7922
8865
|
if (has("yarn.lock")) return "yarn install";
|
|
7923
8866
|
if (has("pnpm-lock.yaml")) return "pnpm install";
|
|
@@ -7925,22 +8868,22 @@ function installCommand(m, root) {
|
|
|
7925
8868
|
return null;
|
|
7926
8869
|
}
|
|
7927
8870
|
function planCopies(main, target, copy, link, fsDeps = {}) {
|
|
7928
|
-
const exists = fsDeps.exists ?? ((p) =>
|
|
7929
|
-
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 }));
|
|
7930
8873
|
const copies = [];
|
|
7931
8874
|
const missing = [];
|
|
7932
8875
|
for (const entry of copy) {
|
|
7933
|
-
const hits2 = /[*?[]/.test(entry) ? glob(entry, main).sort() : exists(
|
|
8876
|
+
const hits2 = /[*?[]/.test(entry) ? glob(entry, main).sort() : exists(path18.join(main, entry)) ? [entry] : [];
|
|
7934
8877
|
if (hits2.length === 0) {
|
|
7935
8878
|
missing.push(entry);
|
|
7936
8879
|
continue;
|
|
7937
8880
|
}
|
|
7938
8881
|
for (const h of hits2)
|
|
7939
|
-
copies.push({ from:
|
|
8882
|
+
copies.push({ from: path18.join(main, h), to: path18.join(target, h) });
|
|
7940
8883
|
}
|
|
7941
8884
|
const links = link.map((l) => ({
|
|
7942
|
-
from:
|
|
7943
|
-
to:
|
|
8885
|
+
from: path18.join(main, l),
|
|
8886
|
+
to: path18.join(target, l)
|
|
7944
8887
|
}));
|
|
7945
8888
|
return { copies, links, missing };
|
|
7946
8889
|
}
|
|
@@ -7956,7 +8899,7 @@ function ownManifest(target, log, instead) {
|
|
|
7956
8899
|
}
|
|
7957
8900
|
}
|
|
7958
8901
|
function excludePattern(rel) {
|
|
7959
|
-
const p =
|
|
8902
|
+
const p = path18.posix.normalize(rel.split(path18.sep).join("/")).replace(/\/+$/, "");
|
|
7960
8903
|
if (p === "." || p === "" || p === ".." || p.startsWith("../") || p.startsWith("/"))
|
|
7961
8904
|
return null;
|
|
7962
8905
|
return `/${p.replace(/[\\*?[\]]/g, (c) => `\\${c}`).replace(/ $/, "\\ ")}`;
|
|
@@ -7980,13 +8923,13 @@ async function excludeLinks(target, links, git, log) {
|
|
|
7980
8923
|
);
|
|
7981
8924
|
return [];
|
|
7982
8925
|
}
|
|
7983
|
-
const file =
|
|
7984
|
-
|
|
7985
|
-
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") : "";
|
|
7986
8929
|
const lines = new Set(had.split("\n"));
|
|
7987
8930
|
const add2 = patterns.filter((p) => !lines.has(p));
|
|
7988
8931
|
if (add2.length)
|
|
7989
|
-
|
|
8932
|
+
fs14.appendFileSync(
|
|
7990
8933
|
file,
|
|
7991
8934
|
`${had.length && !had.endsWith("\n") ? "\n" : ""}${add2.join("\n")}
|
|
7992
8935
|
`
|
|
@@ -8002,7 +8945,7 @@ function registryFor(m, main, mainBranch, deps2) {
|
|
|
8002
8945
|
});
|
|
8003
8946
|
const { registry, freed } = reclaimGone(
|
|
8004
8947
|
read,
|
|
8005
|
-
deps2.exists ?? ((p) =>
|
|
8948
|
+
deps2.exists ?? ((p) => fs14.existsSync(p))
|
|
8006
8949
|
);
|
|
8007
8950
|
for (const s of freed) deps2.log(`slot ${s} reclaimed: its checkout is gone`);
|
|
8008
8951
|
return { file, registry };
|
|
@@ -8032,12 +8975,12 @@ async function worktreeList(main, git) {
|
|
|
8032
8975
|
}
|
|
8033
8976
|
function realAhead(p) {
|
|
8034
8977
|
const rest = [];
|
|
8035
|
-
let d =
|
|
8036
|
-
while (!present(d) &&
|
|
8037
|
-
rest.unshift(
|
|
8038
|
-
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);
|
|
8039
8982
|
}
|
|
8040
|
-
return
|
|
8983
|
+
return path18.join(realOf(d), ...rest);
|
|
8041
8984
|
}
|
|
8042
8985
|
var shPath = (p) => /^[\w./@%+:-]+$/.test(p) ? p : shq(p);
|
|
8043
8986
|
async function worktreeAdd(m, opts, deps2) {
|
|
@@ -8074,7 +9017,7 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8074
9017
|
}
|
|
8075
9018
|
);
|
|
8076
9019
|
const target = realAhead(
|
|
8077
|
-
|
|
9020
|
+
path18.join(worktreeDir(m, main), ...opts.branch.split("/"))
|
|
8078
9021
|
);
|
|
8079
9022
|
const taken = () => new McprocError("WORKTREE_EXISTS", `${target} exists already`, {
|
|
8080
9023
|
hint: "Remove it, or pick another branch name."
|
|
@@ -8097,9 +9040,9 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8097
9040
|
`project slug ${project2} is the slug of slot ${k} (${e.path})`,
|
|
8098
9041
|
{ hint: "Pass another project slug." }
|
|
8099
9042
|
);
|
|
8100
|
-
|
|
9043
|
+
fs14.mkdirSync(path18.dirname(target), { recursive: true });
|
|
8101
9044
|
try {
|
|
8102
|
-
|
|
9045
|
+
fs14.mkdirSync(target);
|
|
8103
9046
|
} catch (e) {
|
|
8104
9047
|
if (e.code === "EEXIST") throw taken();
|
|
8105
9048
|
throw e;
|
|
@@ -8134,7 +9077,7 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8134
9077
|
);
|
|
8135
9078
|
} catch (e) {
|
|
8136
9079
|
try {
|
|
8137
|
-
|
|
9080
|
+
fs14.rmdirSync(target);
|
|
8138
9081
|
} catch {
|
|
8139
9082
|
}
|
|
8140
9083
|
await store.withLock(() => {
|
|
@@ -8154,8 +9097,8 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8154
9097
|
repo: main,
|
|
8155
9098
|
vars: (probe ?? m).slot !== null ? { slot } : void 0
|
|
8156
9099
|
});
|
|
8157
|
-
const gi =
|
|
8158
|
-
if (!present(gi))
|
|
9100
|
+
const gi = path18.join(target, ".mcproc", ".gitignore");
|
|
9101
|
+
if (!present(gi)) fs14.writeFileSync(gi, "*\n");
|
|
8159
9102
|
const own2 = probe ? ownManifest(target, deps2.log, "the copies and the install") : null;
|
|
8160
9103
|
const dm = own2 ?? m;
|
|
8161
9104
|
const only = (xs, not) => [...new Set(xs)].filter((e) => !not.includes(e));
|
|
@@ -8185,15 +9128,15 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8185
9128
|
missing: [...ownPlan.missing, ...extraPlan.missing]
|
|
8186
9129
|
};
|
|
8187
9130
|
const fromOverride = [
|
|
8188
|
-
...addedCopies.map((c) =>
|
|
8189
|
-
...addedLinks.map((l) =>
|
|
9131
|
+
...addedCopies.map((c) => path18.relative(main, c.from)),
|
|
9132
|
+
...addedLinks.map((l) => path18.relative(main, l.from))
|
|
8190
9133
|
];
|
|
8191
9134
|
let step = "";
|
|
8192
9135
|
try {
|
|
8193
9136
|
for (const c of plan.copies) {
|
|
8194
9137
|
step = `copying ${c.from}`;
|
|
8195
|
-
|
|
8196
|
-
|
|
9138
|
+
fs14.mkdirSync(path18.dirname(c.to), { recursive: true });
|
|
9139
|
+
fs14.cpSync(c.from, c.to, {
|
|
8197
9140
|
recursive: true,
|
|
8198
9141
|
force: false,
|
|
8199
9142
|
errorOnExist: false
|
|
@@ -8201,8 +9144,8 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8201
9144
|
}
|
|
8202
9145
|
for (const l of plan.links) {
|
|
8203
9146
|
step = `linking ${l.from}`;
|
|
8204
|
-
|
|
8205
|
-
if (!present(l.to))
|
|
9147
|
+
fs14.mkdirSync(path18.dirname(l.to), { recursive: true });
|
|
9148
|
+
if (!present(l.to)) fs14.symlinkSync(l.from, l.to);
|
|
8206
9149
|
}
|
|
8207
9150
|
} catch (e) {
|
|
8208
9151
|
throw new McprocError(
|
|
@@ -8217,7 +9160,7 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8217
9160
|
deps2.log(`copy: ${miss} is not in ${main}, skipped`);
|
|
8218
9161
|
const excluded = await excludeLinks(
|
|
8219
9162
|
target,
|
|
8220
|
-
plan.links.map((l) =>
|
|
9163
|
+
plan.links.map((l) => path18.relative(target, l.to)),
|
|
8221
9164
|
deps2.git,
|
|
8222
9165
|
deps2.log
|
|
8223
9166
|
);
|
|
@@ -8246,8 +9189,8 @@ async function worktreeAdd(m, opts, deps2) {
|
|
|
8246
9189
|
project,
|
|
8247
9190
|
repo: main,
|
|
8248
9191
|
manifest: own2 ? "worktree" : "main",
|
|
8249
|
-
copied: plan.copies.map((c) =>
|
|
8250
|
-
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)),
|
|
8251
9194
|
from_override: fromOverride,
|
|
8252
9195
|
excluded,
|
|
8253
9196
|
missing: plan.missing,
|
|
@@ -8264,7 +9207,7 @@ async function worktreeLs(m, deps2) {
|
|
|
8264
9207
|
branch: await currentBranch(main, deps2.git),
|
|
8265
9208
|
project: m.base_project
|
|
8266
9209
|
});
|
|
8267
|
-
const exists = deps2.exists ?? ((p) =>
|
|
9210
|
+
const exists = deps2.exists ?? ((p) => fs14.existsSync(p));
|
|
8268
9211
|
const branches = await worktreeList(main, deps2.git);
|
|
8269
9212
|
const procs = await deps2.jlist();
|
|
8270
9213
|
const leases = new LeaseStore(deps2.stateDir).list();
|
|
@@ -8314,7 +9257,7 @@ async function worktreeRemove(m, ref, opts, deps2) {
|
|
|
8314
9257
|
if (!(await worktreeList(main, deps2.git)).has(realOf(e.path))) {
|
|
8315
9258
|
let empty = false;
|
|
8316
9259
|
try {
|
|
8317
|
-
empty =
|
|
9260
|
+
empty = fs14.readdirSync(e.path).length === 0;
|
|
8318
9261
|
} catch {
|
|
8319
9262
|
}
|
|
8320
9263
|
if (!empty)
|
|
@@ -8323,7 +9266,7 @@ async function worktreeRemove(m, ref, opts, deps2) {
|
|
|
8323
9266
|
`${e.path} (slot ${k}) is no git worktree of ${main}`,
|
|
8324
9267
|
{ hint: "Look at what is in it, remove it by hand, then worktree ls." }
|
|
8325
9268
|
);
|
|
8326
|
-
|
|
9269
|
+
fs14.rmdirSync(e.path);
|
|
8327
9270
|
deps2.log(`${e.path} was an empty folder git never filled: removed`);
|
|
8328
9271
|
await free();
|
|
8329
9272
|
return { path: e.path, slot: Number(k), branch: e.branch, stopped: [] };
|
|
@@ -8344,14 +9287,15 @@ async function worktreeRemove(m, ref, opts, deps2) {
|
|
|
8344
9287
|
main
|
|
8345
9288
|
);
|
|
8346
9289
|
const top = realOf(worktreeDir(m, main));
|
|
8347
|
-
for (let d =
|
|
9290
|
+
for (let d = path18.dirname(e.path); d.startsWith(`${top}${path18.sep}`); d = path18.dirname(d)) {
|
|
8348
9291
|
try {
|
|
8349
|
-
|
|
9292
|
+
fs14.rmdirSync(d);
|
|
8350
9293
|
} catch {
|
|
8351
9294
|
break;
|
|
8352
9295
|
}
|
|
8353
9296
|
}
|
|
8354
9297
|
await free();
|
|
9298
|
+
forgetProject(e.project, e.path, deps2.stateDir);
|
|
8355
9299
|
return { path: e.path, slot: Number(k), branch: e.branch, stopped };
|
|
8356
9300
|
}
|
|
8357
9301
|
async function stopCheckout(root, opts) {
|
|
@@ -8411,10 +9355,10 @@ async function defaultWorktreeDeps(ctx, io, opts = {}) {
|
|
|
8411
9355
|
}
|
|
8412
9356
|
|
|
8413
9357
|
// src/core/incidents/store.ts
|
|
8414
|
-
import
|
|
9358
|
+
import fs15 from "fs";
|
|
8415
9359
|
import { createHash as createHash3, randomUUID } from "crypto";
|
|
8416
9360
|
import net from "net";
|
|
8417
|
-
import
|
|
9361
|
+
import path19 from "path";
|
|
8418
9362
|
|
|
8419
9363
|
// src/core/incidents/taken.ts
|
|
8420
9364
|
function takenPoints(s) {
|
|
@@ -8484,16 +9428,16 @@ async function pollIncidents(fleet, names, opts = {}) {
|
|
|
8484
9428
|
};
|
|
8485
9429
|
const files = targets.flatMap(
|
|
8486
9430
|
(t) => STREAMS2.map((stream) => {
|
|
8487
|
-
const
|
|
9431
|
+
const path23 = stream === "out" ? t.p.out_log : t.p.err_log;
|
|
8488
9432
|
const mark = t.prev?.[stream];
|
|
8489
|
-
const same = mark && mark.path ===
|
|
9433
|
+
const same = mark && mark.path === path23;
|
|
8490
9434
|
const emptied = opts.restart?.[markKey(g.host, t.app)]?.[stream];
|
|
8491
|
-
const restarted = !!emptied && (emptied.includes(
|
|
9435
|
+
const restarted = !!emptied && (emptied.includes(path23) || emptied.includes("*"));
|
|
8492
9436
|
const anchor = !restarted && same && mark.at !== void 0 && mark.anchor !== void 0 ? { at: mark.at, hex: mark.anchor } : null;
|
|
8493
9437
|
return {
|
|
8494
9438
|
t,
|
|
8495
9439
|
stream,
|
|
8496
|
-
path:
|
|
9440
|
+
path: path23,
|
|
8497
9441
|
offset: restarted ? 0 : same ? mark.offset : null,
|
|
8498
9442
|
anchor,
|
|
8499
9443
|
restarted
|
|
@@ -8757,7 +9701,7 @@ function processEvents(app, host, p, prev, errLines) {
|
|
|
8757
9701
|
}
|
|
8758
9702
|
|
|
8759
9703
|
// src/core/incidents/store.ts
|
|
8760
|
-
var fileClearKey = (key, stream,
|
|
9704
|
+
var fileClearKey = (key, stream, path23) => `${key}\0${stream}\0${path23}`;
|
|
8761
9705
|
var countedClearKey = (key) => `${key}\0counted`;
|
|
8762
9706
|
var isAppClearKey = (k) => !k.includes("\0");
|
|
8763
9707
|
function parseFileClearKey(k) {
|
|
@@ -8766,13 +9710,13 @@ function parseFileClearKey(k) {
|
|
|
8766
9710
|
return { key, stream, path: rest.join("\0") };
|
|
8767
9711
|
}
|
|
8768
9712
|
var LOCK_WAIT_MS2 = 3e4;
|
|
8769
|
-
var storePath = (manifestPath) =>
|
|
9713
|
+
var storePath = (manifestPath) => path19.join(path19.dirname(manifestPath), "incidents.json");
|
|
8770
9714
|
var pad = (n) => String(n).padStart(2, "0");
|
|
8771
9715
|
var localStamp = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
8772
9716
|
function readStore(file) {
|
|
8773
9717
|
try {
|
|
8774
9718
|
const s = JSON.parse(
|
|
8775
|
-
|
|
9719
|
+
fs15.readFileSync(file, "utf8")
|
|
8776
9720
|
);
|
|
8777
9721
|
const obj = (o) => typeof o === "object" && o !== null ? o : {};
|
|
8778
9722
|
if (s && s.v === 1 && typeof s.apps === "object" && s.apps !== null)
|
|
@@ -8787,12 +9731,12 @@ function readStore(file) {
|
|
|
8787
9731
|
return { v: 1, apps: {}, cleared: {}, clears: {} };
|
|
8788
9732
|
}
|
|
8789
9733
|
function lockAddress(file) {
|
|
8790
|
-
let dir =
|
|
9734
|
+
let dir = path19.dirname(path19.resolve(file));
|
|
8791
9735
|
try {
|
|
8792
|
-
dir =
|
|
9736
|
+
dir = fs15.realpathSync(dir);
|
|
8793
9737
|
} catch {
|
|
8794
9738
|
}
|
|
8795
|
-
const id = createHash3("sha256").update(
|
|
9739
|
+
const id = createHash3("sha256").update(path19.join(dir, path19.basename(file))).digest();
|
|
8796
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 };
|
|
8797
9741
|
}
|
|
8798
9742
|
function tryLock(addr) {
|
|
@@ -8828,8 +9772,8 @@ async function withStore(file, fn, waitMs = LOCK_WAIT_MS2) {
|
|
|
8828
9772
|
try {
|
|
8829
9773
|
const { result, next } = await fn(readStore(file));
|
|
8830
9774
|
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
8831
|
-
|
|
8832
|
-
|
|
9775
|
+
fs15.writeFileSync(tmp, JSON.stringify(next));
|
|
9776
|
+
fs15.renameSync(tmp, file);
|
|
8833
9777
|
return result;
|
|
8834
9778
|
} finally {
|
|
8835
9779
|
await new Promise((r) => lock.close(r));
|
|
@@ -8896,8 +9840,8 @@ async function clearLogs(fleet, names, file = storePath(fleet.manifest.path), no
|
|
|
8896
9840
|
for (const f of await flushLogs(await fleet.ctx(g.host), g.apps)) {
|
|
8897
9841
|
const key = markKey(g.host, f.app);
|
|
8898
9842
|
const prev = s.apps[key];
|
|
8899
|
-
const mark = (
|
|
8900
|
-
path:
|
|
9843
|
+
const mark = (path23, was) => f.failed.includes(path23) ? was : {
|
|
9844
|
+
path: path23,
|
|
8901
9845
|
offset: 0,
|
|
8902
9846
|
...was?.stamp ? { stamp: was.stamp } : {}
|
|
8903
9847
|
};
|
|
@@ -8915,15 +9859,15 @@ async function clearLogs(fleet, names, file = storePath(fleet.manifest.path), no
|
|
|
8915
9859
|
const ck = countedClearKey(key);
|
|
8916
9860
|
next.clears[ck] = (s.clears[ck] ?? 0) + 1;
|
|
8917
9861
|
}
|
|
8918
|
-
for (const [stream,
|
|
9862
|
+
for (const [stream, path23] of [
|
|
8919
9863
|
["out", f.proc.out_log],
|
|
8920
9864
|
["err", f.proc.err_log]
|
|
8921
9865
|
]) {
|
|
8922
|
-
if (f.failed.includes(
|
|
8923
|
-
const fk = fileClearKey(key, stream,
|
|
9866
|
+
if (f.failed.includes(path23)) continue;
|
|
9867
|
+
const fk = fileClearKey(key, stream, path23);
|
|
8924
9868
|
next.clears[fk] = (s.clears[fk] ?? 0) + 1;
|
|
8925
9869
|
}
|
|
8926
|
-
const freed = (
|
|
9870
|
+
const freed = (path23, bytes) => f.failed.includes(path23) ? 0 : bytes;
|
|
8927
9871
|
out.push({
|
|
8928
9872
|
app: f.app,
|
|
8929
9873
|
host: g.host,
|
|
@@ -9169,22 +10113,22 @@ async function doctor(m, hostName, spawner, hereCheck) {
|
|
|
9169
10113
|
}
|
|
9170
10114
|
|
|
9171
10115
|
// src/core/init.ts
|
|
9172
|
-
import
|
|
9173
|
-
import
|
|
10116
|
+
import fs17 from "fs";
|
|
10117
|
+
import path21 from "path";
|
|
9174
10118
|
import { parse as parseYaml3 } from "yaml";
|
|
9175
10119
|
|
|
9176
10120
|
// src/core/turbo.ts
|
|
9177
|
-
import
|
|
9178
|
-
import
|
|
10121
|
+
import fs16 from "fs";
|
|
10122
|
+
import path20 from "path";
|
|
9179
10123
|
function readJson2(p) {
|
|
9180
10124
|
try {
|
|
9181
|
-
return JSON.parse(
|
|
10125
|
+
return JSON.parse(fs16.readFileSync(p, "utf8"));
|
|
9182
10126
|
} catch {
|
|
9183
10127
|
return null;
|
|
9184
10128
|
}
|
|
9185
10129
|
}
|
|
9186
10130
|
function readTurbo(root) {
|
|
9187
|
-
const raw = readJson2(
|
|
10131
|
+
const raw = readJson2(path20.join(root, "turbo.json"));
|
|
9188
10132
|
const tasks = raw?.tasks ?? raw?.pipeline;
|
|
9189
10133
|
if (!tasks || typeof tasks !== "object" || Array.isArray(tasks)) return null;
|
|
9190
10134
|
const names = Object.keys(tasks);
|
|
@@ -9205,14 +10149,14 @@ function isDistLib(pkg2) {
|
|
|
9205
10149
|
function loadWorkspace(root, dirs) {
|
|
9206
10150
|
const out = [];
|
|
9207
10151
|
for (const dir of dirs) {
|
|
9208
|
-
const pkg2 = readJson2(
|
|
10152
|
+
const pkg2 = readJson2(path20.join(dir, "package.json"));
|
|
9209
10153
|
if (!pkg2) continue;
|
|
9210
10154
|
const deps2 = /* @__PURE__ */ new Set();
|
|
9211
10155
|
for (const k of ["dependencies", "devDependencies", "peerDependencies"])
|
|
9212
10156
|
for (const d of Object.keys(pkg2[k] ?? {})) deps2.add(d);
|
|
9213
10157
|
out.push({
|
|
9214
|
-
name: String(pkg2.name ??
|
|
9215
|
-
dir:
|
|
10158
|
+
name: String(pkg2.name ?? path20.basename(dir)),
|
|
10159
|
+
dir: path20.relative(root, dir) || ".",
|
|
9216
10160
|
scripts: { ...pkg2.scripts ?? {} },
|
|
9217
10161
|
deps: [...deps2],
|
|
9218
10162
|
distLib: isDistLib(pkg2)
|
|
@@ -9245,11 +10189,11 @@ function turboBin(pm) {
|
|
|
9245
10189
|
function libsFilters(libs, globParents, all = []) {
|
|
9246
10190
|
const folded = new Set(libs.map((l) => l.name));
|
|
9247
10191
|
const shared = new Set(
|
|
9248
|
-
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))
|
|
9249
10193
|
);
|
|
9250
10194
|
const out = [];
|
|
9251
10195
|
for (const l of libs) {
|
|
9252
|
-
const parent =
|
|
10196
|
+
const parent = path20.dirname(l.dir);
|
|
9253
10197
|
const f = globParents.includes(parent) && !shared.has(parent) ? `--filter='./${parent}/*'` : `--filter=${l.name}`;
|
|
9254
10198
|
if (!out.includes(f)) out.push(f);
|
|
9255
10199
|
}
|
|
@@ -9276,9 +10220,9 @@ function sanitizeName(s) {
|
|
|
9276
10220
|
return n || "app";
|
|
9277
10221
|
}
|
|
9278
10222
|
function pmFor(root) {
|
|
9279
|
-
if (
|
|
9280
|
-
if (
|
|
9281
|
-
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")))
|
|
9282
10226
|
return "bun run";
|
|
9283
10227
|
return "npm run";
|
|
9284
10228
|
}
|
|
@@ -9286,12 +10230,12 @@ function walk(dir, maxDepth, depth = 0) {
|
|
|
9286
10230
|
const out = [];
|
|
9287
10231
|
let entries;
|
|
9288
10232
|
try {
|
|
9289
|
-
entries =
|
|
10233
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
9290
10234
|
} catch {
|
|
9291
10235
|
return out;
|
|
9292
10236
|
}
|
|
9293
10237
|
for (const e of entries) {
|
|
9294
|
-
const p =
|
|
10238
|
+
const p = path21.join(dir, e.name);
|
|
9295
10239
|
if (e.isDirectory()) {
|
|
9296
10240
|
if (SKIP_DIRS.has(e.name) || e.name.startsWith(".")) continue;
|
|
9297
10241
|
if (depth < maxDepth) out.push(...walk(p, maxDepth, depth + 1));
|
|
@@ -9372,7 +10316,7 @@ function vitePort(dir, section2, config) {
|
|
|
9372
10316
|
let raw;
|
|
9373
10317
|
for (const f of config ? [config] : VITE_CONFIGS) {
|
|
9374
10318
|
try {
|
|
9375
|
-
raw =
|
|
10319
|
+
raw = fs17.readFileSync(path21.resolve(dir, f), "utf8");
|
|
9376
10320
|
break;
|
|
9377
10321
|
} catch {
|
|
9378
10322
|
}
|
|
@@ -9396,9 +10340,9 @@ function guessPort(cmd, dir) {
|
|
|
9396
10340
|
const m = /(?:\bPORT=|--port[= ]|\s-p\s)(\d{2,5})\b/.exec(cmd);
|
|
9397
10341
|
if (m) return Number(m[1]);
|
|
9398
10342
|
for (const f of [".env", ".env.local", ".env.development"]) {
|
|
9399
|
-
const p =
|
|
9400
|
-
if (!
|
|
9401
|
-
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"));
|
|
9402
10346
|
if (e) return Number(e[1]);
|
|
9403
10347
|
}
|
|
9404
10348
|
if (NEXT_RE.test(cmd)) return 3e3;
|
|
@@ -9409,10 +10353,10 @@ function guessPort(cmd, dir) {
|
|
|
9409
10353
|
const p = vitePort(dir, section2, config);
|
|
9410
10354
|
return p === null ? void 0 : p ?? (section2 === "server" ? 5173 : 4173);
|
|
9411
10355
|
}
|
|
9412
|
-
const src =
|
|
9413
|
-
if (
|
|
10356
|
+
const src = path21.join(dir, "src");
|
|
10357
|
+
if (fs17.existsSync(src)) {
|
|
9414
10358
|
for (const f of walk(src, 3).filter((x) => /\.(ts|js|mjs|cjs|tsx|jsx)$/.test(x)).slice(0, 200)) {
|
|
9415
|
-
const text =
|
|
10359
|
+
const text = fs17.readFileSync(f, "utf8");
|
|
9416
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(
|
|
9417
10361
|
text
|
|
9418
10362
|
) ?? /\bPORT\s*\)?\s*(?:\?\?|\|\|)\s*["']?(\d{2,5})\b/.exec(text);
|
|
@@ -9435,10 +10379,10 @@ var notAwaited = (lib) => {
|
|
|
9435
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`;
|
|
9436
10380
|
};
|
|
9437
10381
|
function packageCandidates(root, pkgDir, pm, turbo) {
|
|
9438
|
-
const pkg2 = readJson2(
|
|
10382
|
+
const pkg2 = readJson2(path21.join(pkgDir, "package.json"));
|
|
9439
10383
|
if (!pkg2?.scripts) return [];
|
|
9440
|
-
const rel =
|
|
9441
|
-
const appName = pkgAppName(String(pkg2.name ??
|
|
10384
|
+
const rel = path21.relative(root, pkgDir) || ".";
|
|
10385
|
+
const appName = pkgAppName(String(pkg2.name ?? path21.basename(pkgDir)));
|
|
9442
10386
|
const prefix = rel === "." ? "" : `${appName}-`;
|
|
9443
10387
|
const file = `${rel === "." ? "" : `${rel}/`}package.json`;
|
|
9444
10388
|
const out = [];
|
|
@@ -9481,21 +10425,21 @@ function expandWorkspaces(root, patterns) {
|
|
|
9481
10425
|
for (const pat of patterns) {
|
|
9482
10426
|
const glob = /^(.*?)\/\*\*?$/.exec(pat);
|
|
9483
10427
|
if (glob) {
|
|
9484
|
-
const base =
|
|
9485
|
-
globParents.push(
|
|
10428
|
+
const base = path21.join(root, glob[1]);
|
|
10429
|
+
globParents.push(path21.relative(root, base));
|
|
9486
10430
|
let names = [];
|
|
9487
10431
|
try {
|
|
9488
|
-
names =
|
|
10432
|
+
names = fs17.readdirSync(base);
|
|
9489
10433
|
} catch {
|
|
9490
10434
|
}
|
|
9491
10435
|
names.sort();
|
|
9492
10436
|
for (const d of names)
|
|
9493
|
-
if (
|
|
9494
|
-
dirs.add(
|
|
10437
|
+
if (fs17.existsSync(path21.join(base, d, "package.json")))
|
|
10438
|
+
dirs.add(path21.join(base, d));
|
|
9495
10439
|
} else if (pat.includes("*")) {
|
|
9496
10440
|
unsupported.push(pat);
|
|
9497
|
-
} else if (
|
|
9498
|
-
dirs.add(
|
|
10441
|
+
} else if (fs17.existsSync(path21.join(root, pat, "package.json"))) {
|
|
10442
|
+
dirs.add(path21.join(root, pat));
|
|
9499
10443
|
}
|
|
9500
10444
|
}
|
|
9501
10445
|
return { dirs: [...dirs], unsupported, globParents };
|
|
@@ -9504,7 +10448,7 @@ function libsBuilders(folded, all, globParents, pm, version) {
|
|
|
9504
10448
|
const bin = turboBin(pm);
|
|
9505
10449
|
const ui = version === 2 ? " --ui=stream" : "";
|
|
9506
10450
|
const filters = libsFilters(folded, globParents, all).join(" ");
|
|
9507
|
-
const parents = [...new Set(folded.map((p) =>
|
|
10451
|
+
const parents = [...new Set(folded.map((p) => path21.dirname(p.dir)))].join(
|
|
9508
10452
|
", "
|
|
9509
10453
|
);
|
|
9510
10454
|
const libs = {
|
|
@@ -9563,12 +10507,12 @@ function discover(root) {
|
|
|
9563
10507
|
const candidates = [];
|
|
9564
10508
|
const notes = [];
|
|
9565
10509
|
const pm = pmFor(root);
|
|
9566
|
-
const pkg2 = readJson2(
|
|
10510
|
+
const pkg2 = readJson2(path21.join(root, "package.json"));
|
|
9567
10511
|
const ws = Array.isArray(pkg2?.workspaces) ? [...pkg2.workspaces] : Array.isArray(pkg2?.workspaces?.packages) ? [...pkg2.workspaces.packages] : [];
|
|
9568
|
-
const pnpmWs =
|
|
9569
|
-
if (
|
|
10512
|
+
const pnpmWs = path21.join(root, "pnpm-workspace.yaml");
|
|
10513
|
+
if (fs17.existsSync(pnpmWs)) {
|
|
9570
10514
|
try {
|
|
9571
|
-
const y = parseYaml3(
|
|
10515
|
+
const y = parseYaml3(fs17.readFileSync(pnpmWs, "utf8"));
|
|
9572
10516
|
if (Array.isArray(y?.packages)) ws.push(...y.packages);
|
|
9573
10517
|
} catch {
|
|
9574
10518
|
notes.push("pnpm-workspace.yaml could not be parsed");
|
|
@@ -9576,8 +10520,8 @@ function discover(root) {
|
|
|
9576
10520
|
}
|
|
9577
10521
|
const expanded = expandWorkspaces(root, ws);
|
|
9578
10522
|
const turbo = readTurbo(root);
|
|
9579
|
-
const turboFile =
|
|
9580
|
-
if (!turbo &&
|
|
10523
|
+
const turboFile = path21.join(root, "turbo.json");
|
|
10524
|
+
if (!turbo && fs17.existsSync(turboFile) && readJson2(turboFile) === null)
|
|
9581
10525
|
notes.push(
|
|
9582
10526
|
"turbo.json could not be parsed (comments are not read); drafted without turbo's tasks"
|
|
9583
10527
|
);
|
|
@@ -9603,7 +10547,7 @@ function discover(root) {
|
|
|
9603
10547
|
}
|
|
9604
10548
|
candidates.push(...packageCandidates(root, root, pm));
|
|
9605
10549
|
for (const dir of expanded.dirs) {
|
|
9606
|
-
const p = liveTask ? pkgs.find((x) => x.dir ===
|
|
10550
|
+
const p = liveTask ? pkgs.find((x) => x.dir === path21.relative(root, dir)) : void 0;
|
|
9607
10551
|
candidates.push(
|
|
9608
10552
|
...packageCandidates(
|
|
9609
10553
|
root,
|
|
@@ -9624,20 +10568,20 @@ function discover(root) {
|
|
|
9624
10568
|
`workspace pattern "${pat}" is not expanded by init; add its packages' scripts by hand`
|
|
9625
10569
|
);
|
|
9626
10570
|
for (const f of walk(root, 4).filter(
|
|
9627
|
-
(p) =>
|
|
10571
|
+
(p) => path21.basename(p) === "project.json"
|
|
9628
10572
|
)) {
|
|
9629
10573
|
const pj = readJson2(f);
|
|
9630
10574
|
if (!pj?.targets) continue;
|
|
9631
|
-
const name2 = String(pj.name ??
|
|
10575
|
+
const name2 = String(pj.name ?? path21.basename(path21.dirname(f)));
|
|
9632
10576
|
for (const t of ["serve", "dev", "build"]) {
|
|
9633
10577
|
if (!pj.targets[t]) continue;
|
|
9634
10578
|
candidates.push({
|
|
9635
10579
|
name: sanitizeName(`${name2}-${t}`),
|
|
9636
|
-
source: `${
|
|
10580
|
+
source: `${path21.relative(root, f)}:${t}`,
|
|
9637
10581
|
run: `npx nx run ${name2}:${t}${t === "build" ? " --watch" : ""}`,
|
|
9638
10582
|
cwd: ".",
|
|
9639
10583
|
kind: t === "build" ? "build" : "server",
|
|
9640
|
-
port: t === "build" ? void 0 : guessPort("",
|
|
10584
|
+
port: t === "build" ? void 0 : guessPort("", path21.dirname(f))
|
|
9641
10585
|
});
|
|
9642
10586
|
}
|
|
9643
10587
|
}
|
|
@@ -9652,7 +10596,7 @@ function discover(root) {
|
|
|
9652
10596
|
kind: guessKind(t, "")
|
|
9653
10597
|
});
|
|
9654
10598
|
}
|
|
9655
|
-
const eco = readJson2(
|
|
10599
|
+
const eco = readJson2(path21.join(root, "ecosystem.json"));
|
|
9656
10600
|
if (Array.isArray(eco?.apps)) {
|
|
9657
10601
|
for (const a of eco.apps) {
|
|
9658
10602
|
if (!a?.name) continue;
|
|
@@ -9673,14 +10617,14 @@ function discover(root) {
|
|
|
9673
10617
|
"ecosystem.config.cjs",
|
|
9674
10618
|
"ecosystem.config.mjs"
|
|
9675
10619
|
]) {
|
|
9676
|
-
if (
|
|
10620
|
+
if (fs17.existsSync(path21.join(root, f)))
|
|
9677
10621
|
notes.push(
|
|
9678
10622
|
`${f} found: JavaScript ecosystem files are not executed by init; port its apps by hand`
|
|
9679
10623
|
);
|
|
9680
10624
|
}
|
|
9681
|
-
const procfile =
|
|
9682
|
-
if (
|
|
9683
|
-
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")) {
|
|
9684
10628
|
const m = /^\s*([A-Za-z0-9_-]+):\s*(.+)$/.exec(line);
|
|
9685
10629
|
if (m) {
|
|
9686
10630
|
const kind = guessKind(m[1], m[2]);
|
|
@@ -9814,20 +10758,20 @@ function localReadonly(text) {
|
|
|
9814
10758
|
}
|
|
9815
10759
|
}
|
|
9816
10760
|
function initProject(rootIn, opts = {}) {
|
|
9817
|
-
const root =
|
|
9818
|
-
if (!
|
|
10761
|
+
const root = path21.resolve(rootIn);
|
|
10762
|
+
if (!fs17.existsSync(root))
|
|
9819
10763
|
throw new McprocError(
|
|
9820
10764
|
"MANIFEST_NOT_FOUND",
|
|
9821
10765
|
`Project directory does not exist: ${root}`
|
|
9822
10766
|
);
|
|
9823
|
-
const dir =
|
|
9824
|
-
const file =
|
|
9825
|
-
if (
|
|
10767
|
+
const dir = path21.join(root, ".mcproc");
|
|
10768
|
+
const file = path21.join(root, MANIFEST_REL);
|
|
10769
|
+
if (fs17.existsSync(file) && !opts.force) {
|
|
9826
10770
|
throw new McprocError("ALREADY_EXISTS", `${file} already exists`, {
|
|
9827
10771
|
hint: "Overwrite the draft with force (README and .gitignore are kept), or keep it and adopt the running apps' ports into it."
|
|
9828
10772
|
});
|
|
9829
10773
|
}
|
|
9830
|
-
if (
|
|
10774
|
+
if (fs17.existsSync(file) && localReadonly(fs17.readFileSync(file, "utf8"))) {
|
|
9831
10775
|
throw new McprocError(
|
|
9832
10776
|
"READONLY_HOST",
|
|
9833
10777
|
`${file} declares the local host readonly; init does not replace it, not even with force`,
|
|
@@ -9836,20 +10780,20 @@ function initProject(rootIn, opts = {}) {
|
|
|
9836
10780
|
}
|
|
9837
10781
|
);
|
|
9838
10782
|
}
|
|
9839
|
-
|
|
10783
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
9840
10784
|
const created = [];
|
|
9841
|
-
const gi =
|
|
9842
|
-
if (!
|
|
9843
|
-
|
|
10785
|
+
const gi = path21.join(dir, ".gitignore");
|
|
10786
|
+
if (!fs17.existsSync(gi) || fs17.readFileSync(gi, "utf8").trim() !== "*") {
|
|
10787
|
+
fs17.writeFileSync(gi, "*\n");
|
|
9844
10788
|
created.push(gi);
|
|
9845
10789
|
}
|
|
9846
|
-
const readme =
|
|
9847
|
-
if (!
|
|
9848
|
-
|
|
10790
|
+
const readme = path21.join(dir, "README.md");
|
|
10791
|
+
if (!fs17.existsSync(readme)) {
|
|
10792
|
+
fs17.writeFileSync(readme, README_TEMPLATE);
|
|
9849
10793
|
created.push(readme);
|
|
9850
10794
|
}
|
|
9851
10795
|
const d = discover(root);
|
|
9852
|
-
|
|
10796
|
+
fs17.writeFileSync(file, renderDraft(root, defaultProject(root), d));
|
|
9853
10797
|
created.push(file);
|
|
9854
10798
|
const by_source = {};
|
|
9855
10799
|
for (const c of d.candidates) {
|
|
@@ -9871,7 +10815,7 @@ function initProject(rootIn, opts = {}) {
|
|
|
9871
10815
|
}
|
|
9872
10816
|
|
|
9873
10817
|
// src/core/adopt.ts
|
|
9874
|
-
import
|
|
10818
|
+
import fs18 from "fs";
|
|
9875
10819
|
import { isMap, isScalar, isSeq, parse, parseDocument } from "yaml";
|
|
9876
10820
|
var EPHEMERAL_FROM = 32768;
|
|
9877
10821
|
function planAdoption(rows, m) {
|
|
@@ -10154,7 +11098,7 @@ async function adoptPorts(fleet) {
|
|
|
10154
11098
|
hint: "Edit the manifest by hand, or remove hosts.local.readonly first."
|
|
10155
11099
|
}
|
|
10156
11100
|
);
|
|
10157
|
-
const before =
|
|
11101
|
+
const before = fs18.readFileSync(m.path, "utf8");
|
|
10158
11102
|
const current = parseManifest(before, m.root, m.path, loadOverride(m.root));
|
|
10159
11103
|
const { rows, errors } = await listFleet(fleet, fleet.select(["all"]), {
|
|
10160
11104
|
cpu: "none",
|
|
@@ -10184,13 +11128,13 @@ async function adoptPorts(fleet) {
|
|
|
10184
11128
|
}
|
|
10185
11129
|
);
|
|
10186
11130
|
}
|
|
10187
|
-
if (
|
|
11131
|
+
if (fs18.readFileSync(m.path, "utf8") !== before)
|
|
10188
11132
|
throw new McprocError(
|
|
10189
11133
|
"ALREADY_EXISTS",
|
|
10190
11134
|
`${m.path} changed while the ports were being probed; nothing written`,
|
|
10191
11135
|
{ hint: "Run the adoption again." }
|
|
10192
11136
|
);
|
|
10193
|
-
|
|
11137
|
+
fs18.writeFileSync(m.path, splice.text);
|
|
10194
11138
|
return {
|
|
10195
11139
|
path: m.path,
|
|
10196
11140
|
written: true,
|
|
@@ -10647,6 +11591,28 @@ function registerReadTools(server, deps2) {
|
|
|
10647
11591
|
};
|
|
10648
11592
|
})
|
|
10649
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
|
+
);
|
|
10650
11616
|
server.registerTool(
|
|
10651
11617
|
"incidents",
|
|
10652
11618
|
{
|
|
@@ -11180,7 +12146,8 @@ function initialState(p) {
|
|
|
11180
12146
|
incidentsCleared: null,
|
|
11181
12147
|
incidentsErrors: [],
|
|
11182
12148
|
incidentsTaken: {},
|
|
11183
|
-
hideTaken: false
|
|
12149
|
+
hideTaken: false,
|
|
12150
|
+
toStacks: p.toStacks ?? false
|
|
11184
12151
|
};
|
|
11185
12152
|
}
|
|
11186
12153
|
function visibleIncidents(s) {
|
|
@@ -11416,8 +12383,9 @@ function keyIntent(key, s, now = Date.now()) {
|
|
|
11416
12383
|
}
|
|
11417
12384
|
case "q":
|
|
11418
12385
|
case "C-c":
|
|
11419
|
-
case "escape":
|
|
11420
12386
|
return { kind: "quit" };
|
|
12387
|
+
case "escape":
|
|
12388
|
+
return s.toStacks ? { kind: "back" } : { kind: "quit" };
|
|
11421
12389
|
default:
|
|
11422
12390
|
return null;
|
|
11423
12391
|
}
|
|
@@ -11459,126 +12427,8 @@ function flatLineOf(app, l) {
|
|
|
11459
12427
|
}
|
|
11460
12428
|
var flatMemo = null;
|
|
11461
12429
|
|
|
11462
|
-
// src/monit/screentext.ts
|
|
11463
|
-
import { charWidth, chars } from "@pm2/blessed/lib/unicode.js";
|
|
11464
|
-
var segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
11465
|
-
var EMOJI_PRESENTATION = new RegExp("\\p{Emoji_Presentation}", "u");
|
|
11466
|
-
var PICTOGRAPH = new RegExp("\\p{Extended_Pictographic}", "u");
|
|
11467
|
-
var REGIONAL = new RegExp("\\p{Regional_Indicator}", "u");
|
|
11468
|
-
var KEYCAP = /^[0-9#*]\uFE0F?\u20E3$/;
|
|
11469
|
-
var INVISIBLE = /[\p{Cc}\p{Cf}]/u;
|
|
11470
|
-
var LINE_BREAK = /\r\n|[\n\v\f\u0085\u2028\u2029]/g;
|
|
11471
|
-
var MARK2 = new RegExp("\\p{M}", "u");
|
|
11472
|
-
var SPACING_MARK = new RegExp("\\p{Mc}", "u");
|
|
11473
|
-
var BLESSED_WIDE = new RegExp(chars.wide.source);
|
|
11474
|
-
var NARROW = {
|
|
11475
|
-
"\u2705": "\u2714",
|
|
11476
|
-
"\u274C": "\u2716",
|
|
11477
|
-
"\u274E": "\u2716",
|
|
11478
|
-
"\u2757": "!",
|
|
11479
|
-
"\u2755": "!",
|
|
11480
|
-
"\u2753": "?",
|
|
11481
|
-
"\u2754": "?"
|
|
11482
|
-
};
|
|
11483
|
-
var TAB = 8;
|
|
11484
|
-
function displayWidth(s) {
|
|
11485
|
-
let w = 0;
|
|
11486
|
-
for (const ch of s.includes("\x1B") ? s.replace(SGR_RE, "") : s)
|
|
11487
|
-
w += charWidth(ch.codePointAt(0)) === 2 ? 2 : 1;
|
|
11488
|
-
return w;
|
|
11489
|
-
}
|
|
11490
|
-
function columns(s) {
|
|
11491
|
-
let w = 0;
|
|
11492
|
-
for (const ch of s) w += charWidth(ch.codePointAt(0));
|
|
11493
|
-
return w;
|
|
11494
|
-
}
|
|
11495
|
-
function keepMark(ch, cp) {
|
|
11496
|
-
return cp <= 65535 && charWidth(cp) === (SPACING_MARK.test(ch) ? 1 : 0);
|
|
11497
|
-
}
|
|
11498
|
-
function cluster(c) {
|
|
11499
|
-
if (c === " ") return c;
|
|
11500
|
-
const first = String.fromCodePoint(c.codePointAt(0));
|
|
11501
|
-
if (REGIONAL.test(first))
|
|
11502
|
-
return [...c].filter((ch) => REGIONAL.test(ch)).map((ch) => String.fromCharCode(ch.codePointAt(0) - 127462 + 65)).join("");
|
|
11503
|
-
if (KEYCAP.test(c)) return c[0];
|
|
11504
|
-
if (PICTOGRAPH.test(first) || EMOJI_PRESENTATION.test(first)) {
|
|
11505
|
-
if (NARROW[first]) return NARROW[first];
|
|
11506
|
-
return !EMOJI_PRESENTATION.test(first) && first.length === 1 ? first : "?";
|
|
11507
|
-
}
|
|
11508
|
-
let out = "";
|
|
11509
|
-
for (const ch of c) {
|
|
11510
|
-
if (INVISIBLE.test(ch)) continue;
|
|
11511
|
-
const cp = ch.codePointAt(0);
|
|
11512
|
-
if (MARK2.test(ch)) {
|
|
11513
|
-
if (keepMark(ch, cp)) out += ch;
|
|
11514
|
-
continue;
|
|
11515
|
-
}
|
|
11516
|
-
if (cp > 65535 || cp >= 55296 && cp <= 57343) {
|
|
11517
|
-
out += "?";
|
|
11518
|
-
continue;
|
|
11519
|
-
}
|
|
11520
|
-
out += charWidth(cp) === 2 === BLESSED_WIDE.test(ch) ? ch : "?";
|
|
11521
|
-
}
|
|
11522
|
-
return out;
|
|
11523
|
-
}
|
|
11524
|
-
function blessedSgr(code) {
|
|
11525
|
-
if (!code.includes(":")) return code;
|
|
11526
|
-
const groups = code.slice(2, -1).split(";").flatMap((g) => {
|
|
11527
|
-
if (!g.includes(":")) return [g];
|
|
11528
|
-
const [kind, mode, ...rest] = g.split(":");
|
|
11529
|
-
if (kind === "4") return ["4"];
|
|
11530
|
-
if (!["38", "48", "58"].includes(kind)) return [];
|
|
11531
|
-
if (mode === "5" && rest[0]) return [`${kind};5;${rest[0]}`];
|
|
11532
|
-
if (mode === "2" && rest.length >= 3)
|
|
11533
|
-
return [`${kind};2;${rest.slice(-3).join(";")}`];
|
|
11534
|
-
return [];
|
|
11535
|
-
});
|
|
11536
|
-
return groups.length ? `\x1B[${groups.join(";")}m` : "";
|
|
11537
|
-
}
|
|
11538
|
-
var SGR_SPLIT = /(\x1b\[[0-9;:]*m)/;
|
|
11539
|
-
var visible = (t) => t.replace(SGR_RE, "");
|
|
11540
|
-
function screenText(input, opts = {}) {
|
|
11541
|
-
const parts = (opts.colors ? keepSgr(input) : stripAnsi(input)).replace(/\r\n/g, "\n").split("\r");
|
|
11542
|
-
let last = parts.length - 1;
|
|
11543
|
-
while (last > 0 && visible(parts[last]) === "") last--;
|
|
11544
|
-
const carried = parts.slice(0, last).flatMap((p) => p.match(SGR_RE) ?? []).join("");
|
|
11545
|
-
let out = "";
|
|
11546
|
-
let col = 0;
|
|
11547
|
-
for (const piece of (carried + parts[last]).split(SGR_SPLIT)) {
|
|
11548
|
-
if (SGR_SPLIT.test(piece) && piece.startsWith("\x1B")) {
|
|
11549
|
-
out += blessedSgr(piece);
|
|
11550
|
-
continue;
|
|
11551
|
-
}
|
|
11552
|
-
for (const { segment } of segmenter.segment(
|
|
11553
|
-
piece.replace(LINE_BREAK, " ").normalize("NFC")
|
|
11554
|
-
)) {
|
|
11555
|
-
const c = cluster(segment);
|
|
11556
|
-
if (c === " ") {
|
|
11557
|
-
const n = TAB - col % TAB;
|
|
11558
|
-
out += " ".repeat(n);
|
|
11559
|
-
col += n;
|
|
11560
|
-
continue;
|
|
11561
|
-
}
|
|
11562
|
-
out += c;
|
|
11563
|
-
col += columns(c);
|
|
11564
|
-
}
|
|
11565
|
-
}
|
|
11566
|
-
return out;
|
|
11567
|
-
}
|
|
11568
|
-
function cutWidth(s, width) {
|
|
11569
|
-
if (displayWidth(s) <= width) return s;
|
|
11570
|
-
let out = "";
|
|
11571
|
-
let w = 0;
|
|
11572
|
-
for (const ch of s) {
|
|
11573
|
-
const cw = displayWidth(ch);
|
|
11574
|
-
if (w + cw > width - 1) break;
|
|
11575
|
-
out += ch;
|
|
11576
|
-
w += cw;
|
|
11577
|
-
}
|
|
11578
|
-
return `${out}\u2026`;
|
|
11579
|
-
}
|
|
11580
|
-
|
|
11581
12430
|
// src/monit/frame.ts
|
|
12431
|
+
var stacksKey = (s) => s.toStacks ? "esc stacks " : "";
|
|
11582
12432
|
function pane(entries, scroll, shift, logH, rowsOf) {
|
|
11583
12433
|
let filled = 0;
|
|
11584
12434
|
let k = 0;
|
|
@@ -11813,7 +12663,7 @@ function renderFrame(s, size) {
|
|
|
11813
12663
|
spec2.rowsOf
|
|
11814
12664
|
);
|
|
11815
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`;
|
|
11816
|
-
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`;
|
|
11817
12667
|
return {
|
|
11818
12668
|
header,
|
|
11819
12669
|
table: [],
|
|
@@ -11836,7 +12686,7 @@ function renderFrame(s, size) {
|
|
|
11836
12686
|
spec2.logH,
|
|
11837
12687
|
spec2.rowsOf
|
|
11838
12688
|
);
|
|
11839
|
-
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`;
|
|
11840
12690
|
return {
|
|
11841
12691
|
header,
|
|
11842
12692
|
table: [],
|
|
@@ -11848,32 +12698,37 @@ function renderFrame(s, size) {
|
|
|
11848
12698
|
};
|
|
11849
12699
|
}
|
|
11850
12700
|
const multi = new Set(s.rows.map((r2) => r2.host)).size > 1;
|
|
11851
|
-
const
|
|
12701
|
+
const o = multi ? 1 : 0;
|
|
12702
|
+
const tableLines = table(
|
|
11852
12703
|
[
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
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
|
+
])
|
|
11863
12728
|
],
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
...multi ? [r2.host] : [],
|
|
11868
|
-
r2.status,
|
|
11869
|
-
fmtUptime(r2.uptime_ms),
|
|
11870
|
-
r2.memory ? fmtBytes(r2.memory) : "-",
|
|
11871
|
-
r2.status === "online" ? fmtCpu(r2.cpu) : "-",
|
|
11872
|
-
cutWidth(screenText(fmtPorts(r2.ports)), 22),
|
|
11873
|
-
cutWidth(screenText(fmtUrl(r2)), 36),
|
|
11874
|
-
r2.runtime ? cutWidth(screenText(r2.runtime), 40) : "-"
|
|
11875
|
-
])
|
|
11876
|
-
]).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));
|
|
11877
12732
|
const spec = paneSpec(s, size);
|
|
11878
12733
|
const p = pane(
|
|
11879
12734
|
spec.entries,
|
|
@@ -11905,7 +12760,7 @@ function renderFrame(s, size) {
|
|
|
11905
12760
|
w
|
|
11906
12761
|
)
|
|
11907
12762
|
] : ["no app selected"];
|
|
11908
|
-
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`;
|
|
11909
12764
|
const footer = cutWidth(right ? `${right} \u2502 ${keys}` : keys, w);
|
|
11910
12765
|
return {
|
|
11911
12766
|
header,
|
|
@@ -12010,31 +12865,31 @@ async function monitOnce(opts, deps2 = {}) {
|
|
|
12010
12865
|
}
|
|
12011
12866
|
|
|
12012
12867
|
// src/cli/install.ts
|
|
12013
|
-
import
|
|
12014
|
-
import
|
|
12015
|
-
import
|
|
12868
|
+
import fs19 from "fs";
|
|
12869
|
+
import os9 from "os";
|
|
12870
|
+
import path22 from "path";
|
|
12016
12871
|
var LAUNCHER_MARK = "# written by mcproc install";
|
|
12017
12872
|
function rcFor(home, shell) {
|
|
12018
|
-
const name2 =
|
|
12019
|
-
return
|
|
12873
|
+
const name2 = path22.basename(shell || "");
|
|
12874
|
+
return path22.join(
|
|
12020
12875
|
home,
|
|
12021
12876
|
name2 === "zsh" ? ".zshrc" : name2 === "bash" ? ".bashrc" : ".profile"
|
|
12022
12877
|
);
|
|
12023
12878
|
}
|
|
12024
12879
|
function installShortcut(o = {}) {
|
|
12025
|
-
const home = o.home ??
|
|
12026
|
-
const binDir =
|
|
12027
|
-
const cli =
|
|
12028
|
-
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")
|
|
12029
12884
|
);
|
|
12030
|
-
const launcher =
|
|
12885
|
+
const launcher = path22.join(binDir, "mcproc");
|
|
12031
12886
|
const pathEnv = o.pathEnv ?? process.env.PATH ?? "";
|
|
12032
|
-
const onPath = pathEnv.split(":").some((p) => p &&
|
|
12887
|
+
const onPath = pathEnv.split(":").some((p) => p && path22.resolve(p.replace(/^~(?=\/|$)/, home)) === binDir);
|
|
12033
12888
|
const next = [];
|
|
12034
12889
|
if (o.uninstall) {
|
|
12035
|
-
const exists =
|
|
12036
|
-
const ours = exists &&
|
|
12037
|
-
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);
|
|
12038
12893
|
else if (exists)
|
|
12039
12894
|
next.push(`${launcher} was not written by mcproc install; left in place`);
|
|
12040
12895
|
else next.push(`nothing at ${launcher}`);
|
|
@@ -12050,43 +12905,43 @@ function installShortcut(o = {}) {
|
|
|
12050
12905
|
next
|
|
12051
12906
|
};
|
|
12052
12907
|
}
|
|
12053
|
-
|
|
12908
|
+
fs19.mkdirSync(binDir, { recursive: true });
|
|
12054
12909
|
let existing = null;
|
|
12055
12910
|
try {
|
|
12056
|
-
existing =
|
|
12911
|
+
existing = fs19.lstatSync(launcher);
|
|
12057
12912
|
} catch {
|
|
12058
12913
|
}
|
|
12059
12914
|
if (existing?.isSymbolicLink()) {
|
|
12060
12915
|
throw new McprocError(
|
|
12061
12916
|
"BAD_ARGUMENT",
|
|
12062
|
-
`${launcher} is a symlink (to ${
|
|
12917
|
+
`${launcher} is a symlink (to ${fs19.readlinkSync(launcher)}); refusing to write through it`,
|
|
12063
12918
|
{ hint: "Remove it yourself, or pass another --bin-dir." }
|
|
12064
12919
|
);
|
|
12065
12920
|
}
|
|
12066
|
-
if (existing && !
|
|
12921
|
+
if (existing && !fs19.readFileSync(launcher, "utf8").includes(LAUNCHER_MARK)) {
|
|
12067
12922
|
throw new McprocError(
|
|
12068
12923
|
"BAD_ARGUMENT",
|
|
12069
12924
|
`${launcher} exists and was not written by mcproc install; refusing to overwrite it`,
|
|
12070
12925
|
{ hint: "Remove it yourself, or pass another --bin-dir." }
|
|
12071
12926
|
);
|
|
12072
12927
|
}
|
|
12073
|
-
|
|
12928
|
+
fs19.writeFileSync(
|
|
12074
12929
|
launcher,
|
|
12075
12930
|
`#!/bin/sh
|
|
12076
|
-
${LAUNCHER_MARK}: runs the mcproc at ${
|
|
12931
|
+
${LAUNCHER_MARK}: runs the mcproc at ${path22.dirname(path22.dirname(cli))}
|
|
12077
12932
|
exec node ${shq(cli)} "$@"
|
|
12078
12933
|
`
|
|
12079
12934
|
);
|
|
12080
|
-
|
|
12935
|
+
fs19.chmodSync(launcher, 493);
|
|
12081
12936
|
let rcFile = null;
|
|
12082
12937
|
let rcUpdated = false;
|
|
12083
12938
|
if (!onPath) {
|
|
12084
12939
|
rcFile = rcFor(home, o.shell ?? process.env.SHELL ?? "");
|
|
12085
|
-
const shown = binDir.startsWith(home +
|
|
12940
|
+
const shown = binDir.startsWith(home + path22.sep) ? `$HOME/${path22.relative(home, binDir)}` : binDir;
|
|
12086
12941
|
const line = `export PATH="${shown}:$PATH"`;
|
|
12087
|
-
const current =
|
|
12942
|
+
const current = fs19.existsSync(rcFile) ? fs19.readFileSync(rcFile, "utf8") : "";
|
|
12088
12943
|
if (!current.includes(line)) {
|
|
12089
|
-
|
|
12944
|
+
fs19.appendFileSync(
|
|
12090
12945
|
rcFile,
|
|
12091
12946
|
`${current === "" || current.endsWith("\n") ? "" : "\n"}
|
|
12092
12947
|
# mcproc install: the shortcut lives in ${shown}
|
|
@@ -12194,6 +13049,7 @@ var MonitLoop = class {
|
|
|
12194
13049
|
inc = freshView();
|
|
12195
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. */
|
|
12196
13051
|
refresh(hosts) {
|
|
13052
|
+
if (this.closed) return Promise.resolve();
|
|
12197
13053
|
this.readCleared();
|
|
12198
13054
|
this.syncWatches();
|
|
12199
13055
|
const gen = this.gen;
|
|
@@ -12303,6 +13159,7 @@ var MonitLoop = class {
|
|
|
12303
13159
|
const probed = this.losses;
|
|
12304
13160
|
try {
|
|
12305
13161
|
const ctx = await this.fleet.ctx(g.host);
|
|
13162
|
+
if (gen !== this.gen) return;
|
|
12306
13163
|
const snap = await this.deps.snapshot(ctx, {
|
|
12307
13164
|
cpu: "none",
|
|
12308
13165
|
prev: this.prev.get(g.host),
|
|
@@ -12512,6 +13369,8 @@ var MonitLoop = class {
|
|
|
12512
13369
|
/** The screen is going away: no follower wakes it any more, and a running reload stops waiting for dependencies. */
|
|
12513
13370
|
close() {
|
|
12514
13371
|
this.closed = true;
|
|
13372
|
+
this.gen++;
|
|
13373
|
+
this.switchSeq++;
|
|
12515
13374
|
this.reloading?.abort();
|
|
12516
13375
|
for (const q of this.wakes.values()) {
|
|
12517
13376
|
if (q.timer) clearTimeout(q.timer);
|
|
@@ -12665,10 +13524,10 @@ var MonitLoop = class {
|
|
|
12665
13524
|
this.names.map((a) => markKey(this.fleet.hostOf(a), a))
|
|
12666
13525
|
);
|
|
12667
13526
|
const st = this.inc;
|
|
12668
|
-
const add2 = (key, stream,
|
|
13527
|
+
const add2 = (key, stream, path23) => {
|
|
12669
13528
|
const by = st.pending[key] ?? {};
|
|
12670
13529
|
const paths = by[stream] ?? /* @__PURE__ */ new Set();
|
|
12671
|
-
paths.add(
|
|
13530
|
+
paths.add(path23);
|
|
12672
13531
|
by[stream] = paths;
|
|
12673
13532
|
st.pending[key] = by;
|
|
12674
13533
|
};
|
|
@@ -12811,9 +13670,11 @@ async function runMonit(opts) {
|
|
|
12811
13670
|
project: manifest.project,
|
|
12812
13671
|
host: viewLabel(fleet.requested),
|
|
12813
13672
|
hosts: views.map(viewLabel),
|
|
12814
|
-
apps: appInfos(manifest)
|
|
13673
|
+
apps: appInfos(manifest),
|
|
13674
|
+
toStacks: Boolean(opts.back)
|
|
12815
13675
|
});
|
|
12816
|
-
const
|
|
13676
|
+
const own2 = !opts.screen;
|
|
13677
|
+
const screen = opts.screen ?? blessed.screen({
|
|
12817
13678
|
smartCSR: true,
|
|
12818
13679
|
title: "mcproc monit",
|
|
12819
13680
|
fullUnicode: true
|
|
@@ -12832,7 +13693,7 @@ async function runMonit(opts) {
|
|
|
12832
13693
|
const detailsTitle = box({ bottom: 7, height: 1 });
|
|
12833
13694
|
const detailsBox = box({ bottom: 1, height: 6 });
|
|
12834
13695
|
const footer = box({ bottom: 0, height: 1 });
|
|
12835
|
-
|
|
13696
|
+
const boxes = [
|
|
12836
13697
|
header,
|
|
12837
13698
|
appsTitle,
|
|
12838
13699
|
tableBox,
|
|
@@ -12841,8 +13702,8 @@ async function runMonit(opts) {
|
|
|
12841
13702
|
detailsTitle,
|
|
12842
13703
|
detailsBox,
|
|
12843
13704
|
footer
|
|
12844
|
-
]
|
|
12845
|
-
|
|
13705
|
+
];
|
|
13706
|
+
for (const b of boxes) screen.append(b);
|
|
12846
13707
|
const appColor = new Map(
|
|
12847
13708
|
Object.keys(manifest.apps).map((a, i) => [a, colorFor(i)])
|
|
12848
13709
|
);
|
|
@@ -12855,7 +13716,9 @@ async function runMonit(opts) {
|
|
|
12855
13716
|
width: screen.width,
|
|
12856
13717
|
height: screen.height
|
|
12857
13718
|
});
|
|
13719
|
+
let left = false;
|
|
12858
13720
|
const show = (f) => {
|
|
13721
|
+
if (left) return;
|
|
12859
13722
|
const { width: w, height: h } = size();
|
|
12860
13723
|
header.setContent(f.header);
|
|
12861
13724
|
footer.setContent(f.footer);
|
|
@@ -12904,44 +13767,511 @@ async function runMonit(opts) {
|
|
|
12904
13767
|
const timer = setInterval(() => {
|
|
12905
13768
|
void loop.refresh();
|
|
12906
13769
|
}, opts.intervalMs);
|
|
12907
|
-
const
|
|
13770
|
+
const leave = (why) => {
|
|
13771
|
+
left = true;
|
|
12908
13772
|
clearInterval(timer);
|
|
12909
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);
|
|
12910
14207
|
screen.destroy();
|
|
12911
14208
|
resolve();
|
|
12912
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
|
+
};
|
|
12913
14265
|
screen.on(
|
|
12914
14266
|
"keypress",
|
|
12915
14267
|
(_ch, key) => {
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
dispatch({ type: "arm", at: null });
|
|
12919
|
-
if (!intent) return;
|
|
12920
|
-
if (intent.kind === "quit") return quit();
|
|
12921
|
-
if (intent.kind === "action") {
|
|
12922
|
-
if (intent.action.type === "arm" && intent.action.at !== null) {
|
|
12923
|
-
const { at, what } = intent.action;
|
|
12924
|
-
setTimeout(() => {
|
|
12925
|
-
if (state.armedAt === at && state.armed === what)
|
|
12926
|
-
dispatch({ type: "arm", at: null });
|
|
12927
|
-
}, CONFIRM_MS + 50).unref();
|
|
12928
|
-
}
|
|
12929
|
-
if (intent.action.type === "select")
|
|
12930
|
-
loop.dispatchSelect(intent.action.delta);
|
|
12931
|
-
else {
|
|
12932
|
-
dispatch(intent.action);
|
|
12933
|
-
if (intent.action.type === "view") void loop.refresh();
|
|
12934
|
-
}
|
|
12935
|
-
return;
|
|
12936
|
-
}
|
|
12937
|
-
if (intent.kind === "host-next") return void loop.nextView();
|
|
12938
|
-
if (intent.kind === "clear") return void loop.runClear(intent.apps);
|
|
12939
|
-
if (intent.kind === "reload-all") return void loop.reloadAll();
|
|
12940
|
-
void loop.runCommand(intent.cmd, intent.app);
|
|
14268
|
+
if (!active) return;
|
|
14269
|
+
void onKey(key.full ?? key.name ?? "");
|
|
12941
14270
|
}
|
|
12942
14271
|
);
|
|
12943
14272
|
screen.on("resize", paint);
|
|
12944
|
-
|
|
14273
|
+
paint();
|
|
14274
|
+
void refresh();
|
|
12945
14275
|
});
|
|
12946
14276
|
}
|
|
12947
14277
|
|
|
@@ -12950,7 +14280,8 @@ var defaultIo = {
|
|
|
12950
14280
|
out: (s) => process.stdout.write(`${s}
|
|
12951
14281
|
`),
|
|
12952
14282
|
err: (s) => process.stderr.write(`${s}
|
|
12953
|
-
`)
|
|
14283
|
+
`),
|
|
14284
|
+
columns: () => process.stdout.isTTY ? process.stdout.columns || void 0 : void 0
|
|
12954
14285
|
};
|
|
12955
14286
|
async function printSince(fleet, apps, since, how, io, signal) {
|
|
12956
14287
|
const marks = new LogMarks();
|
|
@@ -13002,10 +14333,20 @@ async function printLs(fleet, names, how, io) {
|
|
|
13002
14333
|
how.reloads
|
|
13003
14334
|
);
|
|
13004
14335
|
io.out(
|
|
13005
|
-
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?.())
|
|
13006
14337
|
);
|
|
13007
14338
|
if (errors.length) process.exitCode = 1;
|
|
13008
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
|
+
}
|
|
13009
14350
|
function buildProgram(io = defaultIo) {
|
|
13010
14351
|
const program = new Command().name("mcproc").description(
|
|
13011
14352
|
"pm2 process control for agents: one manifest per project, local and remote hosts, per-app ports"
|
|
@@ -13014,6 +14355,7 @@ function buildProgram(io = defaultIo) {
|
|
|
13014
14355
|
"project root (default: MCPROC_PROJECT, CLAUDE_PROJECT_DIR, cwd)"
|
|
13015
14356
|
).option("--host <name>", "host from the manifest (default: local)").option("--json", "print JSON instead of text");
|
|
13016
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?.();
|
|
13017
14359
|
const wrap = (fn) => async (...all) => {
|
|
13018
14360
|
const cmd = all[all.length - 1];
|
|
13019
14361
|
const opts = all[all.length - 2] ?? {};
|
|
@@ -13036,7 +14378,13 @@ Hint: ${e.hint}` : ""}`
|
|
|
13036
14378
|
const fleet = fleetOf(g);
|
|
13037
14379
|
const waits = [];
|
|
13038
14380
|
const rows = await actOn(fleet, target, act, { ...opts, waits });
|
|
13039
|
-
const { text, failed } = renderMutation(
|
|
14381
|
+
const { text, failed } = renderMutation(
|
|
14382
|
+
rows,
|
|
14383
|
+
fleet.fallback,
|
|
14384
|
+
act,
|
|
14385
|
+
waits,
|
|
14386
|
+
width(g)
|
|
14387
|
+
);
|
|
13040
14388
|
emit(g, text, { ...hostsOf(rows, fleet.fallback), rows, waits });
|
|
13041
14389
|
if (failed) process.exitCode = 1;
|
|
13042
14390
|
};
|
|
@@ -13065,7 +14413,7 @@ ${r.next}`, r);
|
|
|
13065
14413
|
wrap(async (g) => {
|
|
13066
14414
|
const { manifest } = prepare({ project: g.project });
|
|
13067
14415
|
const h = hostsInfo(manifest);
|
|
13068
|
-
emit(g, renderHosts(h), h);
|
|
14416
|
+
emit(g, renderHosts(h, width(g)), h);
|
|
13069
14417
|
})
|
|
13070
14418
|
);
|
|
13071
14419
|
program.command("doctor").description(
|
|
@@ -13098,7 +14446,7 @@ ${r.next}`, r);
|
|
|
13098
14446
|
const fleet = fleetOf(g);
|
|
13099
14447
|
if (!app) {
|
|
13100
14448
|
const d2 = describeProject(fleet.manifest);
|
|
13101
|
-
emit(g, renderProject(d2), d2);
|
|
14449
|
+
emit(g, renderProject(d2, width(g)), d2);
|
|
13102
14450
|
return;
|
|
13103
14451
|
}
|
|
13104
14452
|
const d = await describeFleetApp(fleet, app);
|
|
@@ -13108,7 +14456,7 @@ ${r.next}`, r);
|
|
|
13108
14456
|
`${d.app.name} [${d.app.kind}]: ${d.app.description}`,
|
|
13109
14457
|
`run: ${d.app.run}`,
|
|
13110
14458
|
`cwd: ${d.app.cwd}`,
|
|
13111
|
-
renderLs([d.row], d.host),
|
|
14459
|
+
renderLs([d.row], d.host, [], width(g)),
|
|
13112
14460
|
urlNote(d.row),
|
|
13113
14461
|
`logs: ${d.logs ? `${d.logs.out} | ${d.logs.err}` : "not started yet"}`
|
|
13114
14462
|
].filter(Boolean).join("\n"),
|
|
@@ -13310,7 +14658,7 @@ ${r.next}`, r);
|
|
|
13310
14658
|
wrap(async (g, _o, [port]) => {
|
|
13311
14659
|
const ctx = await ctxOf(g);
|
|
13312
14660
|
const r = await portsReport(ctx, port ? Number(port) : void 0);
|
|
13313
|
-
emit(g, renderPorts(r, ctx.host.name), r);
|
|
14661
|
+
emit(g, renderPorts(r, ctx.host.name, width(g)), r);
|
|
13314
14662
|
})
|
|
13315
14663
|
);
|
|
13316
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(
|
|
@@ -13336,7 +14684,7 @@ ${r.next}`, r);
|
|
|
13336
14684
|
ctx = void 0;
|
|
13337
14685
|
}
|
|
13338
14686
|
const v = await leasesView(void 0, ctx);
|
|
13339
|
-
emit(g, renderLeases(v), v);
|
|
14687
|
+
emit(g, renderLeases(v, width(g)), v);
|
|
13340
14688
|
})
|
|
13341
14689
|
);
|
|
13342
14690
|
leases.command("cancel <id>").description("cancel a pending lease").action(
|
|
@@ -13345,6 +14693,22 @@ ${r.next}`, r);
|
|
|
13345
14693
|
emit(g, `cancelled ${l.id} (${l.project} ${l.target})`, l);
|
|
13346
14694
|
})
|
|
13347
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
|
+
);
|
|
13348
14712
|
program.command("exec <command...>").description(
|
|
13349
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)"
|
|
13350
14714
|
).option(
|
|
@@ -13451,7 +14815,7 @@ ${r.next}`, r);
|
|
|
13451
14815
|
wrap(async (g) => {
|
|
13452
14816
|
const { manifest, deps: deps2 } = await worktreeCtx(g);
|
|
13453
14817
|
const rows = await worktreeLs(manifest, deps2);
|
|
13454
|
-
emit(g, renderWorktrees(rows), rows);
|
|
14818
|
+
emit(g, renderWorktrees(rows, width(g)), rows);
|
|
13455
14819
|
})
|
|
13456
14820
|
);
|
|
13457
14821
|
worktree.command("remove <ref>").description(
|
|
@@ -13476,7 +14840,7 @@ ${r.next}`, r);
|
|
|
13476
14840
|
const stop = () => ac.abort();
|
|
13477
14841
|
process.once("SIGINT", stop);
|
|
13478
14842
|
process.once("SIGTERM", stop);
|
|
13479
|
-
|
|
14843
|
+
fs20.mkdirSync(deps2.store.dir, { recursive: true });
|
|
13480
14844
|
deps2.watch = leaseDirWatch(deps2.store.dir);
|
|
13481
14845
|
const r = await watchLeases(deps2, { signal: ac.signal });
|
|
13482
14846
|
io.out(`watcher ${r}`);
|
|
@@ -13492,25 +14856,36 @@ ${r.next}`, r);
|
|
|
13492
14856
|
})
|
|
13493
14857
|
);
|
|
13494
14858
|
program.command("monit").description(
|
|
13495
|
-
"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"
|
|
13496
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(
|
|
13497
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
|
+
}
|
|
13498
14884
|
if (o.once) {
|
|
13499
|
-
io.out(
|
|
13500
|
-
await monitOnce({
|
|
13501
|
-
project: g.project,
|
|
13502
|
-
host: g.host,
|
|
13503
|
-
lines: Number(o.lines) || 200
|
|
13504
|
-
})
|
|
13505
|
-
);
|
|
14885
|
+
io.out(await monitOnce({ project: g.project, host: g.host, lines }));
|
|
13506
14886
|
return;
|
|
13507
14887
|
}
|
|
13508
|
-
await runMonit({
|
|
13509
|
-
project: g.project,
|
|
13510
|
-
host: g.host,
|
|
13511
|
-
intervalMs: Math.max(500, Number(o.interval) || 2e3),
|
|
13512
|
-
lines: Number(o.lines) || 200
|
|
13513
|
-
});
|
|
14888
|
+
await runMonit({ project: g.project, host: g.host, intervalMs, lines });
|
|
13514
14889
|
})
|
|
13515
14890
|
);
|
|
13516
14891
|
program.command("unpack").description(
|