@kdonev/termscape 0.1.9 → 0.2.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.
Files changed (89) hide show
  1. package/README.md +25 -0
  2. package/dist/cli-args.d.ts +10 -0
  3. package/dist/cli-args.d.ts.map +1 -1
  4. package/dist/cli-args.js +2 -0
  5. package/dist/cli-args.js.map +1 -1
  6. package/dist/cli.js +15 -258
  7. package/dist/cli.js.map +1 -1
  8. package/dist/hub.d.ts +28 -1
  9. package/dist/hub.d.ts.map +1 -1
  10. package/dist/hub.js +65 -2
  11. package/dist/hub.js.map +1 -1
  12. package/dist/hub.tgz +0 -0
  13. package/dist/main.d.ts +7 -0
  14. package/dist/main.d.ts.map +1 -0
  15. package/dist/main.js +343 -0
  16. package/dist/main.js.map +1 -0
  17. package/dist/protocol/domain.d.ts +35 -0
  18. package/dist/protocol/domain.d.ts.map +1 -1
  19. package/dist/protocol/domain.js +31 -2
  20. package/dist/protocol/domain.js.map +1 -1
  21. package/dist/protocol/index.d.ts +1 -0
  22. package/dist/protocol/index.d.ts.map +1 -1
  23. package/dist/protocol/index.js +1 -0
  24. package/dist/protocol/index.js.map +1 -1
  25. package/dist/protocol/peer.d.ts +12 -0
  26. package/dist/protocol/peer.d.ts.map +1 -1
  27. package/dist/protocol/peer.js +30 -1
  28. package/dist/protocol/peer.js.map +1 -1
  29. package/dist/protocol/version.d.ts +12 -0
  30. package/dist/protocol/version.d.ts.map +1 -0
  31. package/dist/protocol/version.js +37 -0
  32. package/dist/protocol/version.js.map +1 -0
  33. package/dist/protocol/ws.d.ts +67 -1
  34. package/dist/protocol/ws.d.ts.map +1 -1
  35. package/dist/protocol/ws.js +11 -1
  36. package/dist/protocol/ws.js.map +1 -1
  37. package/dist/remote/enroll.d.ts.map +1 -1
  38. package/dist/remote/enroll.js +22 -1
  39. package/dist/remote/enroll.js.map +1 -1
  40. package/dist/remote/join.d.ts.map +1 -1
  41. package/dist/remote/join.js +10 -1
  42. package/dist/remote/join.js.map +1 -1
  43. package/dist/remote/peer-serve.d.ts.map +1 -1
  44. package/dist/remote/peer-serve.js +14 -0
  45. package/dist/remote/peer-serve.js.map +1 -1
  46. package/dist/remote/peer.d.ts +7 -0
  47. package/dist/remote/peer.d.ts.map +1 -1
  48. package/dist/remote/peer.js +25 -1
  49. package/dist/remote/peer.js.map +1 -1
  50. package/dist/remote/registry.d.ts +24 -2
  51. package/dist/remote/registry.d.ts.map +1 -1
  52. package/dist/remote/registry.js +76 -5
  53. package/dist/remote/registry.js.map +1 -1
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +11 -1
  56. package/dist/server.js.map +1 -1
  57. package/dist/supervisor.d.ts +44 -0
  58. package/dist/supervisor.d.ts.map +1 -0
  59. package/dist/supervisor.js +125 -0
  60. package/dist/supervisor.js.map +1 -0
  61. package/dist/update/host.d.ts +19 -0
  62. package/dist/update/host.d.ts.map +1 -0
  63. package/dist/update/host.js +56 -0
  64. package/dist/update/host.js.map +1 -0
  65. package/dist/update/install.d.ts +38 -0
  66. package/dist/update/install.d.ts.map +1 -0
  67. package/dist/update/install.js +81 -0
  68. package/dist/update/install.js.map +1 -0
  69. package/dist/update/post-install.d.ts +2 -0
  70. package/dist/update/post-install.d.ts.map +1 -0
  71. package/dist/update/post-install.js +39 -0
  72. package/dist/update/post-install.js.map +1 -0
  73. package/dist/update/restart.d.ts +31 -0
  74. package/dist/update/restart.d.ts.map +1 -0
  75. package/dist/update/restart.js +65 -0
  76. package/dist/update/restart.js.map +1 -0
  77. package/dist/update/resume.d.ts +5 -0
  78. package/dist/update/resume.d.ts.map +1 -0
  79. package/dist/update/resume.js +47 -0
  80. package/dist/update/resume.js.map +1 -0
  81. package/dist/update/updater.d.ts +57 -0
  82. package/dist/update/updater.d.ts.map +1 -0
  83. package/dist/update/updater.js +190 -0
  84. package/dist/update/updater.js.map +1 -0
  85. package/package.json +1 -1
  86. package/web/assets/{index-i7fXLKF0.js → index-BoqmaIyh.js} +9 -9
  87. package/web/assets/index-Czi_vAR-.css +1 -0
  88. package/web/index.html +2 -2
  89. package/web/assets/index-gfbbNQK4.css +0 -1
@@ -0,0 +1,44 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import { type RestartPlan } from './update/restart.js';
3
+ /**
4
+ * The process a terminal actually holds, when the hub can update itself.
5
+ *
6
+ * An update replaces the hub, and the hub cannot restart itself: the new one
7
+ * needs the old one's port, so the old one has to be gone first. Something
8
+ * has to outlive it, and this is that thing. It does nothing but run the hub
9
+ * as a child with the terminal passed straight through, and start whatever
10
+ * the hub left in its restart plan when it exits with EXIT_RESTART - so the
11
+ * terminal stays attached, logs keep printing, and Ctrl+C still stops it.
12
+ *
13
+ * It must stay this small. It never loads the hub, and so never loads a
14
+ * native module: on Windows a loaded .node cannot be replaced, and a global
15
+ * update has to replace exactly those files while this is still running.
16
+ */
17
+ type Values = {
18
+ headless?: boolean;
19
+ join?: string;
20
+ version?: boolean;
21
+ help?: boolean;
22
+ };
23
+ /**
24
+ * Only an installed copy of the published package is supervised: that is the
25
+ * only kind that can update itself. A checkout, a joined machine (updated
26
+ * from its canvas, by an installer that expects to find the hub in hub.pid)
27
+ * and a headless hub all run exactly as before.
28
+ */
29
+ export declare function shouldSupervise(values: Values, cliPath: string, env?: NodeJS.ProcessEnv): boolean;
30
+ /** Tell the supervisor, if there is one, that this hub is exiting and how. */
31
+ export declare function announceExit(code: number): Promise<void>;
32
+ /** Run the hub, and every hub an update asks for after it, until one exits for good. */
33
+ export declare function supervise(cliPath: string): Promise<never>;
34
+ /**
35
+ * The loop itself: start a hub, and when it exits asking to be restarted,
36
+ * start what it left in its restart plan. Resolves with the exit code of the
37
+ * first hub that exits for any other reason.
38
+ */
39
+ export declare function runHubs(first: RestartPlan, opts?: {
40
+ stdio?: Array<'ignore' | 'inherit' | 'pipe'>;
41
+ onChild?: (child: ChildProcess | null) => void;
42
+ }): Promise<number>;
43
+ export {};
44
+ //# sourceMappingURL=supervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAiC,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGtF;;;;;;;;;;;;;GAaG;AAEH,KAAK,MAAM,GAAG;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAIT;AAuBD,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxD;AAED,wFAAwF;AACxF,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAiB/D;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,WAAW,EAClB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAA;CAAO,GAC1G,OAAO,CAAC,MAAM,CAAC,CAmDjB"}
@@ -0,0 +1,125 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { EXIT_RESTART, takeRestartPlan } from './update/restart.js';
3
+ import { isInstalledPackage } from './update/install.js';
4
+ /**
5
+ * Only an installed copy of the published package is supervised: that is the
6
+ * only kind that can update itself. A checkout, a joined machine (updated
7
+ * from its canvas, by an installer that expects to find the hub in hub.pid)
8
+ * and a headless hub all run exactly as before.
9
+ */
10
+ export function shouldSupervise(values, cliPath, env = process.env) {
11
+ if (env.TERMSCAPE_SUPERVISED === '1' || env.TERMSCAPE_NO_SUPERVISOR === '1')
12
+ return false;
13
+ if (values.version || values.help || values.headless || values.join)
14
+ return false;
15
+ return isInstalledPackage(cliPath);
16
+ }
17
+ /** How long a hub that said it is exiting gets to actually be gone. */
18
+ const EXIT_GRACE_MS = 5_000;
19
+ /**
20
+ * Stop a hub that is stuck on its way out, and anything it still holds. On
21
+ * Windows only taskkill reaches the whole tree; its agents' terminals were
22
+ * closed already, but a straggler would keep the new hub's files busy.
23
+ */
24
+ function killTree(child) {
25
+ if (child.pid === undefined || child.exitCode !== null)
26
+ return;
27
+ console.error('[termscape] the hub did not exit; stopping it');
28
+ if (process.platform === 'win32') {
29
+ spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], {
30
+ stdio: 'ignore',
31
+ windowsHide: true,
32
+ });
33
+ }
34
+ else {
35
+ child.kill('SIGKILL');
36
+ }
37
+ }
38
+ /** Tell the supervisor, if there is one, that this hub is exiting and how. */
39
+ export function announceExit(code) {
40
+ const send = process.send?.bind(process);
41
+ if (!send || !process.connected)
42
+ return Promise.resolve();
43
+ return new Promise((resolve) => {
44
+ // A supervisor that went away must not hold this exit up.
45
+ const timer = setTimeout(resolve, 500);
46
+ send({ t: 'exiting', code }, undefined, {}, () => {
47
+ clearTimeout(timer);
48
+ resolve();
49
+ });
50
+ });
51
+ }
52
+ /** Run the hub, and every hub an update asks for after it, until one exits for good. */
53
+ export async function supervise(cliPath) {
54
+ let child = null;
55
+ // Ctrl+C reaches every process on the terminal, the hub included, and the
56
+ // hub is the one that decides what stopping means. This only waits for it.
57
+ process.on('SIGINT', () => { });
58
+ for (const sig of ['SIGTERM', 'SIGHUP']) {
59
+ process.on(sig, () => {
60
+ if (child)
61
+ child.kill(sig);
62
+ else
63
+ process.exit(1);
64
+ });
65
+ }
66
+ const code = await runHubs({ command: process.execPath, args: [...process.execArgv, cliPath, ...process.argv.slice(2)] }, { onChild: (c) => (child = c) });
67
+ process.exit(code);
68
+ }
69
+ /**
70
+ * The loop itself: start a hub, and when it exits asking to be restarted,
71
+ * start what it left in its restart plan. Resolves with the exit code of the
72
+ * first hub that exits for any other reason.
73
+ */
74
+ export async function runHubs(first, opts = {}) {
75
+ // Anything left over from a restart that never happened is not ours to run.
76
+ takeRestartPlan();
77
+ const [stdin, stdout, stderr] = opts.stdio ?? ['inherit', 'inherit', 'inherit'];
78
+ let plan = first;
79
+ for (;;) {
80
+ const started = spawn(plan.command, plan.args, {
81
+ // The extra channel is how a hub says it is on its way out; see below.
82
+ stdio: [stdin, stdout, stderr, 'ipc'],
83
+ env: { ...process.env, ...plan.env, TERMSCAPE_SUPERVISED: '1' },
84
+ });
85
+ opts.onChild?.(started);
86
+ const code = await new Promise((resolve) => {
87
+ /*
88
+ * A hub announces its exit, and its code, before calling process.exit -
89
+ * because on Windows that call has been seen to never return once an
90
+ * agent's terminal had been open, leaving a process with its state
91
+ * saved and its port closed that nothing would ever restart. Past the
92
+ * grace period it is killed here, and its announced code stands.
93
+ */
94
+ let announced = null;
95
+ let watchdog = null;
96
+ started.on('message', (msg) => {
97
+ const m = msg;
98
+ if (m?.t !== 'exiting' || typeof m.code !== 'number' || watchdog)
99
+ return;
100
+ announced = m.code;
101
+ watchdog = setTimeout(() => killTree(started), EXIT_GRACE_MS);
102
+ });
103
+ started.on('error', (err) => {
104
+ console.error(`[termscape] could not start the hub: ${err.message}`);
105
+ resolve(1);
106
+ });
107
+ started.on('exit', (c, signal) => {
108
+ if (watchdog)
109
+ clearTimeout(watchdog);
110
+ resolve(announced ?? c ?? (signal ? 1 : 0));
111
+ });
112
+ });
113
+ opts.onChild?.(null);
114
+ if (code !== EXIT_RESTART)
115
+ return code;
116
+ const next = takeRestartPlan();
117
+ if (!next) {
118
+ console.error('[termscape] the hub asked to be restarted but left no plan; stopping');
119
+ return 1;
120
+ }
121
+ console.log('\n[termscape] restarting\n');
122
+ plan = next;
123
+ }
124
+ }
125
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Update a machine joined to a canvas, to the build that canvas runs.
3
+ *
4
+ * It runs the canvas's own join installer again, which already knows how to
5
+ * do every part of this - stop the hub that is running (this one), swap the
6
+ * install while keeping the dependency tree where it can, start the new hub
7
+ * and wait for it to rejoin. The installer carries a fresh join key, but the
8
+ * host token already on disk is tried first, so this machine comes back as
9
+ * itself rather than as a second machine.
10
+ *
11
+ * The installer is started detached, because it outlives this process by
12
+ * design: the first thing it does is stop it. Its output goes to
13
+ * update.log beside the hub's own, which is the one place to look when a
14
+ * machine was told to update and never came back.
15
+ *
16
+ * Resolves once the installer is fetched and running.
17
+ */
18
+ export declare function runHostUpdate(hubUrl: string, platform?: NodeJS.Platform): Promise<void>;
19
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/update/host.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -0,0 +1,56 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { closeSync, openSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { paths } from '../paths.js';
5
+ /**
6
+ * Update a machine joined to a canvas, to the build that canvas runs.
7
+ *
8
+ * It runs the canvas's own join installer again, which already knows how to
9
+ * do every part of this - stop the hub that is running (this one), swap the
10
+ * install while keeping the dependency tree where it can, start the new hub
11
+ * and wait for it to rejoin. The installer carries a fresh join key, but the
12
+ * host token already on disk is tried first, so this machine comes back as
13
+ * itself rather than as a second machine.
14
+ *
15
+ * The installer is started detached, because it outlives this process by
16
+ * design: the first thing it does is stop it. Its output goes to
17
+ * update.log beside the hub's own, which is the one place to look when a
18
+ * machine was told to update and never came back.
19
+ *
20
+ * Resolves once the installer is fetched and running.
21
+ */
22
+ export async function runHostUpdate(hubUrl, platform = process.platform) {
23
+ const windows = platform === 'win32';
24
+ const origin = new URL(hubUrl).origin;
25
+ const res = await fetch(`${origin}/${windows ? 'join.ps1' : 'join.sh'}`, {
26
+ signal: AbortSignal.timeout(30_000),
27
+ });
28
+ if (!res.ok)
29
+ throw new Error(`the canvas answered ${res.status} for its installer`);
30
+ const script = await res.text();
31
+ const home = paths.home();
32
+ const file = join(home, windows ? 'update.ps1' : 'update.sh');
33
+ writeFileSync(file, script, { mode: 0o700 });
34
+ const log = openSync(join(home, 'update.log'), 'w');
35
+ try {
36
+ const [command, args] = windows
37
+ ? ['powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', file]]
38
+ : ['sh', [file]];
39
+ const child = spawn(command, args, {
40
+ detached: true,
41
+ windowsHide: true,
42
+ stdio: ['ignore', log, log],
43
+ // The installer reads TERMSCAPE_HOME to find the install it replaces.
44
+ env: { ...process.env, TERMSCAPE_HOME: home },
45
+ });
46
+ await new Promise((resolve, reject) => {
47
+ child.once('spawn', () => resolve());
48
+ child.once('error', reject);
49
+ });
50
+ child.unref();
51
+ }
52
+ finally {
53
+ closeSync(log);
54
+ }
55
+ }
56
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/update/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,WAA4B,OAAO,CAAC,QAAQ;IAE5C,MAAM,OAAO,GAAG,QAAQ,KAAK,OAAO,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE;QACvE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;KACpC,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,MAAM,oBAAoB,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAEhC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC9D,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAE7C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC;QACH,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,OAAO;YAC7B,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YACpG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QACnB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACjC,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,IAAI;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC;YAC3B,sEAAsE;YACtE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE;SAC9C,CAAC,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;AACH,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { InstallKind } from '../protocol/index.js';
2
+ /** What the package is called on the registry. */
3
+ export declare const PACKAGE_NAME = "@kdonev/termscape";
4
+ /**
5
+ * Whether this file sits inside an installed copy of the published package,
6
+ * as opposed to a checkout. The one thing the supervisor needs to know, and it
7
+ * is decided from the path alone because the supervisor must not spend a
8
+ * second asking npm before the hub has even started.
9
+ */
10
+ export declare function isInstalledPackage(cliPath: string): boolean;
11
+ /**
12
+ * How this hub came to be running, which is how it can replace itself.
13
+ *
14
+ * A global install is told apart from a copy inside somebody's project by
15
+ * the shim npm puts beside it: `<prefix>/termscape.cmd` on Windows,
16
+ * `<prefix>/bin/termscape` beside `<prefix>/lib/node_modules` elsewhere. The
17
+ * project copy cannot be updated by `npm install -g`, which would install a
18
+ * second copy nothing runs.
19
+ *
20
+ * Read off the disk rather than asked of `npm root -g`, whose answer npm
21
+ * redacts: a path segment that looks like a token, a UUID say, comes back as
22
+ * `***` and then matches nothing.
23
+ */
24
+ export declare function installKind(cliPath: string, opts: {
25
+ joined: boolean;
26
+ exists?: (p: string) => boolean;
27
+ platform?: NodeJS.Platform;
28
+ }): InstallKind;
29
+ /**
30
+ * npm's own entry point, run with this Node rather than through `npm` on the
31
+ * PATH: on Windows that is npm.cmd, which cannot be spawned without a shell,
32
+ * and a shell drags its quoting rules into every argument. Null when npm is
33
+ * not where Node's own distribution puts it.
34
+ */
35
+ export declare function npmCliPath(nodeExe?: string): string | null;
36
+ /** Run npm and collect its stdout. Rejects with its output on a non-zero exit. */
37
+ export declare function runNpm(args: string[], timeoutMs?: number): Promise<string>;
38
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/update/install.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,kDAAkD;AAClD,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAQhD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAE,GACrF,WAAW,CAeb;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,SAAmB,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED,kFAAkF;AAClF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAoB9E"}
@@ -0,0 +1,81 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join, posix } from 'node:path';
4
+ /** What the package is called on the registry. */
5
+ export const PACKAGE_NAME = '@kdonev/termscape';
6
+ /** A path compared the same way on every platform. */
7
+ function norm(p) {
8
+ const slashed = p.replace(/\\/g, '/');
9
+ return process.platform === 'win32' ? slashed.toLowerCase() : slashed;
10
+ }
11
+ /**
12
+ * Whether this file sits inside an installed copy of the published package,
13
+ * as opposed to a checkout. The one thing the supervisor needs to know, and it
14
+ * is decided from the path alone because the supervisor must not spend a
15
+ * second asking npm before the hub has even started.
16
+ */
17
+ export function isInstalledPackage(cliPath) {
18
+ return norm(cliPath).includes(`/node_modules/${PACKAGE_NAME}/`);
19
+ }
20
+ /**
21
+ * How this hub came to be running, which is how it can replace itself.
22
+ *
23
+ * A global install is told apart from a copy inside somebody's project by
24
+ * the shim npm puts beside it: `<prefix>/termscape.cmd` on Windows,
25
+ * `<prefix>/bin/termscape` beside `<prefix>/lib/node_modules` elsewhere. The
26
+ * project copy cannot be updated by `npm install -g`, which would install a
27
+ * second copy nothing runs.
28
+ *
29
+ * Read off the disk rather than asked of `npm root -g`, whose answer npm
30
+ * redacts: a path segment that looks like a token, a UUID say, comes back as
31
+ * `***` and then matches nothing.
32
+ */
33
+ export function installKind(cliPath, opts) {
34
+ if (opts.joined)
35
+ return 'host';
36
+ if (!isInstalledPackage(cliPath))
37
+ return 'source';
38
+ if (norm(cliPath).includes('/_npx/'))
39
+ return 'npx';
40
+ const exists = opts.exists ?? existsSync;
41
+ const slashed = cliPath.replace(/\\/g, '/');
42
+ const at = slashed.toLowerCase().lastIndexOf(`/node_modules/${PACKAGE_NAME}/`);
43
+ const modules = slashed.slice(0, at + '/node_modules'.length);
44
+ const prefix = posix.dirname(modules);
45
+ const global = (opts.platform ?? process.platform) === 'win32'
46
+ ? exists(`${prefix}/termscape.cmd`)
47
+ : posix.basename(prefix) === 'lib' && exists(`${posix.dirname(prefix)}/bin/termscape`);
48
+ return global ? 'global' : 'source';
49
+ }
50
+ /**
51
+ * npm's own entry point, run with this Node rather than through `npm` on the
52
+ * PATH: on Windows that is npm.cmd, which cannot be spawned without a shell,
53
+ * and a shell drags its quoting rules into every argument. Null when npm is
54
+ * not where Node's own distribution puts it.
55
+ */
56
+ export function npmCliPath(nodeExe = process.execPath) {
57
+ const dir = dirname(nodeExe);
58
+ const candidates = [
59
+ join(dir, 'node_modules', 'npm', 'bin', 'npm-cli.js'), // Windows layout
60
+ join(dir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'), // POSIX layout
61
+ ];
62
+ return candidates.find((p) => existsSync(p)) ?? null;
63
+ }
64
+ /** Run npm and collect its stdout. Rejects with its output on a non-zero exit. */
65
+ export function runNpm(args, timeoutMs = 5 * 60_000) {
66
+ const cli = npmCliPath();
67
+ const [cmd, argv, shell] = cli
68
+ ? [process.execPath, [cli, ...args], false]
69
+ : ['npm', args, process.platform === 'win32'];
70
+ return new Promise((resolve, reject) => {
71
+ execFile(cmd, argv, { timeout: timeoutMs, shell, windowsHide: true, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
72
+ if (err) {
73
+ const detail = `${stderr}`.trim().split('\n').slice(-5).join('\n');
74
+ reject(new Error(`npm ${args[0]} failed: ${detail || err.message}`));
75
+ return;
76
+ }
77
+ resolve(`${stdout}`);
78
+ });
79
+ });
80
+ }
81
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/update/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=post-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-install.d.ts","sourceRoot":"","sources":["../../src/update/post-install.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The step between a hub and its update, for a global install.
3
+ *
4
+ * Started by the supervisor once the old hub has exited - by then nothing
5
+ * holds the package's native modules open, which is what lets Windows replace
6
+ * them. It installs the release, then exits with EXIT_RESTART and a plan to
7
+ * start the hub from the same place it always ran from. If the install fails,
8
+ * the plan starts the old hub again instead, carrying the error so the canvas
9
+ * can say what happened rather than simply not having updated.
10
+ *
11
+ * node post-install.js <version> <cli.js> [hub args...]
12
+ */
13
+ import { spawnSync } from 'node:child_process';
14
+ import { EXIT_RESTART, writeRestartPlan } from './restart.js';
15
+ import { PACKAGE_NAME, npmCliPath } from './install.js';
16
+ const [version, cliPath, ...args] = process.argv.slice(2);
17
+ if (!version || !cliPath) {
18
+ console.error('usage: post-install.js <version> <cli.js> [hub args...]');
19
+ process.exit(1);
20
+ }
21
+ const spec = `${PACKAGE_NAME}@${version}`;
22
+ console.log(`[termscape] installing ${spec}`);
23
+ const npm = npmCliPath();
24
+ const npmArgs = ['install', '-g', '--no-audit', '--no-fund', spec];
25
+ const result = npm
26
+ ? spawnSync(process.execPath, [npm, ...npmArgs], { stdio: 'inherit' })
27
+ : spawnSync('npm', npmArgs, { stdio: 'inherit', shell: process.platform === 'win32' });
28
+ const failed = result.error?.message ??
29
+ (result.status === 0 ? null : `npm install -g exited with ${result.status}`);
30
+ if (failed) {
31
+ console.error(`[termscape] update failed: ${failed}; starting the version already installed`);
32
+ }
33
+ writeRestartPlan({
34
+ command: process.execPath,
35
+ args: [cliPath, ...args],
36
+ ...(failed ? { env: { TERMSCAPE_UPDATE_ERROR: `could not install ${spec}: ${failed}` } } : {}),
37
+ });
38
+ process.exit(EXIT_RESTART);
39
+ //# sourceMappingURL=post-install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-install.js","sourceRoot":"","sources":["../../src/update/post-install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1D,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACzB,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,IAAI,GAAG,GAAG,YAAY,IAAI,OAAO,EAAE,CAAC;AAC1C,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;AAC9C,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;AACzB,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;AACnE,MAAM,MAAM,GAAG,GAAG;IAChB,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACtE,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC,CAAC;AAEzF,MAAM,MAAM,GACV,MAAM,CAAC,KAAK,EAAE,OAAO;IACrB,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,8BAA8B,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/E,IAAI,MAAM,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CAAC,8BAA8B,MAAM,0CAA0C,CAAC,CAAC;AAChG,CAAC;AAED,gBAAgB,CAAC;IACf,OAAO,EAAE,OAAO,CAAC,QAAQ;IACzB,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,sBAAsB,EAAE,qBAAqB,IAAI,KAAK,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;CAC/F,CAAC,CAAC;AACH,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The exit code a supervised hub uses to say "start me again, as the restart
3
+ * plan says". 75 is EX_TEMPFAIL: nothing else in the hub exits with it.
4
+ */
5
+ export declare const EXIT_RESTART = 75;
6
+ /** What the supervisor runs next, written by the hub on its way out. */
7
+ export interface RestartPlan {
8
+ command: string;
9
+ args: string[];
10
+ env?: Record<string, string>;
11
+ }
12
+ export declare function restartFile(): string;
13
+ export declare function writeRestartPlan(plan: RestartPlan): void;
14
+ /** Read and remove the plan, so a stale one can never start anything twice. */
15
+ export declare function takeRestartPlan(): RestartPlan | null;
16
+ /**
17
+ * The command line the updated hub starts with: the one this hub was given,
18
+ * pinned to the port and token it actually ended up with.
19
+ *
20
+ * Pinned because a canvas already open - in the app window about to be
21
+ * replaced, in a browser tab, on a phone - holds a URL with both in it, and
22
+ * reconnects to that URL on its own if the new hub is where the old one was.
23
+ * A browser tab needs no second tab opened beside it, so `--browser` becomes
24
+ * `--no-open` too; the app window closes with the hub that owned it and the
25
+ * new hub opens its own.
26
+ */
27
+ export declare function restartArgs(argv: readonly string[], pin: {
28
+ port: number;
29
+ token: string;
30
+ }): string[];
31
+ //# sourceMappingURL=restart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restart.d.ts","sourceRoot":"","sources":["../../src/update/restart.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAExD;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,IAAI,WAAW,GAAG,IAAI,CAgBpD;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,EAAE,CAcnG"}
@@ -0,0 +1,65 @@
1
+ import { readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { paths } from '../paths.js';
4
+ /**
5
+ * The exit code a supervised hub uses to say "start me again, as the restart
6
+ * plan says". 75 is EX_TEMPFAIL: nothing else in the hub exits with it.
7
+ */
8
+ export const EXIT_RESTART = 75;
9
+ export function restartFile() {
10
+ return join(paths.home(), 'restart.json');
11
+ }
12
+ export function writeRestartPlan(plan) {
13
+ writeFileSync(restartFile(), JSON.stringify(plan));
14
+ }
15
+ /** Read and remove the plan, so a stale one can never start anything twice. */
16
+ export function takeRestartPlan() {
17
+ const file = restartFile();
18
+ let raw;
19
+ try {
20
+ raw = readFileSync(file, 'utf8');
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ rmSync(file, { force: true });
26
+ try {
27
+ const plan = JSON.parse(raw);
28
+ if (typeof plan.command !== 'string' || !Array.isArray(plan.args))
29
+ return null;
30
+ return plan;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ const PINNED = ['--port', '--token'];
37
+ /**
38
+ * The command line the updated hub starts with: the one this hub was given,
39
+ * pinned to the port and token it actually ended up with.
40
+ *
41
+ * Pinned because a canvas already open - in the app window about to be
42
+ * replaced, in a browser tab, on a phone - holds a URL with both in it, and
43
+ * reconnects to that URL on its own if the new hub is where the old one was.
44
+ * A browser tab needs no second tab opened beside it, so `--browser` becomes
45
+ * `--no-open` too; the app window closes with the hub that owned it and the
46
+ * new hub opens its own.
47
+ */
48
+ export function restartArgs(argv, pin) {
49
+ const out = [];
50
+ for (let i = 0; i < argv.length; i++) {
51
+ const arg = argv[i];
52
+ const name = arg.split('=')[0];
53
+ if (PINNED.includes(name)) {
54
+ if (!arg.includes('='))
55
+ i++; // `--port 7777`: skip the value too
56
+ continue;
57
+ }
58
+ out.push(arg);
59
+ }
60
+ if (out.includes('--browser') && !out.includes('--no-open'))
61
+ out.push('--no-open');
62
+ out.push(`--port=${pin.port}`, `--token=${pin.token}`);
63
+ return out;
64
+ }
65
+ //# sourceMappingURL=restart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restart.js","sourceRoot":"","sources":["../../src/update/restart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAS/B,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,cAAc,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAiB;IAChD,aAAa,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;QAC5C,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/E,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAErC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,IAAuB,EAAE,GAAoC;IACvF,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACrB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QAChC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,CAAC,EAAE,CAAC,CAAC,oCAAoC;YACjE,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACnF,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,EAAE,EAAE,WAAW,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACvD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function resumeFile(): string;
2
+ export declare function saveResumeList(sessionIds: string[], now?: number): void;
3
+ /** Read and remove the list, so it is acted on once. Empty when stale or absent. */
4
+ export declare function takeResumeList(now?: number): string[];
5
+ //# sourceMappingURL=resume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/update/resume.ts"],"names":[],"mappings":"AAwBA,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,SAAa,GAAG,IAAI,CAG3E;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,GAAG,SAAa,GAAG,MAAM,EAAE,CAkBzD"}
@@ -0,0 +1,47 @@
1
+ import { readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { paths } from '../paths.js';
4
+ /**
5
+ * Which agents were running when a hub went down to be updated, so the hub
6
+ * that replaces it can bring exactly those back.
7
+ *
8
+ * Only an update writes this. Stopping a hub by hand is a decision about the
9
+ * agents too, and those come back stopped, as they always have.
10
+ */
11
+ /**
12
+ * An update that never finished - an installer that failed, a machine that
13
+ * went to sleep halfway - must not resume agents at some unrelated start
14
+ * days later.
15
+ */
16
+ const MAX_AGE_MS = 30 * 60_000;
17
+ export function resumeFile() {
18
+ return join(paths.home(), 'resume.json');
19
+ }
20
+ export function saveResumeList(sessionIds, now = Date.now()) {
21
+ const list = { at: now, sessionIds };
22
+ writeFileSync(resumeFile(), JSON.stringify(list));
23
+ }
24
+ /** Read and remove the list, so it is acted on once. Empty when stale or absent. */
25
+ export function takeResumeList(now = Date.now()) {
26
+ const file = resumeFile();
27
+ let raw;
28
+ try {
29
+ raw = readFileSync(file, 'utf8');
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ rmSync(file, { force: true });
35
+ try {
36
+ const list = JSON.parse(raw);
37
+ if (typeof list.at !== 'number' || now - list.at > MAX_AGE_MS)
38
+ return [];
39
+ return Array.isArray(list.sessionIds)
40
+ ? list.sessionIds.filter((id) => typeof id === 'string')
41
+ : [];
42
+ }
43
+ catch {
44
+ return [];
45
+ }
46
+ }
47
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../src/update/resume.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,UAAU,GAAG,EAAE,GAAG,MAAM,CAAC;AAO/B,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,aAAa,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAoB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACnE,MAAM,IAAI,GAAe,EAAE,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;IACjD,aAAa,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC7C,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QACpD,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,GAAG,GAAG,IAAI,CAAC,EAAE,GAAG,UAAU;YAAE,OAAO,EAAE,CAAC;QACzE,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;YACnC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,CAAC;YACtE,CAAC,CAAC,EAAE,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}