@edspencer/paddock 0.80.0 → 0.82.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 +1 -1
- package/packages/server/dist/app.d.ts +7 -0
- package/packages/server/dist/app.js +1 -1
- package/packages/server/dist/auth.d.ts +3 -2
- package/packages/server/dist/auth.js +11 -0
- package/packages/server/dist/background-live.d.ts +17 -0
- package/packages/server/dist/background-live.js +24 -0
- package/packages/server/dist/busy.d.ts +96 -0
- package/packages/server/dist/busy.js +88 -0
- package/packages/server/dist/cli/args.d.ts +70 -29
- package/packages/server/dist/cli/args.js +306 -7
- package/packages/server/dist/cli/busy-command.d.ts +58 -0
- package/packages/server/dist/cli/busy-command.js +108 -0
- package/packages/server/dist/cli/busy.d.ts +69 -0
- package/packages/server/dist/cli/busy.js +150 -0
- package/packages/server/dist/cli/output.d.ts +52 -0
- package/packages/server/dist/cli/output.js +84 -0
- package/packages/server/dist/cli/paddock.js +65 -21
- package/packages/server/dist/cli/service/index.d.ts +64 -0
- package/packages/server/dist/cli/service/index.js +208 -7
- package/packages/server/dist/cli/service/launchd.d.ts +15 -1
- package/packages/server/dist/cli/service/launchd.js +116 -18
- package/packages/server/dist/cli/service/ready.d.ts +30 -0
- package/packages/server/dist/cli/service/ready.js +35 -0
- package/packages/server/dist/cli/service/systemd.js +27 -0
- package/packages/server/dist/cli/update/install-method.d.ts +131 -0
- package/packages/server/dist/cli/update/install-method.js +258 -0
- package/packages/server/dist/cli/update/lock.d.ts +40 -0
- package/packages/server/dist/cli/update/lock.js +108 -0
- package/packages/server/dist/cli/update/package-manager.d.ts +109 -0
- package/packages/server/dist/cli/update/package-manager.js +221 -0
- package/packages/server/dist/cli/update/registry.d.ts +89 -0
- package/packages/server/dist/cli/update/registry.js +145 -0
- package/packages/server/dist/cli/update/semver.d.ts +68 -0
- package/packages/server/dist/cli/update/semver.js +232 -0
- package/packages/server/dist/cli/update/update-check.d.ts +80 -0
- package/packages/server/dist/cli/update/update-check.js +116 -0
- package/packages/server/dist/cli/update/update.d.ts +160 -0
- package/packages/server/dist/cli/update/update.js +609 -0
- package/packages/server/dist/cli/update-command.d.ts +20 -0
- package/packages/server/dist/cli/update-command.js +113 -0
- package/packages/server/dist/herdctl.d.ts +19 -0
- package/packages/server/dist/herdctl.js +26 -0
- package/packages/server/dist/routes/meta.js +45 -0
- package/packages/server/dist/runtime-file.d.ts +26 -0
- package/packages/server/dist/runtime-file.js +91 -0
- package/packages/server/dist/start.d.ts +33 -0
- package/packages/server/dist/start.js +115 -8
- package/packages/server/dist/ws-context.d.ts +9 -0
- package/packages/server/dist/ws-turn.js +1 -0
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-DHczLhwQ.js → DeleteProjectDialog-CphdGB3R.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-C4OPLNO8.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-DtTOpTji.js → DiscoverView-B_kcF4sm.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-BRoBh1C1.js → InstanceConfigPage-CvUK5DKW.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-DJf8IJuk.js → MarkdownRenderer-DvZ-j_Ao.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-EioguUKW.js → ProjectRedirect-DayHvUH2.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-BiKnGfk6.js → ProjectView-CCw83gUi.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-D6kfaIal.js → ProjectsGrid-B3Klg2H4.js} +1 -1
- package/packages/web/dist/assets/{RootHome-DpcyuV6e.js → RootHome-BxcDvpgI.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-CHM29PhP.js → Surfaces-7UlhFBSG.js} +1 -1
- package/packages/web/dist/assets/{arc-fiqD05Hu.js → arc-DNkh5o5Q.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BuuB2Nkf.js → architectureDiagram-3BPJPVTR-BrgN3oLF.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DQMBuzfi.js → blockDiagram-GPEHLZMM-D05XOJSk.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-BKN0UxCH.js → c4Diagram-AAUBKEIU-CKaMBhMP.js} +1 -1
- package/packages/web/dist/assets/channel-JrEedxB8.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-DW37EupX.js → chunk-2J33WTMH-8hgadCQc.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-Dyv7zV_c.js → chunk-4BX2VUAB-CBfEC-pu.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-DzMR0dOk.js → chunk-55IACEB6-DV1eMgHk.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-BdZ3Sor8.js → chunk-727SXJPM-Cfgl5fxY.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-B4i7_og0.js → chunk-AQP2D5EJ-BXhqD295.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-jcTr85it.js → chunk-FMBD7UC4-TFGOPtek.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-fgMsadqB.js → chunk-ND2GUHAM-CHFM665R.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-bJNHYS1W.js → chunk-QZHKN3VN-CqEf6MMT.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CHJiQp-W.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CHJiQp-W.js +2 -0
- package/packages/web/dist/assets/{core-Bk4KFrmN.js → core-DmEaKxBb.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BIqCSlQy.js → cose-bilkent-S5V4N54A-BnGI6jkr.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-6nyhtVQz.js → dagre-BM42HDAG-CKoPx61E.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-CjizfipR.js → diagram-2AECGRRQ-mMmtlBl1.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-DlgB5vWb.js → diagram-5GNKFQAL-Cdz9Mtnt.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-CNC9qanV.js → diagram-KO2AKTUF-DQxM3wYj.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-DSJ-s9lh.js → diagram-LMA3HP47-BWrinPsq.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-C9FoHBDg.js → diagram-OG6HWLK6-U6MYUve2.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-Cj_AY97j.js → documentTitle-DGjRXuTP.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-z6gku4Yt.js → erDiagram-TEJ5UH35-xqMCsXxY.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-Dl5c_FgU.js → flowDiagram-I6XJVG4X-PMfy_czF.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-CIscJ-Dt.js → ganttDiagram-6RSMTGT7-whhYYE4N.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-CvttOXM_.js → gitGraphDiagram-PVQCEYII-C51JW4tf.js} +1 -1
- package/packages/web/dist/assets/{index-DN82do6Z.js → index-CHOtvlnO.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-dzWYdDJj.js → infoDiagram-5YYISTIA-SH8cr905.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-8yX9k4bo.js → ishikawaDiagram-YF4QCWOH-BjqaoU3U.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-BmitzJDR.js → journeyDiagram-JHISSGLW-CBmdEVrd.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BZmIkfkW.js → kanban-definition-UN3LZRKU-U8njjMLZ.js} +1 -1
- package/packages/web/dist/assets/{linear-BBLXE4o7.js → linear-DF66GPs1.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-mSKff4yu.js → mermaid.core-CouJz7Ph.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-BWAUa4CP.js → mindmap-definition-RKZ34NQL-BYIf0oFt.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-VmVdiL4C.js → pieDiagram-4H26LBE5-Cm8rgg09.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-Dybfv6NU.js → quadrantDiagram-W4KKPZXB-C3Il5kOH.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-BPnOvB7w.js → requirementDiagram-4Y6WPE33-D3PE2nTe.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-YXIEmKw7.js → sankeyDiagram-5OEKKPKP-DdmvBM18.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DintIRUe.js → sequenceDiagram-3UESZ5HK-B8M_GAIq.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-eMsjahWp.js → stateDiagram-AJRCARHV-DZkXnK1_.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CjCdT9Ls.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-as4zRCPn.js → timeline-definition-PNZ67QCA-IHXq7rqi.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CPzvUkM0.js → vennDiagram-CIIHVFJN-Dga7tLtT.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-q6moXj5g.js → wardley-L42UT6IY-CmcwnEJ-.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-BsEEb9Gm.js → wardleyDiagram-YWT4CUSO-zHuSLrD4.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-DSgZlcZW.js → xychartDiagram-2RQKCTM6-D93MU_ic.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-BSGSzTEs.js +0 -2
- package/packages/web/dist/assets/channel-B7yh6Su_.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DIlhMPgi.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DIlhMPgi.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CpAOGzbO.js +0 -2
|
@@ -88,7 +88,7 @@ export const CONFIG_ACTIONS = ["show", "eject"];
|
|
|
88
88
|
* Paddock and Managers, every existing service unit's `ExecStart` names it, and
|
|
89
89
|
* it is in two years of other people's scripts. They parse to the same command.
|
|
90
90
|
*/
|
|
91
|
-
export const VERBS = ["serve", "start", "service", "config"];
|
|
91
|
+
export const VERBS = ["serve", "start", "service", "config", "busy", "update", "upgrade"];
|
|
92
92
|
/**
|
|
93
93
|
* Split an action off a verb's argv, then parse what follows as flags.
|
|
94
94
|
*
|
|
@@ -141,7 +141,74 @@ function parseAction(verb, actions, rest) {
|
|
|
141
141
|
* for. Corrected rather than deleted because the grammar it documents is real
|
|
142
142
|
* and load-bearing; it was only the example's error that was misdescribed.
|
|
143
143
|
*/
|
|
144
|
+
/**
|
|
145
|
+
* Flags that mean something on SOME commands only, and the commands they mean
|
|
146
|
+
* it on.
|
|
147
|
+
*
|
|
148
|
+
* Rejecting a flag the command cannot act on, rather than parsing and dropping
|
|
149
|
+
* it, is the difference between being told and guessing: the two readings of a
|
|
150
|
+
* quiet `service uninstall --force` are "it overrode something" and "it did
|
|
151
|
+
* nothing", and the exit code is 0 either way. The same trap sits under
|
|
152
|
+
* `service status --refresh` and `serve --check`.
|
|
153
|
+
*
|
|
154
|
+
* One table, checked once in {@link parseCommand}, rather than a rule per
|
|
155
|
+
* command — a second copy of this logic is a second chance for the two to
|
|
156
|
+
* disagree about where a flag applies. `where` is matched against the
|
|
157
|
+
* invocation as the user would write it (`service stop`, `update`).
|
|
158
|
+
*/
|
|
159
|
+
const SCOPED_FLAGS = [
|
|
160
|
+
{
|
|
161
|
+
flag: "--force",
|
|
162
|
+
read: (o) => o.force === true,
|
|
163
|
+
where: ["service stop", "service restart", "update"],
|
|
164
|
+
why: "It overrides the busy check, and those are the commands that run one.",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
flag: "--check",
|
|
168
|
+
read: (o) => o.checkOnly === true,
|
|
169
|
+
where: ["update"],
|
|
170
|
+
why: "It reports what an update would do, and changes nothing.",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
flag: "--to",
|
|
174
|
+
read: (o) => o.to !== undefined,
|
|
175
|
+
where: ["update"],
|
|
176
|
+
why: "It names the version or dist-tag to move to.",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
flag: "--yes",
|
|
180
|
+
read: (o) => o.yes === true,
|
|
181
|
+
where: ["update"],
|
|
182
|
+
why: "`update` is the only command that asks a question.",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
flag: "--refresh",
|
|
186
|
+
read: (o) => o.refresh === true,
|
|
187
|
+
where: ["service install"],
|
|
188
|
+
why: "It re-renders an existing unit, keeping the settings it carries.",
|
|
189
|
+
},
|
|
190
|
+
];
|
|
144
191
|
export function parseCommand(argv) {
|
|
192
|
+
const command = dispatch(argv);
|
|
193
|
+
// `--help` wins over every rule below, the same way it wins over a missing
|
|
194
|
+
// action above: asking for the usage is not a usage error.
|
|
195
|
+
if (command.opts.help)
|
|
196
|
+
return command;
|
|
197
|
+
const said = "action" in command && command.action !== undefined
|
|
198
|
+
? `${command.verb} ${command.action}`
|
|
199
|
+
: command.verb;
|
|
200
|
+
for (const rule of SCOPED_FLAGS) {
|
|
201
|
+
if (!rule.read(command.opts))
|
|
202
|
+
continue;
|
|
203
|
+
if (rule.where.includes(said))
|
|
204
|
+
continue;
|
|
205
|
+
const quoted = rule.where.map((w) => `\`${w}\``);
|
|
206
|
+
const list = quoted.length === 1 ? quoted[0] : `${quoted.slice(0, -1).join(", ")} and ${quoted.at(-1)}`;
|
|
207
|
+
throw new UsageError(`${rule.flag} applies to ${list} only, not \`${said}\`.\n${rule.why}`);
|
|
208
|
+
}
|
|
209
|
+
return command;
|
|
210
|
+
}
|
|
211
|
+
function dispatch(argv) {
|
|
145
212
|
const [first, ...rest] = argv;
|
|
146
213
|
if (first === "service") {
|
|
147
214
|
return { verb: "service", ...parseAction("service", SERVICE_ACTIONS, rest) };
|
|
@@ -149,6 +216,14 @@ export function parseCommand(argv) {
|
|
|
149
216
|
if (first === "config") {
|
|
150
217
|
return { verb: "config", ...parseAction("config", CONFIG_ACTIONS, rest) };
|
|
151
218
|
}
|
|
219
|
+
// `busy` (SPEC §1, §4). A top-level verb rather than a `service` action
|
|
220
|
+
// because its whole point is to be askable where there is no service to ask:
|
|
221
|
+
// a Docker container, a Watchtower pre-update hook, an Ansible task.
|
|
222
|
+
if (first === "busy")
|
|
223
|
+
return { verb: "busy", opts: parseArgs(rest) };
|
|
224
|
+
// `update`, with `upgrade` accepted and undocumented (SPEC §1).
|
|
225
|
+
if (first === "update" || first === "upgrade")
|
|
226
|
+
return { verb: "update", opts: parseArgs(rest) };
|
|
152
227
|
// `serve` and `start` are one command: see VERBS.
|
|
153
228
|
if (first === "serve" || first === "start")
|
|
154
229
|
return { verb: "serve", opts: parseArgs(rest) };
|
|
@@ -220,6 +295,31 @@ export function parseArgs(argv) {
|
|
|
220
295
|
case "--include-env":
|
|
221
296
|
opts.includeEnv = true;
|
|
222
297
|
break;
|
|
298
|
+
// `service stop` / `service restart` / `update`: override the busy check
|
|
299
|
+
// (SPEC §4).
|
|
300
|
+
case "--force":
|
|
301
|
+
opts.force = true;
|
|
302
|
+
break;
|
|
303
|
+
// `update` flags (SPEC §1).
|
|
304
|
+
case "--check":
|
|
305
|
+
opts.checkOnly = true;
|
|
306
|
+
break;
|
|
307
|
+
case "--to": {
|
|
308
|
+
const v = next();
|
|
309
|
+
if (v === "" || v.startsWith("-")) {
|
|
310
|
+
throw new UsageError(`${arg} needs a version or a dist-tag, got: ${JSON.stringify(v)}`);
|
|
311
|
+
}
|
|
312
|
+
opts.to = v;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case "-y":
|
|
316
|
+
case "--yes":
|
|
317
|
+
opts.yes = true;
|
|
318
|
+
break;
|
|
319
|
+
// `service install --refresh` (SPEC §5 step 11).
|
|
320
|
+
case "--refresh":
|
|
321
|
+
opts.refresh = true;
|
|
322
|
+
break;
|
|
223
323
|
// `service logs` flags, the names `tail`, `journalctl` and every other
|
|
224
324
|
// log reader uses — this is not the place to be original.
|
|
225
325
|
case "-f":
|
|
@@ -293,6 +393,8 @@ export function explainListenError(err, host, port) {
|
|
|
293
393
|
export const USAGE_SYNOPSIS = `Usage
|
|
294
394
|
paddock [serve] [options] run the server in this terminal
|
|
295
395
|
paddock service <action> [options] install|uninstall|status|start|stop|restart|logs
|
|
396
|
+
paddock busy [--json] is the running instance busy?
|
|
397
|
+
paddock update [--check] [--to <v>] install a newer Paddock
|
|
296
398
|
paddock config <show|eject> [options] what this instance's config resolved to
|
|
297
399
|
paddock -v | --version print the version and exit
|
|
298
400
|
paddock -h | --help the full help`;
|
|
@@ -306,10 +408,16 @@ Usage
|
|
|
306
408
|
paddock service uninstall stop it and remove the unit
|
|
307
409
|
paddock service status [--json] installed? running? which version?
|
|
308
410
|
paddock service start start an installed service
|
|
309
|
-
paddock service stop
|
|
310
|
-
paddock service restart
|
|
411
|
+
paddock service stop [--force] stop it, leave it installed
|
|
412
|
+
paddock service restart [--force] stop it and start it again
|
|
311
413
|
paddock service logs [-f] [-n <lines>] the service's log
|
|
312
414
|
(\`paddock service --help\`)
|
|
415
|
+
paddock busy [--json] is it busy? exit 0 no, 75 yes, 3
|
|
416
|
+
nothing answering
|
|
417
|
+
(\`paddock busy --help\`)
|
|
418
|
+
paddock update [--check] [--to <v>] install a newer Paddock and restart
|
|
419
|
+
the service
|
|
420
|
+
(\`paddock update --help\`)
|
|
313
421
|
paddock config show [--resolved] what this instance's config resolved
|
|
314
422
|
to, and where each value came from
|
|
315
423
|
paddock config eject [--write] freeze that resolution into
|
|
@@ -326,11 +434,15 @@ Options
|
|
|
326
434
|
-h, --help Show this help
|
|
327
435
|
|
|
328
436
|
Exit codes
|
|
329
|
-
0 done; running and answering
|
|
437
|
+
0 done; running and answering; up to date; not busy
|
|
330
438
|
1 failed
|
|
331
439
|
2 usage: an unknown command or flag, or a bad value
|
|
332
|
-
3 not running: installed, but not running or not answering
|
|
440
|
+
3 not running: installed, but not running or not answering; nothing answers
|
|
333
441
|
4 not installed: there is no service unit
|
|
442
|
+
5 this install cannot update itself; the right command was printed (update)
|
|
443
|
+
6 the update failed and was rolled back; the old version is back (update)
|
|
444
|
+
7 the update failed and could not be rolled back; a person is needed
|
|
445
|
+
75 busy: refused, and why was printed. Try later, or use --force
|
|
334
446
|
|
|
335
447
|
Opening your own directories
|
|
336
448
|
Where you run this from does not matter — the instance is decided by its data
|
|
@@ -434,18 +546,44 @@ export const SERVICE_USAGE = `paddock service — keep Paddock running in the ba
|
|
|
434
546
|
|
|
435
547
|
Usage
|
|
436
548
|
paddock service install [options] register it and start it now
|
|
549
|
+
paddock service install --refresh re-write the unit, keeping its settings
|
|
437
550
|
paddock service uninstall stop it and deregister it
|
|
438
551
|
paddock service status [--json] is it registered, is it running, which
|
|
439
552
|
version, where are the logs
|
|
440
553
|
paddock service start start an installed service that is stopped
|
|
441
|
-
paddock service stop
|
|
442
|
-
paddock service restart
|
|
554
|
+
paddock service stop [--force] stop it, and leave it installed
|
|
555
|
+
paddock service restart [--force] stop it and start it again, re-reading config
|
|
443
556
|
paddock service logs [-f] [-n <n>] the service's log (the last 50 lines)
|
|
444
557
|
|
|
445
558
|
Options (logs)
|
|
446
559
|
-f, --follow keep printing what gets appended, until Ctrl-C
|
|
447
560
|
-n, --lines <n> how many lines to print first (default 50)
|
|
448
561
|
|
|
562
|
+
Options (stop, restart)
|
|
563
|
+
--force stop it even though it is busy
|
|
564
|
+
|
|
565
|
+
Options (install)
|
|
566
|
+
--refresh Re-render the unit from the settings the EXISTING one
|
|
567
|
+
carries — its port, host, data dir and verbosity —
|
|
568
|
+
rather than from this shell's flags and environment.
|
|
569
|
+
This is what \`paddock update\` runs, through the new
|
|
570
|
+
version's CLI, so the unit is written in the format
|
|
571
|
+
that version writes. Exit 4 with nothing installed:
|
|
572
|
+
there is no unit to refresh, and doing a first
|
|
573
|
+
install with defaults instead is how a --port 9000
|
|
574
|
+
service comes back on 7233.
|
|
575
|
+
|
|
576
|
+
Busy
|
|
577
|
+
\`stop\` and \`restart\` ask the running instance whether it is busy first, and
|
|
578
|
+
refuse with exit 75 if it is — a chat turn in flight, a background task, a
|
|
579
|
+
scheduled job. \`paddock busy\` asks the same question on its own.
|
|
580
|
+
|
|
581
|
+
Being refused is the useful case: a restart kills a chat turn mid-sentence,
|
|
582
|
+
and the turn cannot be resumed from where it was. Wait, or --force.
|
|
583
|
+
|
|
584
|
+
\`uninstall\` does not ask. Removing the service is an explicit act, and
|
|
585
|
+
someone uninstalling it has already decided.
|
|
586
|
+
|
|
449
587
|
Options (install only — recorded in the generated unit)
|
|
450
588
|
-p, --port <port> HTTP/WS port (default 7233)
|
|
451
589
|
--host <host> Bind address (default 127.0.0.1)
|
|
@@ -490,6 +628,7 @@ Exit codes
|
|
|
490
628
|
4 not installed: there is no unit (status, logs, start, stop, restart)
|
|
491
629
|
1 something failed
|
|
492
630
|
2 usage: an unknown action or flag, or a bad value
|
|
631
|
+
75 busy, and --force was not given (stop, restart)
|
|
493
632
|
|
|
494
633
|
Installed from npx?
|
|
495
634
|
\`service install\` refuses. An npx cache path is hash-keyed and npm may prune
|
|
@@ -505,6 +644,66 @@ A note on access
|
|
|
505
644
|
process that could reach the port could already read the same Claude login as
|
|
506
645
|
you. Set PADDOCK_AUTH_MODE if you want a credential on it anyway.
|
|
507
646
|
|
|
647
|
+
Docs: https://github.com/edspencer/paddock`;
|
|
648
|
+
export const BUSY_USAGE = `paddock busy — is the running instance in the middle of something?
|
|
649
|
+
|
|
650
|
+
Usage
|
|
651
|
+
paddock busy ask, and say so in a sentence
|
|
652
|
+
paddock busy --json the same answer as one JSON document
|
|
653
|
+
paddock busy -d <path> ask a particular instance
|
|
654
|
+
|
|
655
|
+
Exit codes — this command is FOR its exit code
|
|
656
|
+
0 not busy. Nothing a restart would cut short.
|
|
657
|
+
75 busy. What is running was printed.
|
|
658
|
+
3 nothing answered. No instance is running, so there is nothing to wait for.
|
|
659
|
+
1 something answered, but not as Paddock's busy check.
|
|
660
|
+
|
|
661
|
+
Options
|
|
662
|
+
-d, --data-dir <path> Which instance to ask (default ~/.paddock, or
|
|
663
|
+
\$PADDOCK_DATA_DIR) — the same rule \`paddock serve\`
|
|
664
|
+
uses, so the two always mean the same instance
|
|
665
|
+
--json One JSON document on stdout: running, busy, url,
|
|
666
|
+
version, counts. Prose goes to stderr
|
|
667
|
+
-h, --help Show this help
|
|
668
|
+
|
|
669
|
+
What counts as busy
|
|
670
|
+
Only work a restart would LOSE or cut short:
|
|
671
|
+
|
|
672
|
+
chat a chat turn running — a human turn, a fired trigger, a cron
|
|
673
|
+
fire. A turn is a live claude subprocess writing a transcript,
|
|
674
|
+
and it cannot be resumed from where it was interrupted.
|
|
675
|
+
background a background task a live session is still holding after its
|
|
676
|
+
turn ended: a background shell, a sub-agent, a Monitor.
|
|
677
|
+
job a scheduled job herdctl is running.
|
|
678
|
+
|
|
679
|
+
Not busy: an idle session (it resumes), an open browser tab (it reconnects),
|
|
680
|
+
a pending curation sweep (it re-runs from its watermark).
|
|
681
|
+
|
|
682
|
+
Where to run it from
|
|
683
|
+
From a terminal. A Paddock chat that runs this is itself one of the chat turns
|
|
684
|
+
it counts, so it will always see itself as busy — and \`service stop --force\`
|
|
685
|
+
from inside a chat stops the service that is running the chat, which does not
|
|
686
|
+
come back with an explanation. That is why the refusal says so.
|
|
687
|
+
|
|
688
|
+
For scripts, containers and Watchtower
|
|
689
|
+
\`busy\` is there for callers with no service to ask. 75 is EX_TEMPFAIL, which
|
|
690
|
+
is the code Watchtower's pre-update hook reads as "skip this one":
|
|
691
|
+
|
|
692
|
+
labels:
|
|
693
|
+
com.centurylinklabs.watchtower.lifecycle.pre-update: paddock busy
|
|
694
|
+
|
|
695
|
+
An Ansible task does the same thing with a retry. Nothing here contacts the
|
|
696
|
+
network or the npm registry, and the question is answered from memory — it is
|
|
697
|
+
cheap enough to ask in a loop.
|
|
698
|
+
|
|
699
|
+
How it finds the instance
|
|
700
|
+
\`<data-dir>/runtime.json\`, written 0600 when the server starts listening and
|
|
701
|
+
removed when it stops. There is no fallback to a default port: guessing one
|
|
702
|
+
would ask whatever else happened to be listening there, and get an answer.
|
|
703
|
+
|
|
704
|
+
A Paddock from before this feature shipped writes no such file, so it reads as
|
|
705
|
+
not running (exit 3). Upgrade it and restart it once.
|
|
706
|
+
|
|
508
707
|
Docs: https://github.com/edspencer/paddock`;
|
|
509
708
|
export const CONFIG_USAGE = `paddock config — what this instance's configuration actually resolved to
|
|
510
709
|
|
|
@@ -602,3 +801,103 @@ Reading nothing, writing nothing
|
|
|
602
801
|
JWT signing material here to hide or reveal.
|
|
603
802
|
|
|
604
803
|
Docs: https://github.com/edspencer/paddock`;
|
|
804
|
+
/**
|
|
805
|
+
* `update`'s own synopsis, printed after a usage error from it (SPEC §2).
|
|
806
|
+
*
|
|
807
|
+
* `update` has one usage error of its own — "there is no terminal to ask, so
|
|
808
|
+
* `--yes` is required" — and the contract says the usage follows the message.
|
|
809
|
+
* {@link USAGE_SYNOPSIS} is the wrong text there: it lists every command, and
|
|
810
|
+
* the reader's problem is one flag on the command they already typed.
|
|
811
|
+
*/
|
|
812
|
+
export const UPDATE_SYNOPSIS = `Usage
|
|
813
|
+
paddock update [--yes] install the latest release
|
|
814
|
+
paddock update --check [--json] what a new version would be; changes nothing
|
|
815
|
+
paddock update --to <version|tag> a particular version, or a dist-tag
|
|
816
|
+
paddock update --force skip the busy check, and nothing else
|
|
817
|
+
paddock update --help the full help`;
|
|
818
|
+
export const UPDATE_USAGE = `paddock update — install a newer Paddock, and put the service back up
|
|
819
|
+
|
|
820
|
+
Usage
|
|
821
|
+
paddock update update to the latest release, asking first
|
|
822
|
+
paddock update --check what a new version would be; changes nothing
|
|
823
|
+
paddock update --yes do not ask (required when there is no terminal)
|
|
824
|
+
paddock update --to 0.79.1 a particular version — the only way to go BACK
|
|
825
|
+
paddock update --to next a dist-tag
|
|
826
|
+
paddock update --json one JSON document on stdout
|
|
827
|
+
|
|
828
|
+
Options
|
|
829
|
+
--check Ask the registry, report, and change nothing. Exits 0
|
|
830
|
+
whether or not there is an update — scripts read
|
|
831
|
+
\`updateAvailable\` from --json
|
|
832
|
+
--to <version|tag> Install this version or dist-tag instead of latest.
|
|
833
|
+
Required to move to a LOWER version: an update never
|
|
834
|
+
goes backwards by accident
|
|
835
|
+
--yes, -y Skip the question. Required when stdin is not a
|
|
836
|
+
terminal, where a command that would have asked exits
|
|
837
|
+
2 rather than guessing
|
|
838
|
+
--force Skip the busy check, and nothing else — the health
|
|
839
|
+
check and the rollback still run
|
|
840
|
+
--json One JSON document on stdout; all prose on stderr
|
|
841
|
+
-h, --help Show this help
|
|
842
|
+
|
|
843
|
+
What it does, in order
|
|
844
|
+
1 takes <data-dir>/update.lock, so two updates cannot interleave
|
|
845
|
+
2 works out how this copy was installed (see "Installs that cannot update
|
|
846
|
+
themselves" below)
|
|
847
|
+
3 asks the registry for the target version and its \`engines.node\`
|
|
848
|
+
4 compares, and stops here with --check
|
|
849
|
+
5 checks the global folder is writable, and prints a sudo line if not.
|
|
850
|
+
It never runs sudo itself
|
|
851
|
+
6 asks the running instance whether it is BUSY — before anything is stopped
|
|
852
|
+
7 asks you, unless --yes
|
|
853
|
+
8 fetches both the new and the current version into the npm cache, while
|
|
854
|
+
the old version is still serving, so a rollback has its package to hand
|
|
855
|
+
9 stops the service
|
|
856
|
+
10 installs with the package manager this copy came from
|
|
857
|
+
11 hands over to the NEW CLI, which re-writes the service unit
|
|
858
|
+
(\`service install --refresh\`) and starts it
|
|
859
|
+
12 waits for /api/health to report the version it just installed
|
|
860
|
+
13 and if any of 10–12 fails, puts the old version back
|
|
861
|
+
|
|
862
|
+
Exit codes — this command is FOR its exit code
|
|
863
|
+
0 updated, or already up to date, or --check reported
|
|
864
|
+
1 something failed, and nothing had been stopped yet
|
|
865
|
+
2 usage — including "there is no terminal to ask, so --yes is required"
|
|
866
|
+
5 this install cannot update itself. The right command was printed
|
|
867
|
+
6 the new version did not start; the old one is running again
|
|
868
|
+
7 the new version did not start and the old one could not be restored.
|
|
869
|
+
The commands to run by hand were printed
|
|
870
|
+
75 busy: a chat turn, a background task or a scheduled job is running.
|
|
871
|
+
Wait, or --force
|
|
872
|
+
|
|
873
|
+
Installs that cannot update themselves (exit 5)
|
|
874
|
+
Paddock reads where it is running from, and five layouts are not ours to
|
|
875
|
+
replace. Each prints the command that WOULD work:
|
|
876
|
+
|
|
877
|
+
a container image docker compose pull && docker compose up -d
|
|
878
|
+
a managed install whatever PADDOCK_UPDATE_HINT says
|
|
879
|
+
npx / pnpm dlx npx @edspencer/paddock@latest already runs the latest
|
|
880
|
+
a checkout git pull && npm install && npm run build
|
|
881
|
+
a project dependency update it in that project
|
|
882
|
+
a yarn classic global yarn global add @edspencer/paddock
|
|
883
|
+
|
|
884
|
+
A global npm, pnpm, bun or volta install updates in place.
|
|
885
|
+
|
|
886
|
+
Where it runs from
|
|
887
|
+
From a terminal, never from inside the service. An updater the service
|
|
888
|
+
started — a Paddock chat, say — is in the service's own process group, so
|
|
889
|
+
stopping the service kills the updater half way through step 9. The busy
|
|
890
|
+
check refuses first and says so; --force from inside a chat would stop the
|
|
891
|
+
service running that chat, which then never gets to report what happened.
|
|
892
|
+
|
|
893
|
+
Asking the registry
|
|
894
|
+
Only \`update\`, \`update --check\` and \`doctor\` ever contact the npm
|
|
895
|
+
registry, and only when you run them. There is no background check and no
|
|
896
|
+
check at startup; the server never asks. What the last check found is cached
|
|
897
|
+
in <data-dir>/update-check.json, which is what \`paddock service status\`
|
|
898
|
+
repeats — it never asks itself.
|
|
899
|
+
|
|
900
|
+
The registry is \`npm_config_registry\`, else \`npm config get registry\`,
|
|
901
|
+
else https://registry.npmjs.org.
|
|
902
|
+
|
|
903
|
+
Docs: https://github.com/edspencer/paddock`;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type BusyResult, type Nouns } from "./busy.js";
|
|
2
|
+
/**
|
|
3
|
+
* How each count in `/api/busy` is said in a sentence (`busy.ts` on the server
|
|
4
|
+
* decides the keys).
|
|
5
|
+
*
|
|
6
|
+
* `background` is spelled out rather than left to the default pluraliser,
|
|
7
|
+
* because "3 backgrounds are running" is not English — and because "background
|
|
8
|
+
* task" is the thing a user recognises from the running-work bar in the UI,
|
|
9
|
+
* where they watched it start.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PADDOCK_NOUNS: Nouns;
|
|
12
|
+
/**
|
|
13
|
+
* What to do about a busy answer — and for Paddock, the first thing to say is
|
|
14
|
+
* **where to run the command from**.
|
|
15
|
+
*
|
|
16
|
+
* This is the keeper-chat trap, and it is specific to Paddock and Managers
|
|
17
|
+
* among the six tools: Paddock's agents can drive Paddock. A keeper chat that
|
|
18
|
+
* runs `paddock service restart` or `paddock update` is itself one of the chat
|
|
19
|
+
* turns the answer just counted, so it will always see itself as busy — and
|
|
20
|
+
* `--force` from there does not override a mistaken reading, it kills the chat
|
|
21
|
+
* that typed it, mid-sentence, along with whatever it had not finished. The
|
|
22
|
+
* agent then has no way to report what happened, because the thing that would
|
|
23
|
+
* report it is the thing that died.
|
|
24
|
+
*
|
|
25
|
+
* So when `chat` is non-zero the advice is not "wait or use --force": it is
|
|
26
|
+
* "run this from a terminal". When the busy work is background tasks or
|
|
27
|
+
* scheduled jobs only, nobody is holding the keyboard inside the service and
|
|
28
|
+
* the ordinary advice applies.
|
|
29
|
+
*/
|
|
30
|
+
export declare function paddockBusyHint(result: BusyResult): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* `paddock busy [--json]` — exit 0 not busy, 75 busy, 3 nothing answering
|
|
33
|
+
* (SPEC §1, §2).
|
|
34
|
+
*
|
|
35
|
+
* The instance is found through `<data-dir>/runtime.json` and nowhere else, so
|
|
36
|
+
* this reports on the same instance `paddock serve` and `paddock config show`
|
|
37
|
+
* resolve — `resolveDataDir` in paddock.ts is the one rule all three use.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runBusy(home: string, json: boolean): Promise<number>;
|
|
40
|
+
/**
|
|
41
|
+
* The check before `service stop` and `service restart` (SPEC §1): refuse with
|
|
42
|
+
* 75 unless `--force`, which prints the same message prefixed `Overriding:` and
|
|
43
|
+
* carries on. Returns null to go ahead.
|
|
44
|
+
*
|
|
45
|
+
* `home` is the data dir of the INSTALLED unit, not whatever `--data-dir` this
|
|
46
|
+
* invocation carries — the same rule `status` and `logs` follow (#824), because
|
|
47
|
+
* the instance being stopped is the one the unit describes.
|
|
48
|
+
*
|
|
49
|
+
* Two deliberate non-refusals. Nothing answering goes ahead: there is nothing
|
|
50
|
+
* to cut short, which is also the answer for a service that is already stopped.
|
|
51
|
+
* And an instance running a Paddock from before this release has no
|
|
52
|
+
* `runtime.json`, so it reads as not running and the stop proceeds exactly as
|
|
53
|
+
* it did before — an upgrade cannot make a stop start failing.
|
|
54
|
+
*/
|
|
55
|
+
export declare function guardServiceBusy(action: "stop" | "restart", home: string, opts: {
|
|
56
|
+
force?: boolean;
|
|
57
|
+
json?: boolean;
|
|
58
|
+
}): Promise<number | null>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paddock's bindings for the copied busy client (`cli/busy.ts`): the nouns its
|
|
3
|
+
* message uses, the keeper-chat trap, and the two entry points — `paddock busy`
|
|
4
|
+
* and the check `service stop` / `service restart` run first (CLI contract
|
|
5
|
+
* SPEC §1, §4).
|
|
6
|
+
*
|
|
7
|
+
* Kept separate from `cli/busy.ts` so that file stays a straight copy of
|
|
8
|
+
* `reference/busy-client.ts`, which is what wave C4 re-syncs. Everything in
|
|
9
|
+
* here is Paddock-specific by definition: the contract says each tool's kinds of
|
|
10
|
+
* work are its own.
|
|
11
|
+
*/
|
|
12
|
+
import { EXIT } from "./exit-codes.js";
|
|
13
|
+
import { createOutput } from "./output.js";
|
|
14
|
+
import { guardBusy, runBusyCommand, } from "./busy.js";
|
|
15
|
+
import { PADDOCK_NAME } from "../version.js";
|
|
16
|
+
/**
|
|
17
|
+
* How each count in `/api/busy` is said in a sentence (`busy.ts` on the server
|
|
18
|
+
* decides the keys).
|
|
19
|
+
*
|
|
20
|
+
* `background` is spelled out rather than left to the default pluraliser,
|
|
21
|
+
* because "3 backgrounds are running" is not English — and because "background
|
|
22
|
+
* task" is the thing a user recognises from the running-work bar in the UI,
|
|
23
|
+
* where they watched it start.
|
|
24
|
+
*/
|
|
25
|
+
export const PADDOCK_NOUNS = {
|
|
26
|
+
chat: ["chat turn", "chat turns"],
|
|
27
|
+
background: ["background task", "background tasks"],
|
|
28
|
+
job: ["scheduled job", "scheduled jobs"],
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* What to do about a busy answer — and for Paddock, the first thing to say is
|
|
32
|
+
* **where to run the command from**.
|
|
33
|
+
*
|
|
34
|
+
* This is the keeper-chat trap, and it is specific to Paddock and Managers
|
|
35
|
+
* among the six tools: Paddock's agents can drive Paddock. A keeper chat that
|
|
36
|
+
* runs `paddock service restart` or `paddock update` is itself one of the chat
|
|
37
|
+
* turns the answer just counted, so it will always see itself as busy — and
|
|
38
|
+
* `--force` from there does not override a mistaken reading, it kills the chat
|
|
39
|
+
* that typed it, mid-sentence, along with whatever it had not finished. The
|
|
40
|
+
* agent then has no way to report what happened, because the thing that would
|
|
41
|
+
* report it is the thing that died.
|
|
42
|
+
*
|
|
43
|
+
* So when `chat` is non-zero the advice is not "wait or use --force": it is
|
|
44
|
+
* "run this from a terminal". When the busy work is background tasks or
|
|
45
|
+
* scheduled jobs only, nobody is holding the keyboard inside the service and
|
|
46
|
+
* the ordinary advice applies.
|
|
47
|
+
*/
|
|
48
|
+
export function paddockBusyHint(result) {
|
|
49
|
+
if (result.state !== "busy")
|
|
50
|
+
return undefined;
|
|
51
|
+
const chats = result.answer.counts?.chat ?? 0;
|
|
52
|
+
if (chats > 0) {
|
|
53
|
+
// The lead sentence is short because `guardBusy` prints it on the same line
|
|
54
|
+
// as the message, after `paddock: `. The reasoning goes underneath, indented
|
|
55
|
+
// like every other continuation this CLI prints.
|
|
56
|
+
return ("Run it from a terminal, not from inside a Paddock chat.\n" +
|
|
57
|
+
" One of the turns counted above may be the chat asking, and --force from there\n" +
|
|
58
|
+
" stops the service running that chat — which then never gets to say so.\n" +
|
|
59
|
+
" Otherwise: wait for the turns to finish.");
|
|
60
|
+
}
|
|
61
|
+
return "Try again once it has finished, or use --force.";
|
|
62
|
+
}
|
|
63
|
+
/** The options both entry points share: name, nouns and hint are fixed for Paddock. */
|
|
64
|
+
function paddockBusyOptions(home, out) {
|
|
65
|
+
return { home, out, name: PADDOCK_NAME, nouns: PADDOCK_NOUNS, hint: paddockBusyHint };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `paddock busy [--json]` — exit 0 not busy, 75 busy, 3 nothing answering
|
|
69
|
+
* (SPEC §1, §2).
|
|
70
|
+
*
|
|
71
|
+
* The instance is found through `<data-dir>/runtime.json` and nowhere else, so
|
|
72
|
+
* this reports on the same instance `paddock serve` and `paddock config show`
|
|
73
|
+
* resolve — `resolveDataDir` in paddock.ts is the one rule all three use.
|
|
74
|
+
*/
|
|
75
|
+
export async function runBusy(home, json) {
|
|
76
|
+
const out = createOutput({ tool: "paddock", json });
|
|
77
|
+
return runBusyCommand(paddockBusyOptions(home, out));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The check before `service stop` and `service restart` (SPEC §1): refuse with
|
|
81
|
+
* 75 unless `--force`, which prints the same message prefixed `Overriding:` and
|
|
82
|
+
* carries on. Returns null to go ahead.
|
|
83
|
+
*
|
|
84
|
+
* `home` is the data dir of the INSTALLED unit, not whatever `--data-dir` this
|
|
85
|
+
* invocation carries — the same rule `status` and `logs` follow (#824), because
|
|
86
|
+
* the instance being stopped is the one the unit describes.
|
|
87
|
+
*
|
|
88
|
+
* Two deliberate non-refusals. Nothing answering goes ahead: there is nothing
|
|
89
|
+
* to cut short, which is also the answer for a service that is already stopped.
|
|
90
|
+
* And an instance running a Paddock from before this release has no
|
|
91
|
+
* `runtime.json`, so it reads as not running and the stop proceeds exactly as
|
|
92
|
+
* it did before — an upgrade cannot make a stop start failing.
|
|
93
|
+
*/
|
|
94
|
+
export async function guardServiceBusy(action, home, opts) {
|
|
95
|
+
// No `result()` call on this path, so `--json` only decides which stream the
|
|
96
|
+
// prose goes to — the one JSON document a `--json` stop may print belongs to
|
|
97
|
+
// the stop, not to the check it ran first.
|
|
98
|
+
const out = createOutput({ tool: "paddock", json: opts.json === true });
|
|
99
|
+
const refusal = await guardBusy({
|
|
100
|
+
...paddockBusyOptions(home, out),
|
|
101
|
+
...(opts.force === true ? { force: true } : {}),
|
|
102
|
+
});
|
|
103
|
+
if (refusal === null)
|
|
104
|
+
return null;
|
|
105
|
+
// Spell out the way past it, after the hint about where to run it from.
|
|
106
|
+
out.warn(`\n paddock service ${action} --force ${action} it anyway\n`);
|
|
107
|
+
return EXIT.busy;
|
|
108
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Output } from "./output.js";
|
|
2
|
+
/** What `/api/busy` answers (SPEC §4). Counts only, until §10's first question is settled. */
|
|
3
|
+
export interface BusyAnswer {
|
|
4
|
+
busy: boolean;
|
|
5
|
+
version?: string;
|
|
6
|
+
counts?: Record<string, number>;
|
|
7
|
+
}
|
|
8
|
+
export type BusyResult = {
|
|
9
|
+
state: "not-running";
|
|
10
|
+
} | {
|
|
11
|
+
state: "idle" | "busy";
|
|
12
|
+
url: string;
|
|
13
|
+
answer: BusyAnswer;
|
|
14
|
+
}
|
|
15
|
+
/** Something answered, but not with a busy answer: an older version without `/api/busy`, or another program. */
|
|
16
|
+
| {
|
|
17
|
+
state: "unknown";
|
|
18
|
+
url: string;
|
|
19
|
+
detail: string;
|
|
20
|
+
};
|
|
21
|
+
export interface FindOptions {
|
|
22
|
+
/** The tool's data home, holding `runtime.json`. */
|
|
23
|
+
home: string;
|
|
24
|
+
/** Ask this URL instead of the one in `runtime.json` (`--url`, or a port the user named). */
|
|
25
|
+
url?: string;
|
|
26
|
+
/** How long to wait for an answer. §4 asks for well under a second; this allows for a slow machine. */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
fetch?: typeof globalThis.fetch;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Asks the running instance whether it's busy. There's no fallback to a default port: an instance that's running
|
|
32
|
+
* wrote `runtime.json`, and guessing a port would ask whatever else is listening there.
|
|
33
|
+
*/
|
|
34
|
+
export declare function checkBusy(o: FindOptions): Promise<BusyResult>;
|
|
35
|
+
/** A kind's singular and plural, where adding an "s" is wrong: `{ batch: ['batch', 'batches'] }`. */
|
|
36
|
+
export type Nouns = Record<string, [singular: string, plural: string]>;
|
|
37
|
+
/** `2 chats and 1 job are running`, from the non-zero counts; '' if there are none. */
|
|
38
|
+
export declare function describeCounts(counts?: Record<string, number>, nouns?: Nouns): string;
|
|
39
|
+
/** The sentence for a person (SPEC §4): `Paddock is busy: 2 chats are running.` */
|
|
40
|
+
export declare function busyMessage(name: string, result: BusyResult, nouns?: Nouns): string;
|
|
41
|
+
/** 0 idle, 75 busy, 3 nothing answers, 1 an answer that isn't a busy answer. */
|
|
42
|
+
export declare function busyExitCode(result: BusyResult): number;
|
|
43
|
+
/** What `<tool> busy --json` prints: valid against `schemas/busy.schema.json`. */
|
|
44
|
+
export declare function busyJson(result: BusyResult): Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* LOCAL CHANGE (see the header): what to suggest when the answer is busy, as a string or as a function of the
|
|
47
|
+
* result. {@link resolveHint} is what both the command and the guard call, so the two can never print different
|
|
48
|
+
* advice about the same answer.
|
|
49
|
+
*/
|
|
50
|
+
export type BusyHint = string | ((result: BusyResult) => string | undefined);
|
|
51
|
+
export interface BusyCommandOptions extends FindOptions {
|
|
52
|
+
/** The product's name, for messages: `Local Review`. */
|
|
53
|
+
name: string;
|
|
54
|
+
out: Output;
|
|
55
|
+
nouns?: Nouns;
|
|
56
|
+
/** LOCAL CHANGE: the advice printed after a busy message — Paddock's keeper-chat trap. */
|
|
57
|
+
hint?: BusyHint;
|
|
58
|
+
}
|
|
59
|
+
/** `<tool> busy [--json]`. Returns the exit code; the caller sets `process.exitCode`. */
|
|
60
|
+
export declare function runBusyCommand(o: BusyCommandOptions): Promise<number>;
|
|
61
|
+
export interface GuardOptions extends BusyCommandOptions {
|
|
62
|
+
force?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The check before `service stop`, `service restart` and `update` (SPEC §4). Returns null to go ahead, or 75 after
|
|
66
|
+
* printing the refusal. With `--force` it prints the same message, prefixed `Overriding:`, and goes ahead.
|
|
67
|
+
* Nothing answering goes ahead: there's nothing to cut short.
|
|
68
|
+
*/
|
|
69
|
+
export declare function guardBusy(o: GuardOptions): Promise<number | null>;
|