@edspencer/paddock 0.79.0 → 0.80.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/package.json +2 -2
- package/packages/server/dist/app.js +18 -14
- package/packages/server/dist/cli/args.d.ts +47 -7
- package/packages/server/dist/cli/args.js +112 -18
- package/packages/server/dist/cli/exit-codes.d.ts +49 -0
- package/packages/server/dist/cli/exit-codes.js +48 -0
- package/packages/server/dist/cli/paddock.js +84 -37
- package/packages/server/dist/cli/service/backend.d.ts +48 -4
- package/packages/server/dist/cli/service/index.d.ts +56 -2
- package/packages/server/dist/cli/service/index.js +283 -56
- package/packages/server/dist/cli/service/launchd.d.ts +8 -0
- package/packages/server/dist/cli/service/launchd.js +29 -7
- package/packages/server/dist/cli/service/logs.d.ts +39 -0
- package/packages/server/dist/cli/service/logs.js +115 -0
- package/packages/server/dist/cli/service/path-env.d.ts +27 -0
- package/packages/server/dist/cli/service/path-env.js +120 -0
- package/packages/server/dist/cli/service/ready.d.ts +22 -0
- package/packages/server/dist/cli/service/ready.js +30 -0
- package/packages/server/dist/cli/service/spec.d.ts +45 -6
- package/packages/server/dist/cli/service/spec.js +34 -7
- package/packages/server/dist/cli/service/systemd.d.ts +47 -4
- package/packages/server/dist/cli/service/systemd.js +127 -22
- package/packages/server/dist/management-mcp-server.d.ts +21 -3
- package/packages/server/dist/management-mcp-server.js +4 -13
- package/packages/server/dist/routes/meta.js +11 -3
- package/packages/server/dist/service-log.d.ts +29 -0
- package/packages/server/dist/service-log.js +104 -0
- package/packages/server/dist/start.js +11 -0
- package/packages/server/dist/version.d.ts +3 -0
- package/packages/server/dist/version.js +32 -0
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-DE5KhPD_.js → DeleteProjectDialog-DHczLhwQ.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-BSGSzTEs.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-DCEp5YDh.js → DiscoverView-DtTOpTji.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-D09mz7nR.js → InstanceConfigPage-BRoBh1C1.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-CujBOaSt.js → MarkdownRenderer-DJf8IJuk.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DaOQz7Bx.js → ProjectRedirect-EioguUKW.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-BxwVtKmQ.js → ProjectView-BiKnGfk6.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-BkULnAQF.js → ProjectsGrid-D6kfaIal.js} +1 -1
- package/packages/web/dist/assets/{RootHome-C7PQt4Mc.js → RootHome-DpcyuV6e.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-XlC0TXmU.js → Surfaces-CHM29PhP.js} +1 -1
- package/packages/web/dist/assets/{arc-C1crrky_.js → arc-fiqD05Hu.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR--3BQUIXc.js → architectureDiagram-3BPJPVTR-BuuB2Nkf.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-EisqobqG.js → blockDiagram-GPEHLZMM-DQMBuzfi.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-e6-wiFTa.js → c4Diagram-AAUBKEIU-BKN0UxCH.js} +1 -1
- package/packages/web/dist/assets/channel-B7yh6Su_.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-ToP1Bx9O.js → chunk-2J33WTMH-DW37EupX.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-BD2icQlZ.js → chunk-4BX2VUAB-Dyv7zV_c.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-D803_50z.js → chunk-55IACEB6-DzMR0dOk.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-CiJOKx_U.js → chunk-727SXJPM-BdZ3Sor8.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BYni6r_j.js → chunk-AQP2D5EJ-B4i7_og0.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-D6QbCpWh.js → chunk-FMBD7UC4-jcTr85it.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-CvJSg6SC.js → chunk-ND2GUHAM-fgMsadqB.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-Cz0BtHbV.js → chunk-QZHKN3VN-bJNHYS1W.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DIlhMPgi.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DIlhMPgi.js +2 -0
- package/packages/web/dist/assets/{core-BaJJFoBn.js → core-Bk4KFrmN.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-CzAP4gqt.js → cose-bilkent-S5V4N54A-BIqCSlQy.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-CwIEzm8u.js → dagre-BM42HDAG-6nyhtVQz.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-BEvdaFOg.js → diagram-2AECGRRQ-CjizfipR.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-mk5qWzPB.js → diagram-5GNKFQAL-DlgB5vWb.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-C4lD_Kit.js → diagram-KO2AKTUF-CNC9qanV.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BmduqHdC.js → diagram-LMA3HP47-DSJ-s9lh.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-CzbvsaAK.js → diagram-OG6HWLK6-C9FoHBDg.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-DvZmF8pw.js → documentTitle-Cj_AY97j.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-B3Nm1nfI.js → erDiagram-TEJ5UH35-z6gku4Yt.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-DTAf1VHv.js → flowDiagram-I6XJVG4X-Dl5c_FgU.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-BOeIF25v.js → ganttDiagram-6RSMTGT7-CIscJ-Dt.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-DR0ETY9X.js → gitGraphDiagram-PVQCEYII-CvttOXM_.js} +1 -1
- package/packages/web/dist/assets/{index-BqXu07yh.js → index-DN82do6Z.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-iFsN2k7Z.js → infoDiagram-5YYISTIA-dzWYdDJj.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-D0aKZ4O0.js → ishikawaDiagram-YF4QCWOH-8yX9k4bo.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Dzt-NowV.js → journeyDiagram-JHISSGLW-BmitzJDR.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-EurYbfWQ.js → kanban-definition-UN3LZRKU-BZmIkfkW.js} +1 -1
- package/packages/web/dist/assets/{linear-DjReSGMv.js → linear-BBLXE4o7.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CPXZwBWr.js → mermaid.core-mSKff4yu.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-BZKr8aHp.js → mindmap-definition-RKZ34NQL-BWAUa4CP.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CGIjsXTW.js → pieDiagram-4H26LBE5-VmVdiL4C.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-CMk1ecJ2.js → quadrantDiagram-W4KKPZXB-Dybfv6NU.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-DR2dI24k.js → requirementDiagram-4Y6WPE33-BPnOvB7w.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-BP6UMMTr.js → sankeyDiagram-5OEKKPKP-YXIEmKw7.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-4jrB80y7.js → sequenceDiagram-3UESZ5HK-DintIRUe.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-yAWdzPCD.js → stateDiagram-AJRCARHV-eMsjahWp.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CpAOGzbO.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-MFxU8aFE.js → timeline-definition-PNZ67QCA-as4zRCPn.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-DptEBZ6E.js → vennDiagram-CIIHVFJN-CPzvUkM0.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-Dk-HljGs.js → wardley-L42UT6IY-q6moXj5g.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DhIz3dBE.js → wardleyDiagram-YWT4CUSO-BsEEb9Gm.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-ytw3T4h8.js → xychartDiagram-2RQKCTM6-DSgZlcZW.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/DiscoverPage-vaaC-Dji.js +0 -2
- package/packages/web/dist/assets/channel-D4JZjkXm.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-BQVXohi4.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-BQVXohi4.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-D0dmQsgw.js +0 -2
|
@@ -23,8 +23,10 @@ import os from "node:os";
|
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { spawn } from "node:child_process";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
|
-
import { CliError, USAGE, SERVICE_USAGE, CONFIG_USAGE, parseCommand, nodeVersionProblem, explainListenError, } from "./args.js";
|
|
27
|
-
import {
|
|
26
|
+
import { CliError, UsageError, USAGE, USAGE_SYNOPSIS, SERVICE_USAGE, CONFIG_USAGE, parseCommand, nodeVersionProblem, explainListenError, } from "./args.js";
|
|
27
|
+
import { EXIT } from "./exit-codes.js";
|
|
28
|
+
import { runService, safeHomeDir, realpathOr } from "./service/index.js";
|
|
29
|
+
import { PADDOCK_NAME, PADDOCK_VERSION } from "../version.js";
|
|
28
30
|
/**
|
|
29
31
|
* This module's own file and directory: `<…>/packages/server/dist/cli/`.
|
|
30
32
|
*
|
|
@@ -62,18 +64,27 @@ function findUp(rel) {
|
|
|
62
64
|
}
|
|
63
65
|
/** Nearest ancestor holding a `package.json` — `packages/server` in both layouts. */
|
|
64
66
|
const packageRoot = findUp("package.json") ?? path.resolve(moduleDir, "../..");
|
|
65
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Report a failure, and hand back the code to exit with (SPEC §2, §3).
|
|
69
|
+
*
|
|
70
|
+
* Two things here are the contract rather than style. Errors go to **stderr**
|
|
71
|
+
* as `paddock: <message>`, so a `--json` document on stdout stays the only
|
|
72
|
+
* thing on stdout. And the code is RETURNED rather than passed to
|
|
73
|
+
* `process.exit()`: exiting mid-write truncates piped output, which is how a
|
|
74
|
+
* `paddock service status | jq` loses the end of its document. `main` assigns
|
|
75
|
+
* `process.exitCode` and lets the process end on its own.
|
|
76
|
+
*
|
|
77
|
+
* A usage error also prints the synopsis after the message, because the spec
|
|
78
|
+
* says the usage follows the message — the short one, not {@link USAGE}'s
|
|
79
|
+
* thousand words, which would bury the line that says what was wrong.
|
|
80
|
+
*/
|
|
81
|
+
function fail(message, code = EXIT.failure) {
|
|
66
82
|
console.error(`paddock: ${message}`);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const raw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf8");
|
|
72
|
-
return JSON.parse(raw).version ?? "unknown";
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return "unknown";
|
|
83
|
+
if (code === EXIT.usage) {
|
|
84
|
+
console.error("");
|
|
85
|
+
console.error(USAGE_SYNOPSIS);
|
|
76
86
|
}
|
|
87
|
+
return code;
|
|
77
88
|
}
|
|
78
89
|
/**
|
|
79
90
|
* Put our bundled `claude` on PATH for child processes.
|
|
@@ -121,7 +132,10 @@ function addBundledBinsToPath() {
|
|
|
121
132
|
function noteFirstRun(dataDir) {
|
|
122
133
|
if (fs.existsSync(dataDir))
|
|
123
134
|
return;
|
|
124
|
-
|
|
135
|
+
// stderr, because stdout's first line belongs to the banner (SPEC §3). This
|
|
136
|
+
// block is prose for a person and it only appears on a first run — which is
|
|
137
|
+
// exactly the run a conformance check or a script reading the URL performs.
|
|
138
|
+
console.error([
|
|
125
139
|
"",
|
|
126
140
|
` Welcome to Paddock. Creating a new instance in ${dataDir}`,
|
|
127
141
|
" Projects, chats and settings persist there between runs.",
|
|
@@ -161,12 +175,17 @@ async function service(command) {
|
|
|
161
175
|
// which `main` has already handled. Typed rather than asserted.
|
|
162
176
|
if (action === undefined) {
|
|
163
177
|
console.log(SERVICE_USAGE);
|
|
164
|
-
return;
|
|
178
|
+
return EXIT.ok;
|
|
165
179
|
}
|
|
166
180
|
try {
|
|
167
|
-
await runService(action, opts, {
|
|
181
|
+
return await runService(action, opts, {
|
|
168
182
|
platform: process.platform,
|
|
169
|
-
|
|
183
|
+
// Realpath'd (SPEC §7). `process.execPath` under a version manager is
|
|
184
|
+
// routinely a symlink into a per-shell directory — fnm's
|
|
185
|
+
// `fnm_multishells/<pid>_<ts>/bin/node` is deleted when that terminal
|
|
186
|
+
// exits — so a unit naming the symlink stops working when the tab closes,
|
|
187
|
+
// at some later login, with nothing in the logs because it never ran.
|
|
188
|
+
nodePath: realpathOr(process.execPath),
|
|
170
189
|
scriptPath: entryScript,
|
|
171
190
|
packageRoot,
|
|
172
191
|
homeDir: safeHomeDir(),
|
|
@@ -180,10 +199,12 @@ async function service(command) {
|
|
|
180
199
|
});
|
|
181
200
|
}
|
|
182
201
|
catch (err) {
|
|
202
|
+
// A CliError carries its own code: an unsupported platform or an npx
|
|
203
|
+
// refusal is a failure (1), a misspelled flag is usage (2).
|
|
183
204
|
if (err instanceof CliError)
|
|
184
|
-
fail(err.message);
|
|
205
|
+
return fail(err.message, err.exitCode);
|
|
185
206
|
if (err instanceof Error)
|
|
186
|
-
fail(err.message);
|
|
207
|
+
return fail(err.message);
|
|
187
208
|
throw err;
|
|
188
209
|
}
|
|
189
210
|
}
|
|
@@ -214,12 +235,13 @@ async function config(command) {
|
|
|
214
235
|
// which `main` has already handled. Typed rather than asserted.
|
|
215
236
|
if (action === undefined) {
|
|
216
237
|
console.log(CONFIG_USAGE);
|
|
217
|
-
return;
|
|
238
|
+
return EXIT.ok;
|
|
218
239
|
}
|
|
219
240
|
process.env.PADDOCK_DATA_DIR = resolveDataDir(opts);
|
|
220
241
|
const { runConfig } = await import("./config.js");
|
|
221
242
|
try {
|
|
222
243
|
await runConfig(action, opts);
|
|
244
|
+
return EXIT.ok;
|
|
223
245
|
}
|
|
224
246
|
catch (err) {
|
|
225
247
|
// A config that will not load reaches here as a CliError already carrying
|
|
@@ -227,9 +249,9 @@ async function config(command) {
|
|
|
227
249
|
// because it is a bug in this command rather than in the configuration.
|
|
228
250
|
// Same shape as `service` above.
|
|
229
251
|
if (err instanceof CliError)
|
|
230
|
-
fail(err.message);
|
|
252
|
+
return fail(err.message, err.exitCode);
|
|
231
253
|
if (err instanceof Error)
|
|
232
|
-
fail(err.message);
|
|
254
|
+
return fail(err.message);
|
|
233
255
|
throw err;
|
|
234
256
|
}
|
|
235
257
|
}
|
|
@@ -239,30 +261,30 @@ async function main() {
|
|
|
239
261
|
command = parseCommand(process.argv.slice(2));
|
|
240
262
|
}
|
|
241
263
|
catch (err) {
|
|
264
|
+
// Parse failures are usage failures: exit 2, not 1 (SPEC §2).
|
|
265
|
+
if (err instanceof UsageError)
|
|
266
|
+
return fail(err.message, err.exitCode);
|
|
242
267
|
if (err instanceof CliError)
|
|
243
|
-
fail(err.message);
|
|
268
|
+
return fail(err.message, err.exitCode);
|
|
244
269
|
throw err;
|
|
245
270
|
}
|
|
246
271
|
const opts = command.opts;
|
|
247
272
|
if (opts.help) {
|
|
248
273
|
console.log(command.verb === "service" ? SERVICE_USAGE : command.verb === "config" ? CONFIG_USAGE : USAGE);
|
|
249
|
-
return;
|
|
274
|
+
return EXIT.ok;
|
|
250
275
|
}
|
|
251
276
|
if (opts.version) {
|
|
252
|
-
|
|
253
|
-
|
|
277
|
+
// Bare: the version and a newline, nothing else (SPEC §1). Scripts parse this.
|
|
278
|
+
console.log(PADDOCK_VERSION);
|
|
279
|
+
return EXIT.ok;
|
|
254
280
|
}
|
|
255
281
|
const problem = nodeVersionProblem(process.versions.node);
|
|
256
282
|
if (problem !== undefined)
|
|
257
|
-
fail(problem);
|
|
258
|
-
if (command.verb === "service")
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (command.verb === "config") {
|
|
263
|
-
await config(command);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
283
|
+
return fail(problem);
|
|
284
|
+
if (command.verb === "service")
|
|
285
|
+
return service(command);
|
|
286
|
+
if (command.verb === "config")
|
|
287
|
+
return config(command);
|
|
266
288
|
addBundledBinsToPath();
|
|
267
289
|
// NOTHING here reads `process.cwd()`, and that is the point (#798). `--here`
|
|
268
290
|
// used to make the current directory the workspace by setting
|
|
@@ -330,9 +352,14 @@ async function main() {
|
|
|
330
352
|
await start();
|
|
331
353
|
}
|
|
332
354
|
catch (err) {
|
|
333
|
-
fail(explainListenError(err, host, port));
|
|
355
|
+
return fail(explainListenError(err, host, port));
|
|
334
356
|
}
|
|
335
357
|
const url = `http://${host}:${port}`;
|
|
358
|
+
// The contract's banner line, FIRST on stdout and in exactly this shape:
|
|
359
|
+
// `<Name> <version>: <url>` (SPEC §3). It is parsed — by the conformance
|
|
360
|
+
// runner, and by anything that wants the URL without guessing the port — so
|
|
361
|
+
// the friendly block below it stays, but below it.
|
|
362
|
+
console.log(`${PADDOCK_NAME} ${PADDOCK_VERSION}: ${url}`);
|
|
336
363
|
// Still name the data dir, even though it no longer varies with cwd: "which
|
|
337
364
|
// instance am I looking at" is a real question the moment anyone passes
|
|
338
365
|
// `--data-dir`, and the answer should be readable off the two lines the tool
|
|
@@ -346,8 +373,28 @@ async function main() {
|
|
|
346
373
|
].join("\n"));
|
|
347
374
|
if (opts.open)
|
|
348
375
|
openBrowser(url);
|
|
376
|
+
// 0, but NOT yet: a foreground serve keeps running because the server holds
|
|
377
|
+
// the event loop open. `process.exitCode` is set and never acted on until the
|
|
378
|
+
// listener closes, which is what SIGTERM does (start.ts).
|
|
379
|
+
return EXIT.ok;
|
|
349
380
|
}
|
|
350
|
-
|
|
381
|
+
/**
|
|
382
|
+
* A closed pipe exits quietly (SPEC §2).
|
|
383
|
+
*
|
|
384
|
+
* `paddock service logs | head -5` closes the pipe under us, and Node's default
|
|
385
|
+
* is an unhandled EPIPE with a stack trace — printed to the terminal, after the
|
|
386
|
+
* output the user asked for, about a thing that is not a problem.
|
|
387
|
+
*/
|
|
388
|
+
process.stdout.on("error", (err) => {
|
|
389
|
+
if (err.code === "EPIPE")
|
|
390
|
+
process.exit(0);
|
|
391
|
+
});
|
|
392
|
+
main().then((code) => {
|
|
393
|
+
// `process.exitCode`, never `process.exit()` (SPEC §2): exiting mid-write
|
|
394
|
+
// truncates piped output, which is how `paddock config show --json | jq`
|
|
395
|
+
// loses the end of a large document.
|
|
396
|
+
process.exitCode = code;
|
|
397
|
+
}, (err) => {
|
|
351
398
|
console.error("fatal:", err);
|
|
352
|
-
process.
|
|
399
|
+
process.exitCode = EXIT.failure;
|
|
353
400
|
});
|
|
@@ -12,6 +12,23 @@ export interface RunResult {
|
|
|
12
12
|
*/
|
|
13
13
|
export type Runner = (cmd: string, args: string[]) => RunResult;
|
|
14
14
|
export declare const spawnRunner: Runner;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the user lingers — Linux only (SPEC §7).
|
|
17
|
+
*
|
|
18
|
+
* `loginctl show-user <user> -p Linger` is the question, and all three answers
|
|
19
|
+
* are distinct and useful: on (nothing to say), off (the service dies at
|
|
20
|
+
* logout, and the fix needs `sudo`), and *unknown* — no `loginctl`, no logind,
|
|
21
|
+
* a container. Collapsing unknown into "off" would print a `sudo` instruction
|
|
22
|
+
* to someone whose machine has no `loginctl` to run it with.
|
|
23
|
+
*/
|
|
24
|
+
export interface LingerState {
|
|
25
|
+
/** False where the platform has no such concept (darwin). */
|
|
26
|
+
applies: boolean;
|
|
27
|
+
/** On, off, or undefined when the check could not answer. */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
/** The user the check asked about, for the printed command. */
|
|
30
|
+
user?: string;
|
|
31
|
+
}
|
|
15
32
|
export interface ServiceState {
|
|
16
33
|
/** Is a unit file installed for us? */
|
|
17
34
|
registered: boolean;
|
|
@@ -20,6 +37,28 @@ export interface ServiceState {
|
|
|
20
37
|
pid?: string;
|
|
21
38
|
/** The Paddock arguments recorded in the installed unit, interpreter stripped. */
|
|
22
39
|
argv: string[];
|
|
40
|
+
/**
|
|
41
|
+
* The interpreter and script the installed unit actually names.
|
|
42
|
+
*
|
|
43
|
+
* Reported separately from {@link argv} because `status` has to be able to
|
|
44
|
+
* say "the Node it runs is not there any more" (SPEC §7), which is only
|
|
45
|
+
* answerable from the paths in the FILE. Under nvm, fnm or volta, removing a
|
|
46
|
+
* Node version leaves a unit naming an interpreter that no longer exists —
|
|
47
|
+
* and the symptom is Paddock being absent with an empty log, because it never
|
|
48
|
+
* got as far as running. Undefined for a unit we could not parse.
|
|
49
|
+
*/
|
|
50
|
+
nodePath?: string;
|
|
51
|
+
cliPath?: string;
|
|
52
|
+
/**
|
|
53
|
+
* The log file the installed unit names, if it names one.
|
|
54
|
+
*
|
|
55
|
+
* Read back rather than recomputed so `status` and `logs` report the file the
|
|
56
|
+
* service is really appending to. A unit written before the logs moved to
|
|
57
|
+
* `<dataDir>/logs/service.log` names the old pair under `<dataDir>/service/`,
|
|
58
|
+
* and telling its owner to read a file nothing writes to is worse than
|
|
59
|
+
* telling them nothing.
|
|
60
|
+
*/
|
|
61
|
+
logPath?: string;
|
|
23
62
|
/**
|
|
24
63
|
* Does the unit ON DISK come back after a graceful stop (#872)?
|
|
25
64
|
*
|
|
@@ -64,8 +103,13 @@ export interface ServiceBackend {
|
|
|
64
103
|
start(): void;
|
|
65
104
|
stop(): void;
|
|
66
105
|
restart(): void;
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Does this user linger? (SPEC §7.) Asked of the OS, not assumed.
|
|
108
|
+
*
|
|
109
|
+
* The note used to be printed unconditionally on Linux, which meant the
|
|
110
|
+
* people who had already run `enable-linger` were told to run it at every
|
|
111
|
+
* install and every status — so the one line that mattered to the people who
|
|
112
|
+
* had NOT was indistinguishable from boilerplate.
|
|
113
|
+
*/
|
|
114
|
+
linger(): LingerState;
|
|
71
115
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CliOptions, type ServiceAction } from "../args.js";
|
|
2
2
|
import { isNpxPath, serviceDir, type ServiceSpec } from "./spec.js";
|
|
3
3
|
import { type Runner } from "./backend.js";
|
|
4
|
-
import { type Readiness } from "./ready.js";
|
|
4
|
+
import { type HealthAnswer, type Readiness } from "./ready.js";
|
|
5
5
|
export interface ServiceContext {
|
|
6
6
|
/** `process.platform`. */
|
|
7
7
|
platform: NodeJS.Platform;
|
|
@@ -26,8 +26,62 @@ export interface ServiceContext {
|
|
|
26
26
|
* message — twenty seconds per case, and green for the wrong reason.
|
|
27
27
|
*/
|
|
28
28
|
checkReady?: (url: string) => Promise<Readiness>;
|
|
29
|
+
/**
|
|
30
|
+
* "What does `/api/health` say?", injected for the same reason `checkReady`
|
|
31
|
+
* is — `status` has to be assertable without a server to ask.
|
|
32
|
+
*/
|
|
33
|
+
probeHealth?: (url: string) => Promise<HealthAnswer | undefined>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The version of the Paddock the unit points at.
|
|
37
|
+
*
|
|
38
|
+
* Found by walking up from the `cli.js` in the unit to the nearest
|
|
39
|
+
* `package.json`, which is `packages/server/package.json` in both the repo and
|
|
40
|
+
* the published tarball — the same hop the CLI itself makes to answer
|
|
41
|
+
* `--version`. Undefined when the unit names a path that is no longer there,
|
|
42
|
+
* which is a state `status` reports rather than guesses around.
|
|
43
|
+
*/
|
|
44
|
+
export declare function installedVersionOf(cliPath: string | undefined): string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* What `service status` knows, before it is either printed or serialised.
|
|
47
|
+
*
|
|
48
|
+
* One shape for both renderings, so `--json` cannot quietly disagree with the
|
|
49
|
+
* prose above it — which is the usual way a `--json` flag rots.
|
|
50
|
+
*/
|
|
51
|
+
export interface StatusReport {
|
|
52
|
+
installed: boolean;
|
|
53
|
+
running: boolean;
|
|
54
|
+
/** Did `/api/health` answer? Only then is the service actually usable. */
|
|
55
|
+
answering: boolean;
|
|
56
|
+
/** The version the unit points at, and the version that answered. */
|
|
57
|
+
installedVersion: string | null;
|
|
58
|
+
runningVersion: string | null;
|
|
59
|
+
port: number | null;
|
|
60
|
+
host: string | null;
|
|
61
|
+
url: string | null;
|
|
62
|
+
pid: number | null;
|
|
63
|
+
/** The Paddock arguments the unit records, interpreter and script stripped. */
|
|
64
|
+
args: string[];
|
|
65
|
+
/** The plist / unit file — present whether or not it exists. */
|
|
66
|
+
unit: string;
|
|
67
|
+
manager: "launchd" | "systemd";
|
|
68
|
+
/** The one file both streams append to. */
|
|
69
|
+
log: string | null;
|
|
70
|
+
dataDir: string | null;
|
|
71
|
+
node: string | null;
|
|
72
|
+
cli: string | null;
|
|
73
|
+
/** Linux only: null where the platform has no such concept. */
|
|
74
|
+
linger: boolean | null;
|
|
75
|
+
/** A stale unit, a missing Node — things that are wrong but not fatal. */
|
|
76
|
+
warnings: string[];
|
|
29
77
|
}
|
|
30
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Dispatch, returning the exit code rather than setting one (SPEC §2).
|
|
80
|
+
*
|
|
81
|
+
* The code is returned up to `paddock.ts`, which assigns `process.exitCode` —
|
|
82
|
+
* never `process.exit()`, which truncates piped output mid-write.
|
|
83
|
+
*/
|
|
84
|
+
export declare function runService(action: ServiceAction, opts: CliOptions, ctx: ServiceContext): Promise<number>;
|
|
31
85
|
/** Exported for the entrypoint's context assembly, and for tests. */
|
|
32
86
|
export { serviceDir, isNpxPath };
|
|
33
87
|
export type { ServiceSpec };
|