@metamask-previews/wallet-cli 0.0.0-preview-b2742a2fc → 0.0.0-preview-a8fd340

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 (64) hide show
  1. package/CHANGELOG.md +1 -2
  2. package/dist/daemon/daemon-client.cjs +140 -0
  3. package/dist/daemon/daemon-client.cjs.map +1 -0
  4. package/dist/daemon/daemon-client.d.cts +79 -0
  5. package/dist/daemon/daemon-client.d.cts.map +1 -0
  6. package/dist/daemon/daemon-client.d.mts +79 -0
  7. package/dist/daemon/daemon-client.d.mts.map +1 -0
  8. package/dist/daemon/daemon-client.mjs +135 -0
  9. package/dist/daemon/daemon-client.mjs.map +1 -0
  10. package/dist/daemon/daemon-spawn.cjs +106 -0
  11. package/dist/daemon/daemon-spawn.cjs.map +1 -0
  12. package/dist/daemon/daemon-spawn.d.cts +28 -0
  13. package/dist/daemon/daemon-spawn.d.cts.map +1 -0
  14. package/dist/daemon/daemon-spawn.d.mts +28 -0
  15. package/dist/daemon/daemon-spawn.d.mts.map +1 -0
  16. package/dist/daemon/daemon-spawn.mjs +102 -0
  17. package/dist/daemon/daemon-spawn.mjs.map +1 -0
  18. package/dist/daemon/prompts.cjs +21 -0
  19. package/dist/daemon/prompts.cjs.map +1 -0
  20. package/dist/daemon/prompts.d.cts +11 -0
  21. package/dist/daemon/prompts.d.cts.map +1 -0
  22. package/dist/daemon/prompts.d.mts +11 -0
  23. package/dist/daemon/prompts.d.mts.map +1 -0
  24. package/dist/daemon/prompts.mjs +17 -0
  25. package/dist/daemon/prompts.mjs.map +1 -0
  26. package/dist/daemon/rpc-socket-server.cjs +271 -0
  27. package/dist/daemon/rpc-socket-server.cjs.map +1 -0
  28. package/dist/daemon/rpc-socket-server.d.cts +44 -0
  29. package/dist/daemon/rpc-socket-server.d.cts.map +1 -0
  30. package/dist/daemon/rpc-socket-server.d.mts +44 -0
  31. package/dist/daemon/rpc-socket-server.d.mts.map +1 -0
  32. package/dist/daemon/rpc-socket-server.mjs +267 -0
  33. package/dist/daemon/rpc-socket-server.mjs.map +1 -0
  34. package/dist/daemon/socket-line.cjs +89 -0
  35. package/dist/daemon/socket-line.cjs.map +1 -0
  36. package/dist/daemon/socket-line.d.cts +19 -0
  37. package/dist/daemon/socket-line.d.cts.map +1 -0
  38. package/dist/daemon/socket-line.d.mts +19 -0
  39. package/dist/daemon/socket-line.d.mts.map +1 -0
  40. package/dist/daemon/socket-line.mjs +84 -0
  41. package/dist/daemon/socket-line.mjs.map +1 -0
  42. package/dist/daemon/stop-daemon.cjs +103 -0
  43. package/dist/daemon/stop-daemon.cjs.map +1 -0
  44. package/dist/daemon/stop-daemon.d.cts +18 -0
  45. package/dist/daemon/stop-daemon.d.cts.map +1 -0
  46. package/dist/daemon/stop-daemon.d.mts +18 -0
  47. package/dist/daemon/stop-daemon.d.mts.map +1 -0
  48. package/dist/daemon/stop-daemon.mjs +99 -0
  49. package/dist/daemon/stop-daemon.mjs.map +1 -0
  50. package/dist/daemon/types.cjs.map +1 -1
  51. package/dist/daemon/types.d.cts +28 -0
  52. package/dist/daemon/types.d.cts.map +1 -1
  53. package/dist/daemon/types.d.mts +28 -0
  54. package/dist/daemon/types.d.mts.map +1 -1
  55. package/dist/daemon/types.mjs.map +1 -1
  56. package/dist/daemon/utils.cjs +109 -0
  57. package/dist/daemon/utils.cjs.map +1 -0
  58. package/dist/daemon/utils.d.cts +50 -0
  59. package/dist/daemon/utils.d.cts.map +1 -0
  60. package/dist/daemon/utils.d.mts +50 -0
  61. package/dist/daemon/utils.d.mts.map +1 -0
  62. package/dist/daemon/utils.mjs +101 -0
  63. package/dist/daemon/utils.mjs.map +1 -0
  64. package/package.json +3 -1
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stopDaemon = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const daemon_client_1 = require("./daemon-client.cjs");
6
+ const utils_1 = require("./utils.cjs");
7
+ /**
8
+ * Stop the daemon via a `shutdown` RPC call. Falls back to PID + SIGTERM if
9
+ * the socket is unresponsive, and escalates to SIGKILL if SIGTERM is ignored.
10
+ *
11
+ * Signals are sent when EITHER the socket was observed (`responsive` or
12
+ * `unreachable`) OR the recorded PID is still alive on its own. The
13
+ * socket-absent + alive-PID branch trades a small risk of signalling a
14
+ * recycled PID for the larger risk of leaving an orphan daemon holding the
15
+ * SQLite database — which `daemon purge` would otherwise wipe out from
16
+ * under it.
17
+ *
18
+ * @param socketPath - The daemon socket path.
19
+ * @param pidPath - The daemon PID file path.
20
+ * @param log - Optional logging function for status messages.
21
+ * @returns True if the daemon was stopped (or was not running).
22
+ */
23
+ async function stopDaemon(socketPath, pidPath, log) {
24
+ const pid = await (0, utils_1.readPidFile)(pidPath);
25
+ const ping = await (0, daemon_client_1.pingDaemon)(socketPath);
26
+ const socketObserved = ping.status === 'responsive' || ping.status === 'unreachable';
27
+ const processAlive = pid !== undefined && (0, utils_1.isProcessAlive)(pid);
28
+ if (!socketObserved && !processAlive) {
29
+ // No live daemon evidence. Just remove the stale PID file if any.
30
+ await cleanupFile(pidPath, 'PID file', log);
31
+ return true;
32
+ }
33
+ log?.('Stopping daemon...');
34
+ let stopped = false;
35
+ // Strategy 1: Graceful socket-based shutdown.
36
+ if (ping.status === 'responsive') {
37
+ try {
38
+ await (0, daemon_client_1.sendCommand)({ socketPath, method: 'shutdown' });
39
+ }
40
+ catch (error) {
41
+ log?.(`Graceful shutdown request failed: ${String(error)}`);
42
+ }
43
+ // A quiet socket does not prove the process exited; require the recorded
44
+ // pid to be gone too, so a daemon that outlived its socket falls through to
45
+ // SIGTERM/SIGKILL rather than being orphaned.
46
+ stopped = await (0, utils_1.waitFor)(async () => (await (0, daemon_client_1.pingDaemon)(socketPath)).status !== 'responsive' &&
47
+ (pid === undefined || !(0, utils_1.isProcessAlive)(pid)), 5000);
48
+ }
49
+ // Strategy 2: SIGTERM. Signal when either the socket was observed or the
50
+ // recorded PID is alive; the absent+alive case typically means someone
51
+ // removed the socket from under a live daemon.
52
+ if (!stopped && processAlive && pid !== undefined) {
53
+ if (!socketObserved) {
54
+ log?.(`Socket at ${socketPath} is absent but recorded pid ${pid} is alive; signalling anyway.`);
55
+ }
56
+ try {
57
+ if ((0, utils_1.sendSignal)(pid, 'SIGTERM')) {
58
+ stopped = await (0, utils_1.waitFor)(() => !(0, utils_1.isProcessAlive)(pid), 5000);
59
+ }
60
+ else {
61
+ stopped = true; // Process already gone (ESRCH).
62
+ }
63
+ }
64
+ catch (error) {
65
+ log?.(`SIGTERM failed: ${String(error)}`);
66
+ }
67
+ }
68
+ // Strategy 3: SIGKILL.
69
+ if (!stopped && processAlive && pid !== undefined) {
70
+ try {
71
+ if ((0, utils_1.sendSignal)(pid, 'SIGKILL')) {
72
+ stopped = await (0, utils_1.waitFor)(() => !(0, utils_1.isProcessAlive)(pid), 2000);
73
+ }
74
+ else {
75
+ stopped = true; // Process already gone (ESRCH).
76
+ }
77
+ }
78
+ catch (error) {
79
+ log?.(`SIGKILL failed: ${String(error)}`);
80
+ }
81
+ }
82
+ if (stopped) {
83
+ await cleanupFile(pidPath, 'PID file', log);
84
+ await cleanupFile(socketPath, 'socket file', log);
85
+ log?.('Daemon stopped.');
86
+ }
87
+ return stopped;
88
+ }
89
+ exports.stopDaemon = stopDaemon;
90
+ /**
91
+ * Remove a file best-effort, logging any failure rather than letting it
92
+ * propagate. ENOENT is silently ignored via `force: true`.
93
+ *
94
+ * @param path - The file path to remove.
95
+ * @param label - Human-readable label for log messages.
96
+ * @param log - Optional log sink.
97
+ */
98
+ async function cleanupFile(path, label, log) {
99
+ await (0, promises_1.rm)(path, { force: true }).catch((error) => {
100
+ log?.(`Failed to remove ${label}: ${String(error)}`);
101
+ });
102
+ }
103
+ //# sourceMappingURL=stop-daemon.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-daemon.cjs","sourceRoot":"","sources":["../../src/daemon/stop-daemon.ts"],"names":[],"mappings":"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","sourcesContent":["import { rm } from 'node:fs/promises';\n\nimport { pingDaemon, sendCommand } from './daemon-client';\nimport { isProcessAlive, readPidFile, sendSignal, waitFor } from './utils';\n\n/**\n * Stop the daemon via a `shutdown` RPC call. Falls back to PID + SIGTERM if\n * the socket is unresponsive, and escalates to SIGKILL if SIGTERM is ignored.\n *\n * Signals are sent when EITHER the socket was observed (`responsive` or\n * `unreachable`) OR the recorded PID is still alive on its own. The\n * socket-absent + alive-PID branch trades a small risk of signalling a\n * recycled PID for the larger risk of leaving an orphan daemon holding the\n * SQLite database — which `daemon purge` would otherwise wipe out from\n * under it.\n *\n * @param socketPath - The daemon socket path.\n * @param pidPath - The daemon PID file path.\n * @param log - Optional logging function for status messages.\n * @returns True if the daemon was stopped (or was not running).\n */\nexport async function stopDaemon(\n socketPath: string,\n pidPath: string,\n log?: (message: string) => void,\n): Promise<boolean> {\n const pid = await readPidFile(pidPath);\n const ping = await pingDaemon(socketPath);\n const socketObserved =\n ping.status === 'responsive' || ping.status === 'unreachable';\n const processAlive = pid !== undefined && isProcessAlive(pid);\n\n if (!socketObserved && !processAlive) {\n // No live daemon evidence. Just remove the stale PID file if any.\n await cleanupFile(pidPath, 'PID file', log);\n return true;\n }\n\n log?.('Stopping daemon...');\n\n let stopped = false;\n\n // Strategy 1: Graceful socket-based shutdown.\n if (ping.status === 'responsive') {\n try {\n await sendCommand({ socketPath, method: 'shutdown' });\n } catch (error) {\n log?.(`Graceful shutdown request failed: ${String(error)}`);\n }\n // A quiet socket does not prove the process exited; require the recorded\n // pid to be gone too, so a daemon that outlived its socket falls through to\n // SIGTERM/SIGKILL rather than being orphaned.\n stopped = await waitFor(\n async () =>\n (await pingDaemon(socketPath)).status !== 'responsive' &&\n (pid === undefined || !isProcessAlive(pid)),\n 5_000,\n );\n }\n\n // Strategy 2: SIGTERM. Signal when either the socket was observed or the\n // recorded PID is alive; the absent+alive case typically means someone\n // removed the socket from under a live daemon.\n if (!stopped && processAlive && pid !== undefined) {\n if (!socketObserved) {\n log?.(\n `Socket at ${socketPath} is absent but recorded pid ${pid} is alive; signalling anyway.`,\n );\n }\n try {\n if (sendSignal(pid, 'SIGTERM')) {\n stopped = await waitFor(() => !isProcessAlive(pid), 5_000);\n } else {\n stopped = true; // Process already gone (ESRCH).\n }\n } catch (error) {\n log?.(`SIGTERM failed: ${String(error)}`);\n }\n }\n\n // Strategy 3: SIGKILL.\n if (!stopped && processAlive && pid !== undefined) {\n try {\n if (sendSignal(pid, 'SIGKILL')) {\n stopped = await waitFor(() => !isProcessAlive(pid), 2_000);\n } else {\n stopped = true; // Process already gone (ESRCH).\n }\n } catch (error) {\n log?.(`SIGKILL failed: ${String(error)}`);\n }\n }\n\n if (stopped) {\n await cleanupFile(pidPath, 'PID file', log);\n await cleanupFile(socketPath, 'socket file', log);\n log?.('Daemon stopped.');\n }\n\n return stopped;\n}\n\n/**\n * Remove a file best-effort, logging any failure rather than letting it\n * propagate. ENOENT is silently ignored via `force: true`.\n *\n * @param path - The file path to remove.\n * @param label - Human-readable label for log messages.\n * @param log - Optional log sink.\n */\nasync function cleanupFile(\n path: string,\n label: string,\n log: ((message: string) => void) | undefined,\n): Promise<void> {\n await rm(path, { force: true }).catch((error: unknown) => {\n log?.(`Failed to remove ${label}: ${String(error)}`);\n });\n}\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stop the daemon via a `shutdown` RPC call. Falls back to PID + SIGTERM if
3
+ * the socket is unresponsive, and escalates to SIGKILL if SIGTERM is ignored.
4
+ *
5
+ * Signals are sent when EITHER the socket was observed (`responsive` or
6
+ * `unreachable`) OR the recorded PID is still alive on its own. The
7
+ * socket-absent + alive-PID branch trades a small risk of signalling a
8
+ * recycled PID for the larger risk of leaving an orphan daemon holding the
9
+ * SQLite database — which `daemon purge` would otherwise wipe out from
10
+ * under it.
11
+ *
12
+ * @param socketPath - The daemon socket path.
13
+ * @param pidPath - The daemon PID file path.
14
+ * @param log - Optional logging function for status messages.
15
+ * @returns True if the daemon was stopped (or was not running).
16
+ */
17
+ export declare function stopDaemon(socketPath: string, pidPath: string, log?: (message: string) => void): Promise<boolean>;
18
+ //# sourceMappingURL=stop-daemon.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-daemon.d.cts","sourceRoot":"","sources":["../../src/daemon/stop-daemon.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,OAAO,CAAC,CA2ElB"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stop the daemon via a `shutdown` RPC call. Falls back to PID + SIGTERM if
3
+ * the socket is unresponsive, and escalates to SIGKILL if SIGTERM is ignored.
4
+ *
5
+ * Signals are sent when EITHER the socket was observed (`responsive` or
6
+ * `unreachable`) OR the recorded PID is still alive on its own. The
7
+ * socket-absent + alive-PID branch trades a small risk of signalling a
8
+ * recycled PID for the larger risk of leaving an orphan daemon holding the
9
+ * SQLite database — which `daemon purge` would otherwise wipe out from
10
+ * under it.
11
+ *
12
+ * @param socketPath - The daemon socket path.
13
+ * @param pidPath - The daemon PID file path.
14
+ * @param log - Optional logging function for status messages.
15
+ * @returns True if the daemon was stopped (or was not running).
16
+ */
17
+ export declare function stopDaemon(socketPath: string, pidPath: string, log?: (message: string) => void): Promise<boolean>;
18
+ //# sourceMappingURL=stop-daemon.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-daemon.d.mts","sourceRoot":"","sources":["../../src/daemon/stop-daemon.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,OAAO,CAAC,CA2ElB"}
@@ -0,0 +1,99 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { pingDaemon, sendCommand } from "./daemon-client.mjs";
3
+ import { isProcessAlive, readPidFile, sendSignal, waitFor } from "./utils.mjs";
4
+ /**
5
+ * Stop the daemon via a `shutdown` RPC call. Falls back to PID + SIGTERM if
6
+ * the socket is unresponsive, and escalates to SIGKILL if SIGTERM is ignored.
7
+ *
8
+ * Signals are sent when EITHER the socket was observed (`responsive` or
9
+ * `unreachable`) OR the recorded PID is still alive on its own. The
10
+ * socket-absent + alive-PID branch trades a small risk of signalling a
11
+ * recycled PID for the larger risk of leaving an orphan daemon holding the
12
+ * SQLite database — which `daemon purge` would otherwise wipe out from
13
+ * under it.
14
+ *
15
+ * @param socketPath - The daemon socket path.
16
+ * @param pidPath - The daemon PID file path.
17
+ * @param log - Optional logging function for status messages.
18
+ * @returns True if the daemon was stopped (or was not running).
19
+ */
20
+ export async function stopDaemon(socketPath, pidPath, log) {
21
+ const pid = await readPidFile(pidPath);
22
+ const ping = await pingDaemon(socketPath);
23
+ const socketObserved = ping.status === 'responsive' || ping.status === 'unreachable';
24
+ const processAlive = pid !== undefined && isProcessAlive(pid);
25
+ if (!socketObserved && !processAlive) {
26
+ // No live daemon evidence. Just remove the stale PID file if any.
27
+ await cleanupFile(pidPath, 'PID file', log);
28
+ return true;
29
+ }
30
+ log?.('Stopping daemon...');
31
+ let stopped = false;
32
+ // Strategy 1: Graceful socket-based shutdown.
33
+ if (ping.status === 'responsive') {
34
+ try {
35
+ await sendCommand({ socketPath, method: 'shutdown' });
36
+ }
37
+ catch (error) {
38
+ log?.(`Graceful shutdown request failed: ${String(error)}`);
39
+ }
40
+ // A quiet socket does not prove the process exited; require the recorded
41
+ // pid to be gone too, so a daemon that outlived its socket falls through to
42
+ // SIGTERM/SIGKILL rather than being orphaned.
43
+ stopped = await waitFor(async () => (await pingDaemon(socketPath)).status !== 'responsive' &&
44
+ (pid === undefined || !isProcessAlive(pid)), 5000);
45
+ }
46
+ // Strategy 2: SIGTERM. Signal when either the socket was observed or the
47
+ // recorded PID is alive; the absent+alive case typically means someone
48
+ // removed the socket from under a live daemon.
49
+ if (!stopped && processAlive && pid !== undefined) {
50
+ if (!socketObserved) {
51
+ log?.(`Socket at ${socketPath} is absent but recorded pid ${pid} is alive; signalling anyway.`);
52
+ }
53
+ try {
54
+ if (sendSignal(pid, 'SIGTERM')) {
55
+ stopped = await waitFor(() => !isProcessAlive(pid), 5000);
56
+ }
57
+ else {
58
+ stopped = true; // Process already gone (ESRCH).
59
+ }
60
+ }
61
+ catch (error) {
62
+ log?.(`SIGTERM failed: ${String(error)}`);
63
+ }
64
+ }
65
+ // Strategy 3: SIGKILL.
66
+ if (!stopped && processAlive && pid !== undefined) {
67
+ try {
68
+ if (sendSignal(pid, 'SIGKILL')) {
69
+ stopped = await waitFor(() => !isProcessAlive(pid), 2000);
70
+ }
71
+ else {
72
+ stopped = true; // Process already gone (ESRCH).
73
+ }
74
+ }
75
+ catch (error) {
76
+ log?.(`SIGKILL failed: ${String(error)}`);
77
+ }
78
+ }
79
+ if (stopped) {
80
+ await cleanupFile(pidPath, 'PID file', log);
81
+ await cleanupFile(socketPath, 'socket file', log);
82
+ log?.('Daemon stopped.');
83
+ }
84
+ return stopped;
85
+ }
86
+ /**
87
+ * Remove a file best-effort, logging any failure rather than letting it
88
+ * propagate. ENOENT is silently ignored via `force: true`.
89
+ *
90
+ * @param path - The file path to remove.
91
+ * @param label - Human-readable label for log messages.
92
+ * @param log - Optional log sink.
93
+ */
94
+ async function cleanupFile(path, label, log) {
95
+ await rm(path, { force: true }).catch((error) => {
96
+ log?.(`Failed to remove ${label}: ${String(error)}`);
97
+ });
98
+ }
99
+ //# sourceMappingURL=stop-daemon.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-daemon.mjs","sourceRoot":"","sources":["../../src/daemon/stop-daemon.ts"],"names":[],"mappings":"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","sourcesContent":["import { rm } from 'node:fs/promises';\n\nimport { pingDaemon, sendCommand } from './daemon-client';\nimport { isProcessAlive, readPidFile, sendSignal, waitFor } from './utils';\n\n/**\n * Stop the daemon via a `shutdown` RPC call. Falls back to PID + SIGTERM if\n * the socket is unresponsive, and escalates to SIGKILL if SIGTERM is ignored.\n *\n * Signals are sent when EITHER the socket was observed (`responsive` or\n * `unreachable`) OR the recorded PID is still alive on its own. The\n * socket-absent + alive-PID branch trades a small risk of signalling a\n * recycled PID for the larger risk of leaving an orphan daemon holding the\n * SQLite database — which `daemon purge` would otherwise wipe out from\n * under it.\n *\n * @param socketPath - The daemon socket path.\n * @param pidPath - The daemon PID file path.\n * @param log - Optional logging function for status messages.\n * @returns True if the daemon was stopped (or was not running).\n */\nexport async function stopDaemon(\n socketPath: string,\n pidPath: string,\n log?: (message: string) => void,\n): Promise<boolean> {\n const pid = await readPidFile(pidPath);\n const ping = await pingDaemon(socketPath);\n const socketObserved =\n ping.status === 'responsive' || ping.status === 'unreachable';\n const processAlive = pid !== undefined && isProcessAlive(pid);\n\n if (!socketObserved && !processAlive) {\n // No live daemon evidence. Just remove the stale PID file if any.\n await cleanupFile(pidPath, 'PID file', log);\n return true;\n }\n\n log?.('Stopping daemon...');\n\n let stopped = false;\n\n // Strategy 1: Graceful socket-based shutdown.\n if (ping.status === 'responsive') {\n try {\n await sendCommand({ socketPath, method: 'shutdown' });\n } catch (error) {\n log?.(`Graceful shutdown request failed: ${String(error)}`);\n }\n // A quiet socket does not prove the process exited; require the recorded\n // pid to be gone too, so a daemon that outlived its socket falls through to\n // SIGTERM/SIGKILL rather than being orphaned.\n stopped = await waitFor(\n async () =>\n (await pingDaemon(socketPath)).status !== 'responsive' &&\n (pid === undefined || !isProcessAlive(pid)),\n 5_000,\n );\n }\n\n // Strategy 2: SIGTERM. Signal when either the socket was observed or the\n // recorded PID is alive; the absent+alive case typically means someone\n // removed the socket from under a live daemon.\n if (!stopped && processAlive && pid !== undefined) {\n if (!socketObserved) {\n log?.(\n `Socket at ${socketPath} is absent but recorded pid ${pid} is alive; signalling anyway.`,\n );\n }\n try {\n if (sendSignal(pid, 'SIGTERM')) {\n stopped = await waitFor(() => !isProcessAlive(pid), 5_000);\n } else {\n stopped = true; // Process already gone (ESRCH).\n }\n } catch (error) {\n log?.(`SIGTERM failed: ${String(error)}`);\n }\n }\n\n // Strategy 3: SIGKILL.\n if (!stopped && processAlive && pid !== undefined) {\n try {\n if (sendSignal(pid, 'SIGKILL')) {\n stopped = await waitFor(() => !isProcessAlive(pid), 2_000);\n } else {\n stopped = true; // Process already gone (ESRCH).\n }\n } catch (error) {\n log?.(`SIGKILL failed: ${String(error)}`);\n }\n }\n\n if (stopped) {\n await cleanupFile(pidPath, 'PID file', log);\n await cleanupFile(socketPath, 'socket file', log);\n log?.('Daemon stopped.');\n }\n\n return stopped;\n}\n\n/**\n * Remove a file best-effort, logging any failure rather than letting it\n * propagate. ENOENT is silently ignored via `force: true`.\n *\n * @param path - The file path to remove.\n * @param label - Human-readable label for log messages.\n * @param log - Optional log sink.\n */\nasync function cleanupFile(\n path: string,\n label: string,\n log: ((message: string) => void) | undefined,\n): Promise<void> {\n await rm(path, { force: true }).catch((error: unknown) => {\n log?.(`Failed to remove ${label}: ${String(error)}`);\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n"]}
1
+ {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Json } from '@metamask/utils';\n\n/**\n * A function that handles a JSON-RPC method call.\n *\n * The `params` argument will be `null` if the client did not provide params.\n */\nexport type RpcHandler = (params: Json) => Promise<Json | void>;\n\n/**\n * A map of RPC method names to their handler functions.\n */\nexport type RpcHandlerMap = Record<string, RpcHandler>;\n\n/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n\n/**\n * Status information returned by the daemon's `getStatus` RPC method.\n */\nexport type DaemonStatusInfo = {\n pid: number;\n uptime: number;\n};\n\n/**\n * Configuration passed to the daemon spawner.\n */\nexport type DaemonSpawnConfig = {\n dataDir: string;\n infuraProjectId: string;\n password: string;\n srp: string;\n packageRoot: string;\n};\n"]}
@@ -1,3 +1,14 @@
1
+ import type { Json } from "@metamask/utils";
2
+ /**
3
+ * A function that handles a JSON-RPC method call.
4
+ *
5
+ * The `params` argument will be `null` if the client did not provide params.
6
+ */
7
+ export type RpcHandler = (params: Json) => Promise<Json | void>;
8
+ /**
9
+ * A map of RPC method names to their handler functions.
10
+ */
11
+ export type RpcHandlerMap = Record<string, RpcHandler>;
1
12
  /**
2
13
  * Resolved paths for daemon state files.
3
14
  */
@@ -7,4 +18,21 @@ export type DaemonPaths = {
7
18
  logPath: string;
8
19
  dbPath: string;
9
20
  };
21
+ /**
22
+ * Status information returned by the daemon's `getStatus` RPC method.
23
+ */
24
+ export type DaemonStatusInfo = {
25
+ pid: number;
26
+ uptime: number;
27
+ };
28
+ /**
29
+ * Configuration passed to the daemon spawner.
30
+ */
31
+ export type DaemonSpawnConfig = {
32
+ dataDir: string;
33
+ infuraProjectId: string;
34
+ password: string;
35
+ srp: string;
36
+ packageRoot: string;
37
+ };
10
38
  //# sourceMappingURL=types.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
1
+ {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAE5C;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC"}
@@ -1,3 +1,14 @@
1
+ import type { Json } from "@metamask/utils";
2
+ /**
3
+ * A function that handles a JSON-RPC method call.
4
+ *
5
+ * The `params` argument will be `null` if the client did not provide params.
6
+ */
7
+ export type RpcHandler = (params: Json) => Promise<Json | void>;
8
+ /**
9
+ * A map of RPC method names to their handler functions.
10
+ */
11
+ export type RpcHandlerMap = Record<string, RpcHandler>;
1
12
  /**
2
13
  * Resolved paths for daemon state files.
3
14
  */
@@ -7,4 +18,21 @@ export type DaemonPaths = {
7
18
  logPath: string;
8
19
  dbPath: string;
9
20
  };
21
+ /**
22
+ * Status information returned by the daemon's `getStatus` RPC method.
23
+ */
24
+ export type DaemonStatusInfo = {
25
+ pid: number;
26
+ uptime: number;
27
+ };
28
+ /**
29
+ * Configuration passed to the daemon spawner.
30
+ */
31
+ export type DaemonSpawnConfig = {
32
+ dataDir: string;
33
+ infuraProjectId: string;
34
+ password: string;
35
+ srp: string;
36
+ packageRoot: string;
37
+ };
10
38
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAE5C;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n"]}
1
+ {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Json } from '@metamask/utils';\n\n/**\n * A function that handles a JSON-RPC method call.\n *\n * The `params` argument will be `null` if the client did not provide params.\n */\nexport type RpcHandler = (params: Json) => Promise<Json | void>;\n\n/**\n * A map of RPC method names to their handler functions.\n */\nexport type RpcHandlerMap = Record<string, RpcHandler>;\n\n/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n\n/**\n * Status information returned by the daemon's `getStatus` RPC method.\n */\nexport type DaemonStatusInfo = {\n pid: number;\n uptime: number;\n};\n\n/**\n * Configuration passed to the daemon spawner.\n */\nexport type DaemonSpawnConfig = {\n dataDir: string;\n infuraProjectId: string;\n password: string;\n srp: string;\n packageRoot: string;\n};\n"]}
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.waitFor = exports.sendSignal = exports.isProcessAlive = exports.readPidFile = exports.isErrorWithCode = void 0;
4
+ const utils_1 = require("@metamask/utils");
5
+ const promises_1 = require("node:fs/promises");
6
+ /**
7
+ * Check whether an unknown error is a Node.js system error with the given code.
8
+ *
9
+ * @param error - The error to check.
10
+ * @param code - The expected error code (e.g. 'ENOENT', 'EPERM').
11
+ * @returns True if the error matches the code.
12
+ */
13
+ function isErrorWithCode(error, code) {
14
+ return (0, utils_1.isErrorWithCode)(error) && error.code === code;
15
+ }
16
+ exports.isErrorWithCode = isErrorWithCode;
17
+ /**
18
+ * Read a PID from a file. The file may contain just the PID, or the PID on
19
+ * the first line followed by additional metadata (e.g. start time written by
20
+ * the daemon).
21
+ *
22
+ * @param pidPath - The PID file path.
23
+ * @returns The PID, or undefined if the file is missing or its first line is
24
+ * not a positive integer.
25
+ */
26
+ async function readPidFile(pidPath) {
27
+ let contents;
28
+ try {
29
+ contents = await (0, promises_1.readFile)(pidPath, 'utf-8');
30
+ }
31
+ catch (error) {
32
+ if (isErrorWithCode(error, 'ENOENT')) {
33
+ return undefined;
34
+ }
35
+ throw error;
36
+ }
37
+ // String.prototype.split always returns at least one element, so [0] is safe.
38
+ const pid = Number(contents.split('\n')[0].trim());
39
+ return Number.isInteger(pid) && pid > 0 ? pid : undefined;
40
+ }
41
+ exports.readPidFile = readPidFile;
42
+ /**
43
+ * Check whether a process is alive by sending signal 0.
44
+ *
45
+ * Treats `ESRCH` as "process is gone", `EPERM` as "process exists but we
46
+ * cannot signal it" (still alive from our perspective), and rethrows
47
+ * anything else so the caller can surface unexpected failures rather than
48
+ * silently assuming the process is dead.
49
+ *
50
+ * @param pid - The process ID to check.
51
+ * @returns True if the process exists.
52
+ */
53
+ function isProcessAlive(pid) {
54
+ try {
55
+ process.kill(pid, 0);
56
+ return true;
57
+ }
58
+ catch (error) {
59
+ if (isErrorWithCode(error, 'ESRCH')) {
60
+ return false;
61
+ }
62
+ if (isErrorWithCode(error, 'EPERM')) {
63
+ return true;
64
+ }
65
+ throw error;
66
+ }
67
+ }
68
+ exports.isProcessAlive = isProcessAlive;
69
+ /**
70
+ * Send a signal to a process. Returns true if the signal was sent, false if
71
+ * the process does not exist (ESRCH). Re-throws on permission errors and
72
+ * other failures.
73
+ *
74
+ * @param pid - The process ID.
75
+ * @param signal - The signal to send.
76
+ * @returns True if the signal was delivered, false if the process is gone.
77
+ */
78
+ function sendSignal(pid, signal) {
79
+ try {
80
+ process.kill(pid, signal);
81
+ return true;
82
+ }
83
+ catch (error) {
84
+ if (isErrorWithCode(error, 'ESRCH')) {
85
+ return false;
86
+ }
87
+ throw error;
88
+ }
89
+ }
90
+ exports.sendSignal = sendSignal;
91
+ /**
92
+ * Poll until a condition is met or the timeout elapses.
93
+ *
94
+ * @param check - A function that returns true when the condition is met.
95
+ * @param timeoutMs - Maximum time to wait in milliseconds.
96
+ * @returns True if the condition was met, false on timeout.
97
+ */
98
+ async function waitFor(check, timeoutMs) {
99
+ const deadline = Date.now() + timeoutMs;
100
+ while (Date.now() < deadline) {
101
+ if (await check()) {
102
+ return true;
103
+ }
104
+ await new Promise((resolve) => setTimeout(resolve, 250));
105
+ }
106
+ return false;
107
+ }
108
+ exports.waitFor = waitFor;
109
+ //# sourceMappingURL=utils.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.cjs","sourceRoot":"","sources":["../../src/daemon/utils.ts"],"names":[],"mappings":";;;AAAA,2CAAkE;AAClE,+CAA4C;AAE5C;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,KAAc,EAAE,IAAY;IAC1D,OAAO,IAAA,uBAAY,EAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AACpD,CAAC;AAFD,0CAEC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,WAAW,CAC/B,OAAe;IAEf,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YACrC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,8EAA8E;IAC9E,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC;AAfD,kCAeC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAbD,wCAaC;AAED;;;;;;;;GAQG;AACH,SAAgB,UAAU,CAAC,GAAW,EAAE,MAAsB;IAC5D,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAVD,gCAUC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,OAAO,CAC3B,KAAuC,EACvC,SAAiB;IAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAZD,0BAYC","sourcesContent":["import { isErrorWithCode as hasErrorCode } from '@metamask/utils';\nimport { readFile } from 'node:fs/promises';\n\n/**\n * Check whether an unknown error is a Node.js system error with the given code.\n *\n * @param error - The error to check.\n * @param code - The expected error code (e.g. 'ENOENT', 'EPERM').\n * @returns True if the error matches the code.\n */\nexport function isErrorWithCode(error: unknown, code: string): boolean {\n return hasErrorCode(error) && error.code === code;\n}\n\n/**\n * Read a PID from a file. The file may contain just the PID, or the PID on\n * the first line followed by additional metadata (e.g. start time written by\n * the daemon).\n *\n * @param pidPath - The PID file path.\n * @returns The PID, or undefined if the file is missing or its first line is\n * not a positive integer.\n */\nexport async function readPidFile(\n pidPath: string,\n): Promise<number | undefined> {\n let contents: string;\n try {\n contents = await readFile(pidPath, 'utf-8');\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ENOENT')) {\n return undefined;\n }\n throw error;\n }\n // String.prototype.split always returns at least one element, so [0] is safe.\n const pid = Number(contents.split('\\n')[0].trim());\n return Number.isInteger(pid) && pid > 0 ? pid : undefined;\n}\n\n/**\n * Check whether a process is alive by sending signal 0.\n *\n * Treats `ESRCH` as \"process is gone\", `EPERM` as \"process exists but we\n * cannot signal it\" (still alive from our perspective), and rethrows\n * anything else so the caller can surface unexpected failures rather than\n * silently assuming the process is dead.\n *\n * @param pid - The process ID to check.\n * @returns True if the process exists.\n */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ESRCH')) {\n return false;\n }\n if (isErrorWithCode(error, 'EPERM')) {\n return true;\n }\n throw error;\n }\n}\n\n/**\n * Send a signal to a process. Returns true if the signal was sent, false if\n * the process does not exist (ESRCH). Re-throws on permission errors and\n * other failures.\n *\n * @param pid - The process ID.\n * @param signal - The signal to send.\n * @returns True if the signal was delivered, false if the process is gone.\n */\nexport function sendSignal(pid: number, signal: NodeJS.Signals): boolean {\n try {\n process.kill(pid, signal);\n return true;\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ESRCH')) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Poll until a condition is met or the timeout elapses.\n *\n * @param check - A function that returns true when the condition is met.\n * @param timeoutMs - Maximum time to wait in milliseconds.\n * @returns True if the condition was met, false on timeout.\n */\nexport async function waitFor(\n check: () => boolean | Promise<boolean>,\n timeoutMs: number,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await check()) {\n return true;\n }\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n return false;\n}\n"]}
@@ -0,0 +1,50 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * Check whether an unknown error is a Node.js system error with the given code.
4
+ *
5
+ * @param error - The error to check.
6
+ * @param code - The expected error code (e.g. 'ENOENT', 'EPERM').
7
+ * @returns True if the error matches the code.
8
+ */
9
+ export declare function isErrorWithCode(error: unknown, code: string): boolean;
10
+ /**
11
+ * Read a PID from a file. The file may contain just the PID, or the PID on
12
+ * the first line followed by additional metadata (e.g. start time written by
13
+ * the daemon).
14
+ *
15
+ * @param pidPath - The PID file path.
16
+ * @returns The PID, or undefined if the file is missing or its first line is
17
+ * not a positive integer.
18
+ */
19
+ export declare function readPidFile(pidPath: string): Promise<number | undefined>;
20
+ /**
21
+ * Check whether a process is alive by sending signal 0.
22
+ *
23
+ * Treats `ESRCH` as "process is gone", `EPERM` as "process exists but we
24
+ * cannot signal it" (still alive from our perspective), and rethrows
25
+ * anything else so the caller can surface unexpected failures rather than
26
+ * silently assuming the process is dead.
27
+ *
28
+ * @param pid - The process ID to check.
29
+ * @returns True if the process exists.
30
+ */
31
+ export declare function isProcessAlive(pid: number): boolean;
32
+ /**
33
+ * Send a signal to a process. Returns true if the signal was sent, false if
34
+ * the process does not exist (ESRCH). Re-throws on permission errors and
35
+ * other failures.
36
+ *
37
+ * @param pid - The process ID.
38
+ * @param signal - The signal to send.
39
+ * @returns True if the signal was delivered, false if the process is gone.
40
+ */
41
+ export declare function sendSignal(pid: number, signal: NodeJS.Signals): boolean;
42
+ /**
43
+ * Poll until a condition is met or the timeout elapses.
44
+ *
45
+ * @param check - A function that returns true when the condition is met.
46
+ * @param timeoutMs - Maximum time to wait in milliseconds.
47
+ * @returns True if the condition was met, false on timeout.
48
+ */
49
+ export declare function waitFor(check: () => boolean | Promise<boolean>, timeoutMs: number): Promise<boolean>;
50
+ //# sourceMappingURL=utils.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.cts","sourceRoot":"","sources":["../../src/daemon/utils.ts"],"names":[],"mappings":";AAGA;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAanD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAUvE;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EACvC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CASlB"}
@@ -0,0 +1,50 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * Check whether an unknown error is a Node.js system error with the given code.
4
+ *
5
+ * @param error - The error to check.
6
+ * @param code - The expected error code (e.g. 'ENOENT', 'EPERM').
7
+ * @returns True if the error matches the code.
8
+ */
9
+ export declare function isErrorWithCode(error: unknown, code: string): boolean;
10
+ /**
11
+ * Read a PID from a file. The file may contain just the PID, or the PID on
12
+ * the first line followed by additional metadata (e.g. start time written by
13
+ * the daemon).
14
+ *
15
+ * @param pidPath - The PID file path.
16
+ * @returns The PID, or undefined if the file is missing or its first line is
17
+ * not a positive integer.
18
+ */
19
+ export declare function readPidFile(pidPath: string): Promise<number | undefined>;
20
+ /**
21
+ * Check whether a process is alive by sending signal 0.
22
+ *
23
+ * Treats `ESRCH` as "process is gone", `EPERM` as "process exists but we
24
+ * cannot signal it" (still alive from our perspective), and rethrows
25
+ * anything else so the caller can surface unexpected failures rather than
26
+ * silently assuming the process is dead.
27
+ *
28
+ * @param pid - The process ID to check.
29
+ * @returns True if the process exists.
30
+ */
31
+ export declare function isProcessAlive(pid: number): boolean;
32
+ /**
33
+ * Send a signal to a process. Returns true if the signal was sent, false if
34
+ * the process does not exist (ESRCH). Re-throws on permission errors and
35
+ * other failures.
36
+ *
37
+ * @param pid - The process ID.
38
+ * @param signal - The signal to send.
39
+ * @returns True if the signal was delivered, false if the process is gone.
40
+ */
41
+ export declare function sendSignal(pid: number, signal: NodeJS.Signals): boolean;
42
+ /**
43
+ * Poll until a condition is met or the timeout elapses.
44
+ *
45
+ * @param check - A function that returns true when the condition is met.
46
+ * @param timeoutMs - Maximum time to wait in milliseconds.
47
+ * @returns True if the condition was met, false on timeout.
48
+ */
49
+ export declare function waitFor(check: () => boolean | Promise<boolean>, timeoutMs: number): Promise<boolean>;
50
+ //# sourceMappingURL=utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.mts","sourceRoot":"","sources":["../../src/daemon/utils.ts"],"names":[],"mappings":";AAGA;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAanD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAUvE;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EACvC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CASlB"}