@edspencer/paddock 0.82.0 → 0.83.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/cli/args.d.ts +29 -4
- package/packages/server/dist/cli/args.js +145 -7
- package/packages/server/dist/cli/busy-command.js +1 -1
- package/packages/server/dist/cli/busy.js +10 -12
- package/packages/server/dist/cli/doctor.d.ts +60 -0
- package/packages/server/dist/cli/doctor.js +580 -0
- package/packages/server/dist/cli/exit-codes.d.ts +21 -45
- package/packages/server/dist/cli/exit-codes.js +42 -45
- package/packages/server/dist/cli/output.js +5 -6
- package/packages/server/dist/cli/paddock.js +37 -12
- package/packages/server/dist/cli/service/index.d.ts +30 -2
- package/packages/server/dist/cli/service/index.js +163 -31
- package/packages/server/dist/cli/service/launchd.d.ts +31 -84
- package/packages/server/dist/cli/service/launchd.js +114 -304
- package/packages/server/dist/cli/service/logs.d.ts +9 -28
- package/packages/server/dist/cli/service/logs.js +33 -75
- package/packages/server/dist/cli/service/manager.d.ts +93 -0
- package/packages/server/dist/cli/service/manager.js +21 -0
- package/packages/server/dist/cli/service/path-env.d.ts +7 -18
- package/packages/server/dist/cli/service/path-env.js +30 -81
- package/packages/server/dist/cli/service/ready.d.ts +16 -23
- package/packages/server/dist/cli/service/ready.js +42 -0
- package/packages/server/dist/cli/service/spec.d.ts +4 -31
- package/packages/server/dist/cli/service/spec.js +9 -0
- package/packages/server/dist/cli/service/systemd.d.ts +27 -101
- package/packages/server/dist/cli/service/systemd.js +118 -261
- package/packages/server/dist/cli/update/health.d.ts +48 -0
- package/packages/server/dist/cli/update/health.js +64 -0
- package/packages/server/dist/cli/update/install-method.d.ts +29 -76
- package/packages/server/dist/cli/update/install-method.js +103 -178
- package/packages/server/dist/cli/update/lock.d.ts +7 -12
- package/packages/server/dist/cli/update/lock.js +28 -62
- package/packages/server/dist/cli/update/package-manager.d.ts +39 -74
- package/packages/server/dist/cli/update/package-manager.js +95 -160
- package/packages/server/dist/cli/update/registry.d.ts +17 -54
- package/packages/server/dist/cli/update/registry.js +46 -89
- package/packages/server/dist/cli/update/semver.d.ts +18 -41
- package/packages/server/dist/cli/update/semver.js +74 -124
- package/packages/server/dist/cli/update/update-check.d.ts +15 -26
- package/packages/server/dist/cli/update/update-check.js +30 -60
- package/packages/server/dist/cli/update/update.d.ts +57 -91
- package/packages/server/dist/cli/update/update.js +230 -431
- package/packages/server/dist/cli/update-command.js +3 -5
- package/packages/server/dist/runtime-file.d.ts +1 -1
- package/packages/server/dist/runtime-file.js +9 -13
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{DeleteProjectDialog-CphdGB3R.js → DeleteProjectDialog-B07MKgv4.js} +1 -1
- package/packages/web/dist/assets/DiscoverPage-D5C2fkeQ.js +2 -0
- package/packages/web/dist/assets/{DiscoverView-B_kcF4sm.js → DiscoverView-DpAAyBEf.js} +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-CvUK5DKW.js → InstanceConfigPage-YX0I_jVA.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-DvZ-j_Ao.js → MarkdownRenderer-C-CUuFhT.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-DayHvUH2.js → ProjectRedirect-qyXbTK56.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-CCw83gUi.js → ProjectView-CguNOv8k.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-B3Klg2H4.js → ProjectsGrid-CH2Lz2Hf.js} +1 -1
- package/packages/web/dist/assets/{RootHome-BxcDvpgI.js → RootHome-CUGUc8za.js} +1 -1
- package/packages/web/dist/assets/{Surfaces-7UlhFBSG.js → Surfaces-11or1q_3.js} +1 -1
- package/packages/web/dist/assets/{arc-DNkh5o5Q.js → arc-CQHeCzrk.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BrgN3oLF.js → architectureDiagram-3BPJPVTR-dGcuFxAa.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-D05XOJSk.js → blockDiagram-GPEHLZMM-CGc9h97S.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CKaMBhMP.js → c4Diagram-AAUBKEIU-BpX0sqhH.js} +1 -1
- package/packages/web/dist/assets/channel-BAeygx-e.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-8hgadCQc.js → chunk-2J33WTMH-uORGdO9D.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-CBfEC-pu.js → chunk-4BX2VUAB-C8zI2PFy.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-DV1eMgHk.js → chunk-55IACEB6-D22Iv6j6.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-Cfgl5fxY.js → chunk-727SXJPM-DdbHXAtS.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BXhqD295.js → chunk-AQP2D5EJ-BeKbob3a.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-TFGOPtek.js → chunk-FMBD7UC4-DtLvFU_y.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-CHFM665R.js → chunk-ND2GUHAM-BWJFo6B1.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-CqEf6MMT.js → chunk-QZHKN3VN-DeFkNg3S.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-Cq2-sBMa.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-Cq2-sBMa.js +2 -0
- package/packages/web/dist/assets/{core-DmEaKxBb.js → core-CTNNIHx5.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BnGI6jkr.js → cose-bilkent-S5V4N54A-BmtlyA-v.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-CKoPx61E.js → dagre-BM42HDAG-oS3rC9CI.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-mMmtlBl1.js → diagram-2AECGRRQ-BzIBqz6A.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Cdz9Mtnt.js → diagram-5GNKFQAL-BgIT2g8R.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-DQxM3wYj.js → diagram-KO2AKTUF-DACT41Re.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BWrinPsq.js → diagram-LMA3HP47-BstA3UkI.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-U6MYUve2.js → diagram-OG6HWLK6-Dp3Cn5n_.js} +1 -1
- package/packages/web/dist/assets/{documentTitle-DGjRXuTP.js → documentTitle-DRSQmA8q.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-xqMCsXxY.js → erDiagram-TEJ5UH35-wf8PyGx_.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-PMfy_czF.js → flowDiagram-I6XJVG4X-CJ4xx6EQ.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-whhYYE4N.js → ganttDiagram-6RSMTGT7-z9obPrPM.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-C51JW4tf.js → gitGraphDiagram-PVQCEYII-Cf4Cu7Tk.js} +1 -1
- package/packages/web/dist/assets/{index-CHOtvlnO.js → index-Bsv-raz2.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-SH8cr905.js → infoDiagram-5YYISTIA-BIfuiYxX.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-BjqaoU3U.js → ishikawaDiagram-YF4QCWOH-1pACxMTX.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-CBmdEVrd.js → journeyDiagram-JHISSGLW-XGbzfw-v.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-U8njjMLZ.js → kanban-definition-UN3LZRKU-Bvl-nMak.js} +1 -1
- package/packages/web/dist/assets/{linear-DF66GPs1.js → linear-uNToO4bv.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CouJz7Ph.js → mermaid.core-CrChvyj6.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-BYIf0oFt.js → mindmap-definition-RKZ34NQL-C6DaQ4Fm.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-Cm8rgg09.js → pieDiagram-4H26LBE5-CEh8lKyF.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-C3Il5kOH.js → quadrantDiagram-W4KKPZXB-z4zvICSM.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-D3PE2nTe.js → requirementDiagram-4Y6WPE33-BBltkjsj.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DdmvBM18.js → sankeyDiagram-5OEKKPKP-D0s95wQ4.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-B8M_GAIq.js → sequenceDiagram-3UESZ5HK-kQa65Nbt.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DZkXnK1_.js → stateDiagram-AJRCARHV-BqVIoyED.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-B6OXxEra.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-IHXq7rqi.js → timeline-definition-PNZ67QCA-Bo5L62WI.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-Dga7tLtT.js → vennDiagram-CIIHVFJN-CN9rH4lT.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-CmcwnEJ-.js → wardley-L42UT6IY-BKhnD_W_.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-zHuSLrD4.js → wardleyDiagram-YWT4CUSO-XHTbZzXj.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-D93MU_ic.js → xychartDiagram-2RQKCTM6--t9XJLbR.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/server/dist/cli/service/backend.d.ts +0 -115
- package/packages/server/dist/cli/service/backend.js +0 -19
- package/packages/web/dist/assets/DiscoverPage-C4OPLNO8.js +0 -2
- package/packages/web/dist/assets/channel-JrEedxB8.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CHJiQp-W.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CHJiQp-W.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CjCdT9Ls.js +0 -2
|
@@ -1,39 +1,20 @@
|
|
|
1
|
-
/** How many lines `logs` prints when nobody says
|
|
1
|
+
/** How many lines `logs` prints when nobody says (SPEC §1). */
|
|
2
2
|
export declare const DEFAULT_LOG_LINES = 50;
|
|
3
|
-
/**
|
|
4
|
-
* The last `lines` lines of `file`.
|
|
5
|
-
*
|
|
6
|
-
* Only the last {@link TAIL_WINDOW} bytes are read. A service log is append-only
|
|
7
|
-
* and can reach tens of megabytes between rotations, and reading all of it to
|
|
8
|
-
* print fifty lines would make `logs` slower than the thing it is reporting on.
|
|
9
|
-
* A window that happens to start mid-line drops that partial first line rather
|
|
10
|
-
* than printing half of one.
|
|
11
|
-
*/
|
|
3
|
+
/** The last `lines` lines of `file`. A window that starts mid-line drops that fragment. */
|
|
12
4
|
export declare function tailFile(file: string, lines: number): string;
|
|
13
5
|
export interface FollowOptions {
|
|
14
|
-
/** Where to start
|
|
6
|
+
/** Where to start: the file's size after the tail. */
|
|
15
7
|
from: number;
|
|
16
|
-
/** Injected for tests. */
|
|
17
8
|
intervalMs?: number;
|
|
18
|
-
/**
|
|
9
|
+
/** Return false to stop. */
|
|
19
10
|
keepGoing?: () => boolean;
|
|
20
11
|
write?: (s: string) => void;
|
|
21
12
|
}
|
|
22
13
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Polling rather than `fs.watch`: the writer here is the supervisor's own file
|
|
27
|
-
* descriptor, and `fs.watch` on an appended-to file is unreliable across
|
|
28
|
-
* platforms (no events at all on some network filesystems, and a stream of
|
|
29
|
-
* them on others). A 400 ms poll of the size is both portable and cheap.
|
|
30
|
-
*
|
|
31
|
-
* Rotation is handled by the one case that can actually happen: the server
|
|
32
|
-
* rotates by COPY-AND-TRUNCATE (see `service-log.ts`), so the inode is stable
|
|
33
|
-
* and the size goes DOWN. Seeing that, this resets to the new start rather than
|
|
34
|
-
* reading past the end forever, which is what a naive follower does — it goes
|
|
35
|
-
* quiet after the first rotation and never recovers.
|
|
14
|
+
* Prints what's appended to `file` until stopped. Polls the size (`fs.watch` on an appended file is unreliable across
|
|
15
|
+
* platforms). The tool rotates by copy-and-truncate, so the size goes down; seeing that, it starts again from 0
|
|
16
|
+
* rather than reading past the end for ever. A file that vanishes is waited for.
|
|
36
17
|
*/
|
|
37
|
-
export declare function followFile(file: string,
|
|
38
|
-
/** The file's size, or 0 if it
|
|
18
|
+
export declare function followFile(file: string, o: FollowOptions): Promise<void>;
|
|
19
|
+
/** The file's size, or 0 if it isn't there: where a follow starts. */
|
|
39
20
|
export declare function sizeOf(file: string): number;
|
|
@@ -1,102 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* answers, neither of which Windows could ever have.
|
|
8
|
-
*
|
|
9
|
-
* Reading the tail of a file is deliberately done by hand rather than by
|
|
10
|
-
* spawning `tail`: `tail -f` is not on every box this runs on (and is `Get-
|
|
11
|
-
* Content -Wait` on the platform the contract requires us not to rule out),
|
|
12
|
-
* and shelling out would make the output depend on which `tail` won a PATH
|
|
13
|
-
* lookup.
|
|
14
|
-
*/
|
|
15
|
-
import fs from "node:fs";
|
|
16
|
-
/** How many lines `logs` prints when nobody says otherwise (SPEC §1). */
|
|
1
|
+
// From edspencer/cli-contract reference/service/logs.ts @ 0.2; local changes: `.ts` import specifiers → `.js`.
|
|
2
|
+
// `<tool> service logs [-f] [-n <lines>]` (SPEC §1, §7): one Node implementation on every OS, because every unit
|
|
3
|
+
// writes to `<home>/logs/service.log`. No `tail`: `tail -f` isn't on Windows, and which `tail` won a PATH lookup would
|
|
4
|
+
// change the output. Node built-ins only.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
/** How many lines `logs` prints when nobody says (SPEC §1). */
|
|
17
7
|
export const DEFAULT_LOG_LINES = 50;
|
|
18
|
-
/**
|
|
8
|
+
/** Only the end of the file is read for a tail: a log can reach tens of megabytes between rotations. */
|
|
19
9
|
const TAIL_WINDOW = 256 * 1024;
|
|
20
|
-
/** The
|
|
21
|
-
const FOLLOW_INTERVAL_MS = 400;
|
|
22
|
-
/**
|
|
23
|
-
* The last `lines` lines of `file`.
|
|
24
|
-
*
|
|
25
|
-
* Only the last {@link TAIL_WINDOW} bytes are read. A service log is append-only
|
|
26
|
-
* and can reach tens of megabytes between rotations, and reading all of it to
|
|
27
|
-
* print fifty lines would make `logs` slower than the thing it is reporting on.
|
|
28
|
-
* A window that happens to start mid-line drops that partial first line rather
|
|
29
|
-
* than printing half of one.
|
|
30
|
-
*/
|
|
10
|
+
/** The last `lines` lines of `file`. A window that starts mid-line drops that fragment. */
|
|
31
11
|
export function tailFile(file, lines) {
|
|
32
|
-
const fd = fs.openSync(file,
|
|
12
|
+
const fd = fs.openSync(file, 'r');
|
|
33
13
|
try {
|
|
34
14
|
const size = fs.fstatSync(fd).size;
|
|
35
15
|
const start = Math.max(0, size - TAIL_WINDOW);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
text = nl === -1 ? "" : text.slice(nl + 1);
|
|
46
|
-
}
|
|
47
|
-
const all = text.split("\n");
|
|
48
|
-
// A trailing newline produces a final empty element that is not a line.
|
|
49
|
-
if (all[all.length - 1] === "")
|
|
16
|
+
if (size === start)
|
|
17
|
+
return '';
|
|
18
|
+
const buf = Buffer.alloc(size - start);
|
|
19
|
+
fs.readSync(fd, buf, 0, buf.length, start);
|
|
20
|
+
let text = buf.toString('utf8');
|
|
21
|
+
if (start > 0)
|
|
22
|
+
text = text.includes('\n') ? text.slice(text.indexOf('\n') + 1) : '';
|
|
23
|
+
const all = text.split('\n');
|
|
24
|
+
if (all.at(-1) === '')
|
|
50
25
|
all.pop();
|
|
51
|
-
return all.slice(-lines).join(
|
|
26
|
+
return all.slice(-lines).join('\n');
|
|
52
27
|
}
|
|
53
28
|
finally {
|
|
54
29
|
fs.closeSync(fd);
|
|
55
30
|
}
|
|
56
31
|
}
|
|
57
32
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* Polling rather than `fs.watch`: the writer here is the supervisor's own file
|
|
62
|
-
* descriptor, and `fs.watch` on an appended-to file is unreliable across
|
|
63
|
-
* platforms (no events at all on some network filesystems, and a stream of
|
|
64
|
-
* them on others). A 400 ms poll of the size is both portable and cheap.
|
|
65
|
-
*
|
|
66
|
-
* Rotation is handled by the one case that can actually happen: the server
|
|
67
|
-
* rotates by COPY-AND-TRUNCATE (see `service-log.ts`), so the inode is stable
|
|
68
|
-
* and the size goes DOWN. Seeing that, this resets to the new start rather than
|
|
69
|
-
* reading past the end forever, which is what a naive follower does — it goes
|
|
70
|
-
* quiet after the first rotation and never recovers.
|
|
33
|
+
* Prints what's appended to `file` until stopped. Polls the size (`fs.watch` on an appended file is unreliable across
|
|
34
|
+
* platforms). The tool rotates by copy-and-truncate, so the size goes down; seeing that, it starts again from 0
|
|
35
|
+
* rather than reading past the end for ever. A file that vanishes is waited for.
|
|
71
36
|
*/
|
|
72
|
-
export async function followFile(file,
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
while (keepGoing()) {
|
|
78
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
37
|
+
export async function followFile(file, o) {
|
|
38
|
+
const write = o.write ?? ((s) => process.stdout.write(s));
|
|
39
|
+
let offset = o.from;
|
|
40
|
+
while (o.keepGoing?.() ?? true) {
|
|
41
|
+
await new Promise((r) => setTimeout(r, o.intervalMs ?? 400));
|
|
79
42
|
let size;
|
|
80
43
|
try {
|
|
81
44
|
size = fs.statSync(file).size;
|
|
82
45
|
}
|
|
83
46
|
catch {
|
|
84
|
-
// The file can vanish: an uninstall, or a rotation that renames. Wait for
|
|
85
|
-
// it to come back rather than giving up — `logs -f` is left running.
|
|
86
47
|
continue;
|
|
87
48
|
}
|
|
88
|
-
if (size < offset)
|
|
89
|
-
// Truncated: the rotation above, or a hand-run `: > service.log`.
|
|
49
|
+
if (size < offset)
|
|
90
50
|
offset = 0;
|
|
91
|
-
}
|
|
92
51
|
if (size === offset)
|
|
93
52
|
continue;
|
|
94
|
-
const fd = fs.openSync(file,
|
|
53
|
+
const fd = fs.openSync(file, 'r');
|
|
95
54
|
try {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
write(buf.toString("utf8"));
|
|
55
|
+
const buf = Buffer.alloc(size - offset);
|
|
56
|
+
fs.readSync(fd, buf, 0, buf.length, offset);
|
|
57
|
+
write(buf.toString('utf8'));
|
|
100
58
|
offset = size;
|
|
101
59
|
}
|
|
102
60
|
finally {
|
|
@@ -104,7 +62,7 @@ export async function followFile(file, opts) {
|
|
|
104
62
|
}
|
|
105
63
|
}
|
|
106
64
|
}
|
|
107
|
-
/** The file's size, or 0 if it
|
|
65
|
+
/** The file's size, or 0 if it isn't there: where a follow starts. */
|
|
108
66
|
export function sizeOf(file) {
|
|
109
67
|
try {
|
|
110
68
|
return fs.statSync(file).size;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Who the service belongs to. */
|
|
2
|
+
export interface ServiceIdentity {
|
|
3
|
+
/** `Paddock`: for messages and the unit's header. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** `paddock`: the bin, for the commands messages print. */
|
|
6
|
+
tool: string;
|
|
7
|
+
/** The launchd label: `net.edspencer.paddock`. A person may type it (`launchctl print gui/501/<label>`). */
|
|
8
|
+
label: string;
|
|
9
|
+
/** The systemd user unit: `paddock.service`. */
|
|
10
|
+
unit: string;
|
|
11
|
+
/** systemd's `Description=`. */
|
|
12
|
+
description: string;
|
|
13
|
+
/** systemd's `Documentation=`, if there's a page. */
|
|
14
|
+
documentation?: string;
|
|
15
|
+
}
|
|
16
|
+
/** What the unit runs, already resolved: every path absolute and real. */
|
|
17
|
+
export interface ServiceSpec {
|
|
18
|
+
/** `realpath(process.execPath)`. */
|
|
19
|
+
nodePath: string;
|
|
20
|
+
/** `realpath(<package>/…/cli.js)`, never the bin shim: its `#!/usr/bin/env node` needs a `node` on a PATH a
|
|
21
|
+
* service manager doesn't have under a version manager. */
|
|
22
|
+
scriptPath: string;
|
|
23
|
+
/** After the script: `serve` and the options. */
|
|
24
|
+
args: string[];
|
|
25
|
+
/** A folder the service owns: `<home>/service`. */
|
|
26
|
+
workingDirectory: string;
|
|
27
|
+
/** `<home>/logs/service.log`: both streams, appended, on every OS. */
|
|
28
|
+
logPath: string;
|
|
29
|
+
/** The service's PATH (path-env.ts). */
|
|
30
|
+
pathEnv: string;
|
|
31
|
+
/** More environment, if the tool needs any. Never a credential: a unit file is readable. */
|
|
32
|
+
env?: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
/** Linux only: whether the user lingers (SPEC §7). Undefined `enabled` means the check couldn't answer. */
|
|
35
|
+
export interface LingerState {
|
|
36
|
+
/** False where there's no such thing (macOS, Windows). */
|
|
37
|
+
applies: boolean;
|
|
38
|
+
enabled?: boolean;
|
|
39
|
+
user?: string;
|
|
40
|
+
}
|
|
41
|
+
/** What the service manager and the unit file say. */
|
|
42
|
+
export interface ServiceState {
|
|
43
|
+
/** A unit file is installed. */
|
|
44
|
+
registered: boolean;
|
|
45
|
+
/** The service manager says the process is up. */
|
|
46
|
+
running: boolean;
|
|
47
|
+
pid?: string;
|
|
48
|
+
/** The tool's arguments the unit records, with Node and the script taken off. */
|
|
49
|
+
argv: string[];
|
|
50
|
+
/** The Node and script the unit names, so `status` can say when one has gone (nvm, fnm and volta remove them). */
|
|
51
|
+
nodePath?: string;
|
|
52
|
+
cliPath?: string;
|
|
53
|
+
/** The log the unit names. Read back, so `logs` reads the file the service writes even if an older unit named another. */
|
|
54
|
+
logPath?: string;
|
|
55
|
+
/** The unit restarts the service after a clean exit (launchd `KeepAlive`, systemd `Restart=always`). True when nothing's registered. */
|
|
56
|
+
keepsAlive: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface ServiceManager {
|
|
59
|
+
platform: 'darwin' | 'linux' | 'win32';
|
|
60
|
+
/** Where the unit file goes. */
|
|
61
|
+
unitPath: string;
|
|
62
|
+
/** The launchd label or systemd unit: the string a person would type. */
|
|
63
|
+
label: string;
|
|
64
|
+
/** For people: `a launchd LaunchAgent`, `a systemd --user service`. */
|
|
65
|
+
kind: string;
|
|
66
|
+
/** Writes the unit, enables it and starts it, replacing a running one. */
|
|
67
|
+
install(spec: ServiceSpec): void;
|
|
68
|
+
/** Stops it, disables it and removes the unit; true if there was one. Keeps the data and the logs. */
|
|
69
|
+
uninstall(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* `start`, `stop` and `restart` throw with the manager's own words. `stop` doesn't disable: the service comes back
|
|
72
|
+
* at the next login or boot (SPEC §1). `restart` re-reads the unit.
|
|
73
|
+
*/
|
|
74
|
+
start(): void;
|
|
75
|
+
stop(): void;
|
|
76
|
+
restart(): void;
|
|
77
|
+
status(): ServiceState;
|
|
78
|
+
linger(): LingerState;
|
|
79
|
+
}
|
|
80
|
+
export interface RunResult {
|
|
81
|
+
/** null when it couldn't be run or was killed. */
|
|
82
|
+
status: number | null;
|
|
83
|
+
stdout: string;
|
|
84
|
+
stderr: string;
|
|
85
|
+
}
|
|
86
|
+
export type Runner = (cmd: string, args: string[]) => RunResult;
|
|
87
|
+
export declare const spawnRunner: Runner;
|
|
88
|
+
/** Blocks for `ms`: the Runner is synchronous, so the waits between its calls are too. */
|
|
89
|
+
export declare function sleepSync(ms: number): void;
|
|
90
|
+
/** No service manager here yet: Windows (P3), or anything else. */
|
|
91
|
+
export declare class UnsupportedPlatformError extends Error {
|
|
92
|
+
name: string;
|
|
93
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// From edspencer/cli-contract reference/service/manager.ts @ 0.2; local changes: `.ts` import specifiers → `.js`.
|
|
2
|
+
// The seam between what a tool's service is and how a service manager spells it (SPEC §7). launchd.ts and
|
|
3
|
+
// systemd.ts implement `ServiceManager`; Task Scheduler (Windows, P3) is the third, later, behind the same interface.
|
|
4
|
+
// Every command goes through a `Runner`, so the lifecycle is tested with a fake one, on any OS. Node built-ins only.
|
|
5
|
+
//
|
|
6
|
+
// The Runner is synchronous on purpose: `update`'s service hooks are, and a sleep between launchd's bootout and
|
|
7
|
+
// bootstrap (`sleepSync`) is all the waiting there is.
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
export const spawnRunner = (cmd, args) => {
|
|
10
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 60_000, windowsHide: true });
|
|
11
|
+
return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
|
|
12
|
+
};
|
|
13
|
+
/** Blocks for `ms`: the Runner is synchronous, so the waits between its calls are too. */
|
|
14
|
+
export function sleepSync(ms) {
|
|
15
|
+
if (ms > 0)
|
|
16
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
17
|
+
}
|
|
18
|
+
/** No service manager here yet: Windows (P3), or anything else. */
|
|
19
|
+
export class UnsupportedPlatformError extends Error {
|
|
20
|
+
name = 'UnsupportedPlatformError';
|
|
21
|
+
}
|
|
@@ -1,27 +1,16 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Entries that belong to one shell or one npm run. */
|
|
2
2
|
export declare const PER_SHELL_PATTERNS: readonly RegExp[];
|
|
3
|
-
/** Where the usual tools live, after the inherited PATH. Only
|
|
3
|
+
/** Where the usual tools live, after the inherited PATH. Only the ones that exist are kept. */
|
|
4
4
|
export declare const SYSTEM_DIRS: readonly string[];
|
|
5
5
|
export interface ServicePathOptions {
|
|
6
|
-
/** Injected for tests:
|
|
6
|
+
/** Injected for tests: is this an existing folder? */
|
|
7
7
|
exists?: (p: string) => boolean;
|
|
8
|
-
/** Injected for tests: the system
|
|
8
|
+
/** Injected for tests: the system folders to add. */
|
|
9
9
|
systemDirs?: readonly string[];
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* The job's PATH: Node's bin directory, then the usable part of `inherited`,
|
|
13
|
-
* then the system directories. De-duplicated, order preserved.
|
|
14
|
-
*/
|
|
11
|
+
/** Node's bin folder, then what's worth keeping of `inherited`, then the system folders; no duplicates. */
|
|
15
12
|
export declare function servicePath(nodeBin: string, inherited: string | undefined, opts?: ServicePathOptions): string;
|
|
16
|
-
/**
|
|
13
|
+
/** The first executable called `name` on `pathVar`. */
|
|
17
14
|
export declare function which(name: string, pathVar: string, isExec?: (p: string) => boolean): string | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* Of `paths`, those that are no longer on disk.
|
|
20
|
-
*
|
|
21
|
-
* This is the check behind `status`'s and (from wave G) `doctor`'s "the Node it
|
|
22
|
-
* runs has gone" warning. It happens under nvm, fnm and volta the moment a Node
|
|
23
|
-
* version is uninstalled: the unit still names an absolute interpreter path,
|
|
24
|
-
* the supervisor still dutifully tries to exec it, and the failure arrives as
|
|
25
|
-
* Paddock being absent with nothing in its own logs — because it never ran.
|
|
26
|
-
*/
|
|
15
|
+
/** Those of `paths` that aren't on disk any more: `status` and `doctor` warn about a unit's Node that has gone. */
|
|
27
16
|
export declare function missingPaths(paths: readonly (string | undefined)[], exists?: (p: string) => boolean): string[];
|
|
@@ -1,52 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* ## Why a unit cannot just inherit the installing shell's PATH
|
|
12
|
-
*
|
|
13
|
-
* Paddock used to record `process.env.PATH` verbatim. That is the PATH of one
|
|
14
|
-
* terminal at one moment, and several of its entries are *per-session*:
|
|
15
|
-
*
|
|
16
|
-
* - **fnm** puts a symlink per terminal under `fnm_multishells/<pid>_<ts>/bin`.
|
|
17
|
-
* The directory is deleted when that shell exits — and fnm never cleans up
|
|
18
|
-
* the stale ones, so a unit installed from an fnm shell names a `node`
|
|
19
|
-
* directory that stops existing the moment the tab closes.
|
|
20
|
-
* - **npx** adds its own per-invocation cache; `npm run` adds `node-gyp-bin`
|
|
21
|
-
* and the package's `node_modules/.bin`. A long-lived background job has no
|
|
22
|
-
* business looking in any of them for `git`.
|
|
23
|
-
*
|
|
24
|
-
* So the job's PATH is rebuilt: Node's own bin directory first (so the `node`
|
|
25
|
-
* the unit runs can resolve its neighbours), then whatever of the installing
|
|
26
|
-
* shell's PATH is worth keeping, then the system directories. Entries that are
|
|
27
|
-
* relative, per-shell, or simply not there are dropped — a PATH naming
|
|
28
|
-
* directories that do not exist is how `git` goes missing at some later login,
|
|
29
|
-
* silently, with the symptom appearing in an agent's turn rather than at
|
|
30
|
-
* install time.
|
|
31
|
-
*/
|
|
32
|
-
import fs from "node:fs";
|
|
33
|
-
import path from "node:path";
|
|
34
|
-
/** PATH entries that belong to one shell session or one npm invocation. */
|
|
1
|
+
// From edspencer/cli-contract reference/service/path-env.ts @ 0.2; local changes: `.ts` import specifiers → `.js`.
|
|
2
|
+
// The PATH a unit gives the service (SPEC §7): Node's own bin folder first, then the installing shell's PATH without
|
|
3
|
+
// its per-shell entries, then the system folders. Only folders that exist are kept. Node built-ins only.
|
|
4
|
+
//
|
|
5
|
+
// A unit can't just record the shell's PATH: fnm puts a folder per terminal (`fnm_multishells/<pid>_<ts>/bin`) on it
|
|
6
|
+
// that goes when the terminal closes, and npx and `npm run` add their own. A unit naming those loses `node` or `git`
|
|
7
|
+
// at some later login, with the symptom showing up far from the cause.
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
/** Entries that belong to one shell or one npm run. */
|
|
35
11
|
export const PER_SHELL_PATTERNS = [
|
|
36
|
-
/[/\\]fnm_multishells[/\\]/, // fnm: a
|
|
37
|
-
/[/\\]_npx[/\\]/, // npx's
|
|
38
|
-
/[/\\]node-gyp-bin$/, // added by `npm run`
|
|
39
|
-
/[/\\]node_modules[/\\]\.bin$/, //
|
|
40
|
-
];
|
|
41
|
-
/** Where the usual tools live, after the inherited PATH. Only existing ones are kept. */
|
|
42
|
-
export const SYSTEM_DIRS = [
|
|
43
|
-
"/opt/homebrew/bin",
|
|
44
|
-
"/usr/local/bin",
|
|
45
|
-
"/usr/bin",
|
|
46
|
-
"/bin",
|
|
47
|
-
"/usr/sbin",
|
|
48
|
-
"/sbin",
|
|
12
|
+
/[/\\]fnm_multishells[/\\]/, // fnm: a folder per terminal, never cleaned up
|
|
13
|
+
/[/\\]_npx[/\\]/, // npx's cache
|
|
14
|
+
/[/\\]node-gyp-bin$/, // added by `npm run`
|
|
15
|
+
/[/\\]node_modules[/\\]\.bin$/, // a package's own tools, added by `npm run` and npx
|
|
49
16
|
];
|
|
17
|
+
/** Where the usual tools live, after the inherited PATH. Only the ones that exist are kept. */
|
|
18
|
+
export const SYSTEM_DIRS = ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'];
|
|
50
19
|
function isDir(p) {
|
|
51
20
|
try {
|
|
52
21
|
return fs.statSync(p).isDirectory();
|
|
@@ -66,55 +35,35 @@ function isExecutableFile(p) {
|
|
|
66
35
|
return false;
|
|
67
36
|
}
|
|
68
37
|
}
|
|
69
|
-
/**
|
|
70
|
-
function keep(dir, exists) {
|
|
71
|
-
if (dir === "" || !path.isAbsolute(dir))
|
|
72
|
-
return false;
|
|
73
|
-
if (PER_SHELL_PATTERNS.some((re) => re.test(dir) || re.test(`${dir}${path.sep}`)))
|
|
74
|
-
return false;
|
|
75
|
-
return exists(dir);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* The job's PATH: Node's bin directory, then the usable part of `inherited`,
|
|
79
|
-
* then the system directories. De-duplicated, order preserved.
|
|
80
|
-
*/
|
|
38
|
+
/** Node's bin folder, then what's worth keeping of `inherited`, then the system folders; no duplicates. */
|
|
81
39
|
export function servicePath(nodeBin, inherited, opts = {}) {
|
|
82
40
|
const exists = opts.exists ?? isDir;
|
|
83
|
-
const system = opts.systemDirs ?? SYSTEM_DIRS;
|
|
84
41
|
const out = [];
|
|
85
42
|
const add = (d) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
43
|
+
const clean = d.length > 1 ? d.replace(/[/\\]+$/, '') : d;
|
|
44
|
+
if (clean === '' || !path.isAbsolute(clean) || out.includes(clean))
|
|
45
|
+
return;
|
|
46
|
+
if (PER_SHELL_PATTERNS.some((re) => re.test(clean) || re.test(`${clean}${path.sep}`)))
|
|
47
|
+
return;
|
|
48
|
+
if (exists(clean))
|
|
89
49
|
out.push(clean);
|
|
90
50
|
};
|
|
91
51
|
add(nodeBin);
|
|
92
|
-
for (const d of (inherited ??
|
|
52
|
+
for (const d of (inherited ?? '').split(path.delimiter))
|
|
93
53
|
add(d);
|
|
94
|
-
for (const d of
|
|
54
|
+
for (const d of opts.systemDirs ?? SYSTEM_DIRS)
|
|
95
55
|
add(d);
|
|
96
56
|
return out.join(path.delimiter);
|
|
97
57
|
}
|
|
98
|
-
/**
|
|
58
|
+
/** The first executable called `name` on `pathVar`. */
|
|
99
59
|
export function which(name, pathVar, isExec = isExecutableFile) {
|
|
100
60
|
for (const dir of pathVar.split(path.delimiter)) {
|
|
101
|
-
if (dir
|
|
102
|
-
|
|
103
|
-
const p = path.join(dir, name);
|
|
104
|
-
if (isExec(p))
|
|
105
|
-
return p;
|
|
61
|
+
if (dir !== '' && isExec(path.join(dir, name)))
|
|
62
|
+
return path.join(dir, name);
|
|
106
63
|
}
|
|
107
64
|
return undefined;
|
|
108
65
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Of `paths`, those that are no longer on disk.
|
|
111
|
-
*
|
|
112
|
-
* This is the check behind `status`'s and (from wave G) `doctor`'s "the Node it
|
|
113
|
-
* runs has gone" warning. It happens under nvm, fnm and volta the moment a Node
|
|
114
|
-
* version is uninstalled: the unit still names an absolute interpreter path,
|
|
115
|
-
* the supervisor still dutifully tries to exec it, and the failure arrives as
|
|
116
|
-
* Paddock being absent with nothing in its own logs — because it never ran.
|
|
117
|
-
*/
|
|
66
|
+
/** Those of `paths` that aren't on disk any more: `status` and `doctor` warn about a unit's Node that has gone. */
|
|
118
67
|
export function missingPaths(paths, exists = (p) => fs.existsSync(p)) {
|
|
119
|
-
return paths.filter((p) => p !== undefined && p !==
|
|
68
|
+
return paths.filter((p) => p !== undefined && p !== '' && !exists(p));
|
|
120
69
|
}
|
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* "Is it actually up?" — the bounded readiness poll the service CLI uses before
|
|
3
|
-
* it claims success (#873, #861).
|
|
4
|
-
*
|
|
5
|
-
* Every service command that starts something has the same failure mode without
|
|
6
|
-
* this: the supervisor accepts the request and returns, so the CLI prints a URL
|
|
7
|
-
* and a cheerful past tense while the server is still binding — or is already
|
|
8
|
-
* crash-looping on a port clash, which `launchctl` and `systemctl` will both
|
|
9
|
-
* happily report as "running" for the ten seconds between restarts. A message
|
|
10
|
-
* that can sit over a crash-loop and still say "running at <url>" is not a
|
|
11
|
-
* status line, it is a guess.
|
|
12
|
-
*
|
|
13
|
-
* So the check is deliberately end-to-end: an HTTP request to the URL a human
|
|
14
|
-
* would open, not a pid lookup. `/api/health` is the probe because it is in
|
|
15
|
-
* `ALWAYS_PUBLIC_PATHS` (`auth.ts`) — it answers whatever `PADDOCK_AUTH_MODE`
|
|
16
|
-
* the installed unit runs with, so this does not quietly stop working for the
|
|
17
|
-
* people who configured a credential.
|
|
18
|
-
*
|
|
19
|
-
* The result is a tri-state on purpose. "I could not confirm it" is a real and
|
|
20
|
-
* common answer — an unusual bind host, a slow first boot — and it is not the
|
|
21
|
-
* same claim as "it failed". Callers print it as a caveat, not an error, and
|
|
22
|
-
* point at the logs.
|
|
23
|
-
*/
|
|
24
1
|
/** Where the poll got to when it stopped. */
|
|
25
2
|
export type Readiness = "ready" | "timeout";
|
|
26
3
|
/** What `/api/health` said, when it said anything (SPEC §8). */
|
|
@@ -111,3 +88,19 @@ export type VersionVerdict = {
|
|
|
111
88
|
export declare function waitForVersion(opts: ReadyOptions & {
|
|
112
89
|
version: string;
|
|
113
90
|
}): Promise<VersionVerdict>;
|
|
91
|
+
/** Is anything accepting connections at host:port? A second at most. */
|
|
92
|
+
export declare function isListening(host: string, port: number): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Is what answers at `url` a Paddock, and which version? Undefined when nothing
|
|
95
|
+
* answers `/api/health` there.
|
|
96
|
+
*
|
|
97
|
+
* `/api/health`'s `{ ok, version }` is the CLI contract's, so every one of the
|
|
98
|
+
* six tools answers it alike; `/api/busy`'s `counts.chat` and
|
|
99
|
+
* `counts.background` are Paddock's own. A Paddock older than 0.81 has no
|
|
100
|
+
* `/api/busy`, and reads as not-Paddock here — callers that can tell it apart
|
|
101
|
+
* some other way (an installed unit on that port) should.
|
|
102
|
+
*/
|
|
103
|
+
export declare function identifyPaddock(url: string, fetchImpl?: typeof fetch): Promise<{
|
|
104
|
+
paddock: boolean;
|
|
105
|
+
version?: string;
|
|
106
|
+
} | undefined>;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* same claim as "it failed". Callers print it as a caveat, not an error, and
|
|
22
22
|
* point at the logs.
|
|
23
23
|
*/
|
|
24
|
+
import net from "node:net";
|
|
24
25
|
/**
|
|
25
26
|
* Ask `<url>/api/health` once, and say what it answered.
|
|
26
27
|
*
|
|
@@ -134,3 +135,44 @@ export async function waitForVersion(opts) {
|
|
|
134
135
|
await sleep(intervalMs);
|
|
135
136
|
}
|
|
136
137
|
}
|
|
138
|
+
/** Is anything accepting connections at host:port? A second at most. */
|
|
139
|
+
export function isListening(host, port) {
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
const s = net.connect({ host, port });
|
|
142
|
+
s.setTimeout(1000);
|
|
143
|
+
s.once("connect", () => {
|
|
144
|
+
s.destroy();
|
|
145
|
+
resolve(true);
|
|
146
|
+
});
|
|
147
|
+
s.once("timeout", () => {
|
|
148
|
+
s.destroy();
|
|
149
|
+
resolve(false);
|
|
150
|
+
});
|
|
151
|
+
s.once("error", () => resolve(false));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Is what answers at `url` a Paddock, and which version? Undefined when nothing
|
|
156
|
+
* answers `/api/health` there.
|
|
157
|
+
*
|
|
158
|
+
* `/api/health`'s `{ ok, version }` is the CLI contract's, so every one of the
|
|
159
|
+
* six tools answers it alike; `/api/busy`'s `counts.chat` and
|
|
160
|
+
* `counts.background` are Paddock's own. A Paddock older than 0.81 has no
|
|
161
|
+
* `/api/busy`, and reads as not-Paddock here — callers that can tell it apart
|
|
162
|
+
* some other way (an installed unit on that port) should.
|
|
163
|
+
*/
|
|
164
|
+
export async function identifyPaddock(url, fetchImpl = fetch) {
|
|
165
|
+
const health = await fetchHealth(url, { fetchImpl });
|
|
166
|
+
if (health === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
const version = health.version !== undefined ? { version: health.version } : {};
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetchImpl(`${url}/api/busy`, { signal: AbortSignal.timeout(2_000) });
|
|
171
|
+
const body = (await res.json());
|
|
172
|
+
const counts = body.counts ?? {};
|
|
173
|
+
return { paddock: health.ok && typeof counts.chat === "number" && "background" in counts, ...version };
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return { paddock: false, ...version };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ServiceIdentity, ServiceSpec } from "./manager.js";
|
|
1
2
|
/**
|
|
2
3
|
* Reverse-DNS on macOS, plain on Linux. Both are stable identifiers a user may
|
|
3
4
|
* end up typing (`launchctl print gui/501/net.edspencer.paddock`,
|
|
@@ -6,41 +7,13 @@
|
|
|
6
7
|
*/
|
|
7
8
|
export declare const LAUNCHD_LABEL = "net.edspencer.paddock";
|
|
8
9
|
export declare const SYSTEMD_UNIT = "paddock.service";
|
|
10
|
+
/** Who the reference's launchd and systemd writers are writing for. */
|
|
11
|
+
export declare const PADDOCK_SERVICE: ServiceIdentity;
|
|
9
12
|
/** Default port, matching the server's own (#741). Kept here so the generated
|
|
10
13
|
* unit can name a port even when the user passed no flag — a unit that relies
|
|
11
14
|
* on a default is a unit whose port changes under it on upgrade. */
|
|
12
15
|
export declare const DEFAULT_PORT = "7233";
|
|
13
|
-
export
|
|
14
|
-
/** Absolute path to the `node` that will run us — `process.execPath`. */
|
|
15
|
-
nodePath: string;
|
|
16
|
-
/**
|
|
17
|
-
* Absolute path to `dist/cli/paddock.js`.
|
|
18
|
-
*
|
|
19
|
-
* NOT the `bin` shim. npm installs a bin as a symlink whose `#!/usr/bin/env
|
|
20
|
-
* node` shebang has to find `node` on PATH, and launchd's PATH is a stub —
|
|
21
|
-
* under a version manager there is no `node` on it at all. Invoking the
|
|
22
|
-
* interpreter explicitly removes the lookup entirely.
|
|
23
|
-
*/
|
|
24
|
-
scriptPath: string;
|
|
25
|
-
/** Arguments after the script: always starts with the `serve` verb. */
|
|
26
|
-
args: string[];
|
|
27
|
-
/** A directory the service can sit in that it certainly owns. */
|
|
28
|
-
workingDirectory: string;
|
|
29
|
-
/**
|
|
30
|
-
* The one file both streams append to, on every OS (SPEC §7).
|
|
31
|
-
*
|
|
32
|
-
* launchd names it twice (`StandardOutPath` and `StandardErrorPath`); systemd
|
|
33
|
-
* names it twice as well (`StandardOutput=append:` / `StandardError=append:`)
|
|
34
|
-
* rather than using the journal. That is what makes `paddock service logs`
|
|
35
|
-
* one Node implementation instead of two, and it is also what makes the
|
|
36
|
-
* Linux logs survive a journal that is volatile or rate-limited — the default
|
|
37
|
-
* on plenty of distributions, where `journalctl --user` after a reboot showed
|
|
38
|
-
* nothing at all.
|
|
39
|
-
*/
|
|
40
|
-
logPath: string;
|
|
41
|
-
/** PATH for the service: Node's bin, the usable inherited entries, the system dirs. */
|
|
42
|
-
pathEnv: string;
|
|
43
|
-
}
|
|
16
|
+
export type { ServiceSpec } from "./manager.js";
|
|
44
17
|
export interface SpecInput {
|
|
45
18
|
nodePath: string;
|
|
46
19
|
scriptPath: string;
|