pinomad 0.13.0 → 0.14.1
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/dist/{chunk-kmzzjv53.js → chunk-1mjbt2z2.js} +143 -10
- package/dist/{chunk-90g1cg9n.js → chunk-3hfaejdx.js} +1 -1
- package/dist/{chunk-d9mq5d46.js → chunk-5ayqca95.js} +10 -8
- package/dist/{chunk-7mn38n4f.js → chunk-63ymn5c9.js} +2 -2
- package/dist/{chunk-563fnad9.js → chunk-6j6hkfbj.js} +8 -4
- package/dist/{chunk-pqe7q1s7.js → chunk-6vbp1vva.js} +2 -1
- package/dist/chunk-8g2e77gf.js +46 -0
- package/dist/{chunk-6th9xv39.js → chunk-97ssw1c0.js} +9 -7
- package/dist/{chunk-g3ahp7b9.js → chunk-9trbbz4r.js} +1 -1
- package/dist/{chunk-4fferbxw.js → chunk-e59k3bmz.js} +213 -30
- package/dist/{chunk-s0h7c7da.js → chunk-ega1wnkg.js} +1 -1
- package/dist/{chunk-maqv34cs.js → chunk-evbv1fkn.js} +3 -3
- package/dist/{chunk-d4tq62yr.js → chunk-f6741a5q.js} +12 -11
- package/dist/{chunk-m51mq8vh.js → chunk-fdhbzwxj.js} +12 -10
- package/dist/{chunk-aapsc9g0.js → chunk-hfwa1vt5.js} +1952 -295
- package/dist/{chunk-t91zt5f0.js → chunk-hg9gs396.js} +1 -1
- package/dist/chunk-mdjk0s0t.js +23 -0
- package/dist/{chunk-wd7aracm.js → chunk-mxwcnnkg.js} +1 -303
- package/dist/chunk-pte4peaf.js +315 -0
- package/dist/{chunk-53xv3fny.js → chunk-s66evzar.js} +2446 -416
- package/dist/chunk-vedqqhbd.js +240 -0
- package/dist/{chunk-ra86wjp5.js → chunk-w9zpff3n.js} +11 -6
- package/dist/pinomad.js +12 -12
- package/package.json +5 -1
- package/web/assets/{core-BHm2gdHL.js → core-BWHGsFpV.js} +1 -1
- package/web/assets/{highlighted-diff-DrHvSLK9.js → highlighted-diff-DJ7KZCpS.js} +2 -2
- package/web/assets/{highlights-1aLwssz-.js → highlights-CyEmo6OI.js} +1 -1
- package/web/assets/{highlights-themes-C1p4pFYS.js → highlights-themes-CSNkBojy.js} +1 -1
- package/web/assets/index-C_qkbyL7.css +1 -0
- package/web/assets/index-DefQtCI2.js +396 -0
- package/web/assets/{shiki-BwyCm3kZ.js → shiki-BzCmRG-v.js} +2 -2
- package/web/assets/{shiki-langs-CMlALLSg.js → shiki-langs-DOkjyV4d.js} +1 -1
- package/web/assets/{stream-tokenizer-BlgIhw0y.js → stream-tokenizer-CBPqFe4Z.js} +1 -1
- package/web/assets/{theming-themes-DDvoqEqT.js → theming-themes-wKVca0aB.js} +1 -1
- package/web/assets/{xterm-view-BnKOnF3C.js → xterm-view-CKa1-QCM.js} +12 -12
- package/web/index.html +3 -2
- package/web/theme-boot.js +11 -0
- package/dist/chunk-sd640dt7.js +0 -22
- package/dist/chunk-t6fw0wa9.js +0 -96
- package/web/assets/index-A_AUJoO6.css +0 -1
- package/web/assets/index-BafYMKiN.js +0 -396
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
joinServer,
|
|
4
|
+
main
|
|
5
|
+
} from "./chunk-5ayqca95.js";
|
|
6
|
+
import"./chunk-63ymn5c9.js";
|
|
7
|
+
import"./chunk-6j6hkfbj.js";
|
|
8
|
+
import"./chunk-hg9gs396.js";
|
|
9
|
+
import"./chunk-nq57fdnq.js";
|
|
10
|
+
import"./chunk-9trbbz4r.js";
|
|
11
|
+
import"./chunk-pte4peaf.js";
|
|
12
|
+
import"./chunk-e59k3bmz.js";
|
|
13
|
+
import"./chunk-kzbzacz9.js";
|
|
14
|
+
import"./chunk-mxwcnnkg.js";
|
|
15
|
+
import"./chunk-1mjbt2z2.js";
|
|
16
|
+
import"./chunk-6vbp1vva.js";
|
|
17
|
+
import"./chunk-ngf1zatm.js";
|
|
18
|
+
import"./chunk-ega1wnkg.js";
|
|
19
|
+
import"./chunk-df32avw6.js";
|
|
20
|
+
export {
|
|
21
|
+
main,
|
|
22
|
+
joinServer
|
|
23
|
+
};
|
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ProtocolMismatchError,
|
|
3
|
-
connectRemoteDurable
|
|
4
|
-
} from "./chunk-4fferbxw.js";
|
|
5
|
-
import {
|
|
6
|
-
isParseArgsError
|
|
7
|
-
} from "./chunk-kzbzacz9.js";
|
|
8
|
-
import {
|
|
9
|
-
DEFAULT_DATA_DIR,
|
|
10
|
-
hostAddress
|
|
11
|
-
} from "./chunk-ngf1zatm.js";
|
|
12
|
-
import {
|
|
13
|
-
commandHint,
|
|
14
|
-
hostEntry,
|
|
15
|
-
packagedVersion
|
|
16
|
-
} from "./chunk-s0h7c7da.js";
|
|
17
|
-
|
|
18
|
-
// apps/host/src/cli/service.ts
|
|
19
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
20
|
-
import { homedir as homedir2 } from "node:os";
|
|
21
|
-
import { resolve as resolve2 } from "node:path";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
23
|
-
import { parseArgs, promisify as promisify2 } from "node:util";
|
|
24
|
-
|
|
25
1
|
// apps/host/src/service/definition.ts
|
|
26
2
|
import { join, resolve } from "node:path";
|
|
27
3
|
var LAUNCHER_EXECUTABLE = "pinomad-host";
|
|
@@ -219,37 +195,6 @@ WantedBy=default.target
|
|
|
219
195
|
`;
|
|
220
196
|
}
|
|
221
197
|
|
|
222
|
-
// apps/host/src/service/idle.ts
|
|
223
|
-
async function waitForIdle(remote, onBusy) {
|
|
224
|
-
if (remote.view.current().tasks === undefined)
|
|
225
|
-
await remote.controller.toggleTasks();
|
|
226
|
-
let announced = false;
|
|
227
|
-
await new Promise((resolve2) => {
|
|
228
|
-
const check = () => {
|
|
229
|
-
const view = remote.view.current();
|
|
230
|
-
if (view.connection !== "connected") {
|
|
231
|
-
unsubscribe();
|
|
232
|
-
resolve2();
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
const tasks = view.tasks;
|
|
236
|
-
if (tasks === undefined)
|
|
237
|
-
return;
|
|
238
|
-
if (Object.keys(tasks.tasks).length === 0) {
|
|
239
|
-
unsubscribe();
|
|
240
|
-
resolve2();
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
if (!announced) {
|
|
244
|
-
announced = true;
|
|
245
|
-
onBusy(tasks);
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
const unsubscribe = remote.view.subscribe(check);
|
|
249
|
-
check();
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
198
|
// apps/host/src/service/manager.ts
|
|
254
199
|
import { execFile, spawn } from "node:child_process";
|
|
255
200
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
@@ -428,251 +373,4 @@ function serviceManager(platform = process.platform) {
|
|
|
428
373
|
throw new Error(`service commands support macOS and Linux, not ${platform}`);
|
|
429
374
|
}
|
|
430
375
|
|
|
431
|
-
|
|
432
|
-
var run2 = promisify2(execFile2);
|
|
433
|
-
var repoRoot = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
434
|
-
var message = (error) => error instanceof Error ? error.message : String(error);
|
|
435
|
-
function connectToHost(url, token, timeoutMs = 3000) {
|
|
436
|
-
const connecting = connectRemoteDurable({ url, token, reconnectDelayMs: { min: 200, max: 2000 } });
|
|
437
|
-
const timedOut = Symbol("timedOut");
|
|
438
|
-
return Promise.race([
|
|
439
|
-
connecting,
|
|
440
|
-
new Promise((resolve3) => {
|
|
441
|
-
const timer = setTimeout(() => resolve3(timedOut), timeoutMs);
|
|
442
|
-
timer.unref();
|
|
443
|
-
})
|
|
444
|
-
]).then((winner) => {
|
|
445
|
-
if (winner !== timedOut)
|
|
446
|
-
return winner;
|
|
447
|
-
connecting.then((remote) => remote.close(), () => {});
|
|
448
|
-
throw new Error("connect timed out");
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
async function install(argv) {
|
|
452
|
-
const spec = installSpec(argv, {
|
|
453
|
-
execPath: process.execPath,
|
|
454
|
-
env: process.env,
|
|
455
|
-
home: homedir2(),
|
|
456
|
-
cwd: process.cwd(),
|
|
457
|
-
mainPath: hostEntry.script,
|
|
458
|
-
entryArgs: hostEntry.args
|
|
459
|
-
});
|
|
460
|
-
const path = await serviceManager().install(spec);
|
|
461
|
-
console.log(`installed ${path}`);
|
|
462
|
-
console.log(`data dir: ${spec.dataDir}`);
|
|
463
|
-
console.log(`browser link: ${commandHint("link")} pairing QR: ${commandHint("pair")}`);
|
|
464
|
-
}
|
|
465
|
-
async function waitUntilIdle(address, connect, log) {
|
|
466
|
-
try {
|
|
467
|
-
const remote = await connect(address.url, address.token);
|
|
468
|
-
await waitForIdle(remote, (tasks) => {
|
|
469
|
-
log("waiting for running tasks to finish (Ctrl-C to cancel)");
|
|
470
|
-
for (const task of Object.values(tasks.tasks)) {
|
|
471
|
-
log(` ${task.kind} in conversation ${task.conversationId}`);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
remote.close();
|
|
475
|
-
} catch {}
|
|
476
|
-
}
|
|
477
|
-
async function stopService({
|
|
478
|
-
force,
|
|
479
|
-
argv,
|
|
480
|
-
manager = serviceManager(),
|
|
481
|
-
connect = connectToHost,
|
|
482
|
-
log = (line) => console.log(line),
|
|
483
|
-
logError = (line) => console.error(line)
|
|
484
|
-
}) {
|
|
485
|
-
const state = await manager.state();
|
|
486
|
-
if (!state.installed) {
|
|
487
|
-
log(`service not installed (${commandHint("service")} install)`);
|
|
488
|
-
return 1;
|
|
489
|
-
}
|
|
490
|
-
if (state.stopped) {
|
|
491
|
-
log("already stopped");
|
|
492
|
-
return 0;
|
|
493
|
-
}
|
|
494
|
-
if (!force) {
|
|
495
|
-
const spec = argv.length === 0 ? await manager.read() : undefined;
|
|
496
|
-
const port = spec === undefined ? undefined : argValue(spec.hostArgs, "--port");
|
|
497
|
-
const flags = spec === undefined ? argv : ["--data-dir", spec.dataDir, ...port === undefined ? [] : ["--url", `ws://127.0.0.1:${port}`]];
|
|
498
|
-
const address = await hostAddress(flags).catch((error) => {
|
|
499
|
-
if (isParseArgsError(error))
|
|
500
|
-
throw error;
|
|
501
|
-
return;
|
|
502
|
-
});
|
|
503
|
-
if (address !== undefined)
|
|
504
|
-
await waitUntilIdle(address, connect, log);
|
|
505
|
-
}
|
|
506
|
-
const holdInterrupt = () => {};
|
|
507
|
-
process.on("SIGINT", holdInterrupt);
|
|
508
|
-
try {
|
|
509
|
-
await manager.stop();
|
|
510
|
-
} finally {
|
|
511
|
-
process.off("SIGINT", holdInterrupt);
|
|
512
|
-
}
|
|
513
|
-
const after = await manager.state();
|
|
514
|
-
if (!after.stopped) {
|
|
515
|
-
logError(`the host is still running (${after.detail}); run \`${commandHint("service")} stop\` again`);
|
|
516
|
-
return 1;
|
|
517
|
-
}
|
|
518
|
-
log(`stopped; it stays off, also after a restart, until \`${commandHint("service")} start\``);
|
|
519
|
-
return 0;
|
|
520
|
-
}
|
|
521
|
-
async function startService({
|
|
522
|
-
manager = serviceManager(),
|
|
523
|
-
log = (line) => console.log(line)
|
|
524
|
-
} = {}) {
|
|
525
|
-
if (!(await manager.state()).installed) {
|
|
526
|
-
log(`service not installed (${commandHint("service")} install)`);
|
|
527
|
-
return 1;
|
|
528
|
-
}
|
|
529
|
-
await manager.start();
|
|
530
|
-
log("started");
|
|
531
|
-
return 0;
|
|
532
|
-
}
|
|
533
|
-
async function versionLine() {
|
|
534
|
-
if (packagedVersion !== undefined)
|
|
535
|
-
return `version: ${packagedVersion}`;
|
|
536
|
-
const head = await run2("git", ["rev-parse", "--short", "HEAD"], { cwd: repoRoot }).then(({ stdout }) => stdout.trim(), () => "unknown");
|
|
537
|
-
return `checkout: ${head}`;
|
|
538
|
-
}
|
|
539
|
-
async function reachabilityLine(argv) {
|
|
540
|
-
try {
|
|
541
|
-
const { url, token } = await hostAddress(argv);
|
|
542
|
-
const remote = await connectToHost(url, token);
|
|
543
|
-
if (remote.view.current().tasks === undefined)
|
|
544
|
-
await remote.controller.toggleTasks();
|
|
545
|
-
const tasks = remote.view.current().tasks;
|
|
546
|
-
const count = tasks === undefined ? 0 : Object.keys(tasks.tasks).length;
|
|
547
|
-
remote.close();
|
|
548
|
-
return `host: reachable at ${url}, ${count} live task${count === 1 ? "" : "s"}`;
|
|
549
|
-
} catch (error) {
|
|
550
|
-
if (isParseArgsError(error))
|
|
551
|
-
throw error;
|
|
552
|
-
return `host: unreachable (${message(error)})`;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
var serviceStatusJson = (state) => JSON.stringify({
|
|
556
|
-
installed: state.installed,
|
|
557
|
-
stopped: state.stopped,
|
|
558
|
-
running: state.running,
|
|
559
|
-
...state.pid === undefined ? {} : { pid: state.pid },
|
|
560
|
-
detail: state.detail
|
|
561
|
-
});
|
|
562
|
-
async function status(argv) {
|
|
563
|
-
const state = await serviceManager().state();
|
|
564
|
-
if (argv.includes("--json")) {
|
|
565
|
-
console.log(serviceStatusJson(state));
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
console.log(`service: ${state.installed ? state.detail : "not installed"}`);
|
|
569
|
-
console.log(await versionLine());
|
|
570
|
-
console.log(await reachabilityLine(argv));
|
|
571
|
-
}
|
|
572
|
-
async function restartService({
|
|
573
|
-
force,
|
|
574
|
-
argv,
|
|
575
|
-
manager = serviceManager(),
|
|
576
|
-
restart,
|
|
577
|
-
connect = connectToHost,
|
|
578
|
-
pollTimeoutMs = 30000,
|
|
579
|
-
pollDelayMs = 500
|
|
580
|
-
}) {
|
|
581
|
-
const state = await manager.state();
|
|
582
|
-
if (!state.installed) {
|
|
583
|
-
console.log(`service not installed (${commandHint("service")} install)`);
|
|
584
|
-
return 1;
|
|
585
|
-
}
|
|
586
|
-
if (state.stopped && restart === undefined) {
|
|
587
|
-
console.log(`service stopped (${commandHint("service")} start)`);
|
|
588
|
-
return 1;
|
|
589
|
-
}
|
|
590
|
-
const address = await hostAddress(argv).catch((error) => {
|
|
591
|
-
if (isParseArgsError(error))
|
|
592
|
-
throw error;
|
|
593
|
-
return;
|
|
594
|
-
});
|
|
595
|
-
if (!force && address !== undefined)
|
|
596
|
-
await waitUntilIdle(address, connect, (line) => console.log(line));
|
|
597
|
-
await (restart?.() ?? manager.restart());
|
|
598
|
-
if (address === undefined) {
|
|
599
|
-
console.log("restart requested (host address unknown; not polling)");
|
|
600
|
-
return 0;
|
|
601
|
-
}
|
|
602
|
-
const deadline = Date.now() + pollTimeoutMs;
|
|
603
|
-
for (;; ) {
|
|
604
|
-
try {
|
|
605
|
-
const remote = await connect(address.url, address.token, 2000);
|
|
606
|
-
remote.close();
|
|
607
|
-
console.log(`host restarted, reachable at ${address.url}`);
|
|
608
|
-
return 0;
|
|
609
|
-
} catch (error) {
|
|
610
|
-
if (error instanceof ProtocolMismatchError && error.direction === "client-older") {
|
|
611
|
-
const newer = error.hostVersion ?? `protocol major ${error.hostMajor}`;
|
|
612
|
-
console.log(`host restarted (${newer}, newer than this upgrader), reachable at ${address.url}`);
|
|
613
|
-
return 0;
|
|
614
|
-
}
|
|
615
|
-
if (Date.now() > deadline) {
|
|
616
|
-
console.error(`host did not answer within ${pollTimeoutMs / 1000}s after restart: ${message(error)}`);
|
|
617
|
-
return 1;
|
|
618
|
-
}
|
|
619
|
-
await new Promise((resolve3) => setTimeout(resolve3, pollDelayMs));
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
async function logs(argv) {
|
|
624
|
-
const { values } = parseArgs({
|
|
625
|
-
args: [...argv],
|
|
626
|
-
options: {
|
|
627
|
-
follow: { type: "boolean", default: false },
|
|
628
|
-
lines: { type: "string", short: "n", default: "100" },
|
|
629
|
-
"data-dir": { type: "string" }
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
const dataDir = resolve2(values["data-dir"] ?? DEFAULT_DATA_DIR);
|
|
633
|
-
await serviceManager().logs(dataDir, Number(values.lines), values.follow);
|
|
634
|
-
}
|
|
635
|
-
async function main(argv) {
|
|
636
|
-
const [command, ...args] = argv;
|
|
637
|
-
try {
|
|
638
|
-
switch (command) {
|
|
639
|
-
case "install":
|
|
640
|
-
await install(args);
|
|
641
|
-
break;
|
|
642
|
-
case "uninstall":
|
|
643
|
-
await serviceManager().uninstall();
|
|
644
|
-
console.log("uninstalled");
|
|
645
|
-
break;
|
|
646
|
-
case "status":
|
|
647
|
-
await status(args);
|
|
648
|
-
break;
|
|
649
|
-
case "restart": {
|
|
650
|
-
const force = args.includes("--force");
|
|
651
|
-
process.exitCode = await restartService({ force, argv: args.filter((arg) => arg !== "--force") });
|
|
652
|
-
break;
|
|
653
|
-
}
|
|
654
|
-
case "stop": {
|
|
655
|
-
const force = args.includes("--force");
|
|
656
|
-
process.exitCode = await stopService({ force, argv: args.filter((arg) => arg !== "--force") });
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
659
|
-
case "start":
|
|
660
|
-
process.exitCode = await startService();
|
|
661
|
-
break;
|
|
662
|
-
case "logs":
|
|
663
|
-
await logs(args);
|
|
664
|
-
break;
|
|
665
|
-
default:
|
|
666
|
-
console.error(`usage: ${commandHint("service")} install|uninstall|status|restart|stop|start|logs`);
|
|
667
|
-
process.exitCode = 1;
|
|
668
|
-
}
|
|
669
|
-
} catch (error) {
|
|
670
|
-
if (isParseArgsError(error))
|
|
671
|
-
throw error;
|
|
672
|
-
console.error(message(error));
|
|
673
|
-
process.exitCode = 1;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
if (false) {}
|
|
677
|
-
|
|
678
|
-
export { LAUNCHER_EXECUTABLE, isLauncher, installSpec, argValue, withoutArg, withoutRelayArgs, serviceManager, stopService, startService, versionLine, reachabilityLine, serviceStatusJson, restartService, main };
|
|
376
|
+
export { LAUNCHER_EXECUTABLE, isLauncher, installSpec, argValue, withoutArg, withoutRelayArgs, serviceManager };
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProtocolMismatchError,
|
|
3
|
+
connectRemoteDurable
|
|
4
|
+
} from "./chunk-e59k3bmz.js";
|
|
5
|
+
import {
|
|
6
|
+
isParseArgsError
|
|
7
|
+
} from "./chunk-kzbzacz9.js";
|
|
8
|
+
import {
|
|
9
|
+
argValue,
|
|
10
|
+
installSpec,
|
|
11
|
+
serviceManager
|
|
12
|
+
} from "./chunk-mxwcnnkg.js";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_DATA_DIR,
|
|
15
|
+
hostAddress
|
|
16
|
+
} from "./chunk-ngf1zatm.js";
|
|
17
|
+
import {
|
|
18
|
+
commandHint,
|
|
19
|
+
hostEntry,
|
|
20
|
+
packagedVersion
|
|
21
|
+
} from "./chunk-ega1wnkg.js";
|
|
22
|
+
|
|
23
|
+
// apps/host/src/cli/service.ts
|
|
24
|
+
import { execFile } from "node:child_process";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { parseArgs, promisify } from "node:util";
|
|
29
|
+
|
|
30
|
+
// apps/host/src/service/idle.ts
|
|
31
|
+
async function waitForIdle(remote, onBusy) {
|
|
32
|
+
if (remote.view.current().tasks === undefined)
|
|
33
|
+
await remote.controller.toggleTasks();
|
|
34
|
+
let announced = false;
|
|
35
|
+
await new Promise((resolve) => {
|
|
36
|
+
const check = () => {
|
|
37
|
+
const view = remote.view.current();
|
|
38
|
+
if (view.connection !== "connected") {
|
|
39
|
+
unsubscribe();
|
|
40
|
+
resolve();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const tasks = view.tasks;
|
|
44
|
+
if (tasks === undefined)
|
|
45
|
+
return;
|
|
46
|
+
if (Object.keys(tasks.tasks).length === 0) {
|
|
47
|
+
unsubscribe();
|
|
48
|
+
resolve();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!announced) {
|
|
52
|
+
announced = true;
|
|
53
|
+
onBusy(tasks);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const unsubscribe = remote.view.subscribe(check);
|
|
57
|
+
check();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// apps/host/src/cli/service.ts
|
|
62
|
+
var run = promisify(execFile);
|
|
63
|
+
var repoRoot = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
64
|
+
var message = (error) => error instanceof Error ? error.message : String(error);
|
|
65
|
+
function connectToHost(url, token, timeoutMs = 3000) {
|
|
66
|
+
const connecting = connectRemoteDurable({ url, token, reconnectDelayMs: { min: 200, max: 2000 } });
|
|
67
|
+
const timedOut = Symbol("timedOut");
|
|
68
|
+
return Promise.race([
|
|
69
|
+
connecting,
|
|
70
|
+
new Promise((resolve2) => {
|
|
71
|
+
const timer = setTimeout(() => resolve2(timedOut), timeoutMs);
|
|
72
|
+
timer.unref();
|
|
73
|
+
})
|
|
74
|
+
]).then((winner) => {
|
|
75
|
+
if (winner !== timedOut)
|
|
76
|
+
return winner;
|
|
77
|
+
connecting.then((remote) => remote.close(), () => {});
|
|
78
|
+
throw new Error("connect timed out");
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function install(argv) {
|
|
82
|
+
const spec = installSpec(argv, {
|
|
83
|
+
execPath: process.execPath,
|
|
84
|
+
env: process.env,
|
|
85
|
+
home: homedir(),
|
|
86
|
+
cwd: process.cwd(),
|
|
87
|
+
mainPath: hostEntry.script,
|
|
88
|
+
entryArgs: hostEntry.args
|
|
89
|
+
});
|
|
90
|
+
const path = await serviceManager().install(spec);
|
|
91
|
+
console.log(`installed ${path}`);
|
|
92
|
+
console.log(`data dir: ${spec.dataDir}`);
|
|
93
|
+
console.log(`browser link: ${commandHint("link")} pairing QR: ${commandHint("pair")}`);
|
|
94
|
+
}
|
|
95
|
+
async function waitUntilIdle(address, connect, log) {
|
|
96
|
+
try {
|
|
97
|
+
const remote = await connect(address.url, address.token);
|
|
98
|
+
await waitForIdle(remote, (tasks) => {
|
|
99
|
+
log("waiting for running tasks to finish (Ctrl-C to cancel)");
|
|
100
|
+
for (const task of Object.values(tasks.tasks)) {
|
|
101
|
+
log(` ${task.kind} in conversation ${task.conversationId}`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
remote.close();
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
async function stopService({
|
|
108
|
+
force,
|
|
109
|
+
argv,
|
|
110
|
+
manager = serviceManager(),
|
|
111
|
+
connect = connectToHost,
|
|
112
|
+
log = (line) => console.log(line),
|
|
113
|
+
logError = (line) => console.error(line)
|
|
114
|
+
}) {
|
|
115
|
+
const state = await manager.state();
|
|
116
|
+
if (!state.installed) {
|
|
117
|
+
log(`service not installed (${commandHint("service")} install)`);
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
if (state.stopped) {
|
|
121
|
+
log("already stopped");
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
if (!force) {
|
|
125
|
+
const spec = argv.length === 0 ? await manager.read() : undefined;
|
|
126
|
+
const port = spec === undefined ? undefined : argValue(spec.hostArgs, "--port");
|
|
127
|
+
const flags = spec === undefined ? argv : ["--data-dir", spec.dataDir, ...port === undefined ? [] : ["--url", `ws://127.0.0.1:${port}`]];
|
|
128
|
+
const address = await hostAddress(flags).catch((error) => {
|
|
129
|
+
if (isParseArgsError(error))
|
|
130
|
+
throw error;
|
|
131
|
+
return;
|
|
132
|
+
});
|
|
133
|
+
if (address !== undefined)
|
|
134
|
+
await waitUntilIdle(address, connect, log);
|
|
135
|
+
}
|
|
136
|
+
const holdInterrupt = () => {};
|
|
137
|
+
process.on("SIGINT", holdInterrupt);
|
|
138
|
+
try {
|
|
139
|
+
await manager.stop();
|
|
140
|
+
} finally {
|
|
141
|
+
process.off("SIGINT", holdInterrupt);
|
|
142
|
+
}
|
|
143
|
+
const after = await manager.state();
|
|
144
|
+
if (!after.stopped) {
|
|
145
|
+
logError(`the host is still running (${after.detail}); run \`${commandHint("service")} stop\` again`);
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
log(`stopped; it stays off, also after a restart, until \`${commandHint("service")} start\``);
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
async function startService({
|
|
152
|
+
manager = serviceManager(),
|
|
153
|
+
log = (line) => console.log(line)
|
|
154
|
+
} = {}) {
|
|
155
|
+
if (!(await manager.state()).installed) {
|
|
156
|
+
log(`service not installed (${commandHint("service")} install)`);
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
await manager.start();
|
|
160
|
+
log("started");
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
async function versionLine() {
|
|
164
|
+
if (packagedVersion !== undefined)
|
|
165
|
+
return `version: ${packagedVersion}`;
|
|
166
|
+
const head = await run("git", ["rev-parse", "--short", "HEAD"], { cwd: repoRoot }).then(({ stdout }) => stdout.trim(), () => "unknown");
|
|
167
|
+
return `checkout: ${head}`;
|
|
168
|
+
}
|
|
169
|
+
async function reachabilityLine(argv) {
|
|
170
|
+
try {
|
|
171
|
+
const { url, token } = await hostAddress(argv);
|
|
172
|
+
const remote = await connectToHost(url, token);
|
|
173
|
+
if (remote.view.current().tasks === undefined)
|
|
174
|
+
await remote.controller.toggleTasks();
|
|
175
|
+
const tasks = remote.view.current().tasks;
|
|
176
|
+
const count = tasks === undefined ? 0 : Object.keys(tasks.tasks).length;
|
|
177
|
+
remote.close();
|
|
178
|
+
return `host: reachable at ${url}, ${count} live task${count === 1 ? "" : "s"}`;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (isParseArgsError(error))
|
|
181
|
+
throw error;
|
|
182
|
+
return `host: unreachable (${message(error)})`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
var serviceStatusJson = (state) => JSON.stringify({
|
|
186
|
+
installed: state.installed,
|
|
187
|
+
stopped: state.stopped,
|
|
188
|
+
running: state.running,
|
|
189
|
+
...state.pid === undefined ? {} : { pid: state.pid },
|
|
190
|
+
detail: state.detail
|
|
191
|
+
});
|
|
192
|
+
async function status(argv) {
|
|
193
|
+
const state = await serviceManager().state();
|
|
194
|
+
if (argv.includes("--json")) {
|
|
195
|
+
console.log(serviceStatusJson(state));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
console.log(`service: ${state.installed ? state.detail : "not installed"}`);
|
|
199
|
+
console.log(await versionLine());
|
|
200
|
+
console.log(await reachabilityLine(argv));
|
|
201
|
+
}
|
|
202
|
+
async function restartService({
|
|
203
|
+
force,
|
|
204
|
+
argv,
|
|
205
|
+
manager = serviceManager(),
|
|
206
|
+
restart,
|
|
207
|
+
connect = connectToHost,
|
|
208
|
+
pollTimeoutMs = 30000,
|
|
209
|
+
pollDelayMs = 500
|
|
210
|
+
}) {
|
|
211
|
+
const state = await manager.state();
|
|
212
|
+
if (!state.installed) {
|
|
213
|
+
console.log(`service not installed (${commandHint("service")} install)`);
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
if (state.stopped && restart === undefined) {
|
|
217
|
+
console.log(`service stopped (${commandHint("service")} start)`);
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
const address = await hostAddress(argv).catch((error) => {
|
|
221
|
+
if (isParseArgsError(error))
|
|
222
|
+
throw error;
|
|
223
|
+
return;
|
|
224
|
+
});
|
|
225
|
+
if (!force && address !== undefined)
|
|
226
|
+
await waitUntilIdle(address, connect, (line) => console.log(line));
|
|
227
|
+
await (restart?.() ?? manager.restart());
|
|
228
|
+
if (address === undefined) {
|
|
229
|
+
console.log("restart requested (host address unknown; not polling)");
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
return awaitRestarted(address, { connect, pollTimeoutMs, pollDelayMs });
|
|
233
|
+
}
|
|
234
|
+
async function awaitRestarted(address, {
|
|
235
|
+
connect = connectToHost,
|
|
236
|
+
pollTimeoutMs = 30000,
|
|
237
|
+
pollDelayMs = 500
|
|
238
|
+
} = {}) {
|
|
239
|
+
const deadline = Date.now() + pollTimeoutMs;
|
|
240
|
+
for (;; ) {
|
|
241
|
+
try {
|
|
242
|
+
const remote = await connect(address.url, address.token, 2000);
|
|
243
|
+
remote.close();
|
|
244
|
+
console.log(`host restarted, reachable at ${address.url}`);
|
|
245
|
+
return 0;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error instanceof ProtocolMismatchError && error.direction === "client-older") {
|
|
248
|
+
const newer = error.hostVersion ?? `protocol major ${error.hostMajor}`;
|
|
249
|
+
console.log(`host restarted (${newer}, newer than this upgrader), reachable at ${address.url}`);
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
if (Date.now() > deadline) {
|
|
253
|
+
console.error(`host did not answer within ${pollTimeoutMs / 1000}s after restart: ${message(error)}`);
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollDelayMs));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function logs(argv) {
|
|
261
|
+
const { values } = parseArgs({
|
|
262
|
+
args: [...argv],
|
|
263
|
+
options: {
|
|
264
|
+
follow: { type: "boolean", default: false },
|
|
265
|
+
lines: { type: "string", short: "n", default: "100" },
|
|
266
|
+
"data-dir": { type: "string" }
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const dataDir = resolve(values["data-dir"] ?? DEFAULT_DATA_DIR);
|
|
270
|
+
await serviceManager().logs(dataDir, Number(values.lines), values.follow);
|
|
271
|
+
}
|
|
272
|
+
async function main(argv) {
|
|
273
|
+
const [command, ...args] = argv;
|
|
274
|
+
try {
|
|
275
|
+
switch (command) {
|
|
276
|
+
case "install":
|
|
277
|
+
await install(args);
|
|
278
|
+
break;
|
|
279
|
+
case "uninstall":
|
|
280
|
+
await serviceManager().uninstall();
|
|
281
|
+
console.log("uninstalled");
|
|
282
|
+
break;
|
|
283
|
+
case "status":
|
|
284
|
+
await status(args);
|
|
285
|
+
break;
|
|
286
|
+
case "restart": {
|
|
287
|
+
const force = args.includes("--force");
|
|
288
|
+
process.exitCode = await restartService({ force, argv: args.filter((arg) => arg !== "--force") });
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
case "stop": {
|
|
292
|
+
const force = args.includes("--force");
|
|
293
|
+
process.exitCode = await stopService({ force, argv: args.filter((arg) => arg !== "--force") });
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case "start":
|
|
297
|
+
process.exitCode = await startService();
|
|
298
|
+
break;
|
|
299
|
+
case "logs":
|
|
300
|
+
await logs(args);
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
console.error(`usage: ${commandHint("service")} install|uninstall|status|restart|stop|start|logs`);
|
|
304
|
+
process.exitCode = 1;
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (isParseArgsError(error))
|
|
308
|
+
throw error;
|
|
309
|
+
console.error(message(error));
|
|
310
|
+
process.exitCode = 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (false) {}
|
|
314
|
+
|
|
315
|
+
export { connectToHost, stopService, startService, versionLine, reachabilityLine, serviceStatusJson, restartService, awaitRestarted, main };
|