@metamask-previews/wallet-cli 0.0.0-preview-9de9aa4 → 0.0.0-preview-b2742a2fc
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/CHANGELOG.md +2 -1
- package/dist/daemon/types.cjs.map +1 -1
- package/dist/daemon/types.d.cts +0 -28
- package/dist/daemon/types.d.cts.map +1 -1
- package/dist/daemon/types.d.mts +0 -28
- package/dist/daemon/types.d.mts.map +1 -1
- package/dist/daemon/types.mjs.map +1 -1
- package/package.json +1 -3
- package/dist/daemon/daemon-client.cjs +0 -140
- package/dist/daemon/daemon-client.cjs.map +0 -1
- package/dist/daemon/daemon-client.d.cts +0 -79
- package/dist/daemon/daemon-client.d.cts.map +0 -1
- package/dist/daemon/daemon-client.d.mts +0 -79
- package/dist/daemon/daemon-client.d.mts.map +0 -1
- package/dist/daemon/daemon-client.mjs +0 -135
- package/dist/daemon/daemon-client.mjs.map +0 -1
- package/dist/daemon/daemon-spawn.cjs +0 -106
- package/dist/daemon/daemon-spawn.cjs.map +0 -1
- package/dist/daemon/daemon-spawn.d.cts +0 -28
- package/dist/daemon/daemon-spawn.d.cts.map +0 -1
- package/dist/daemon/daemon-spawn.d.mts +0 -28
- package/dist/daemon/daemon-spawn.d.mts.map +0 -1
- package/dist/daemon/daemon-spawn.mjs +0 -102
- package/dist/daemon/daemon-spawn.mjs.map +0 -1
- package/dist/daemon/prompts.cjs +0 -21
- package/dist/daemon/prompts.cjs.map +0 -1
- package/dist/daemon/prompts.d.cts +0 -11
- package/dist/daemon/prompts.d.cts.map +0 -1
- package/dist/daemon/prompts.d.mts +0 -11
- package/dist/daemon/prompts.d.mts.map +0 -1
- package/dist/daemon/prompts.mjs +0 -17
- package/dist/daemon/prompts.mjs.map +0 -1
- package/dist/daemon/rpc-socket-server.cjs +0 -271
- package/dist/daemon/rpc-socket-server.cjs.map +0 -1
- package/dist/daemon/rpc-socket-server.d.cts +0 -44
- package/dist/daemon/rpc-socket-server.d.cts.map +0 -1
- package/dist/daemon/rpc-socket-server.d.mts +0 -44
- package/dist/daemon/rpc-socket-server.d.mts.map +0 -1
- package/dist/daemon/rpc-socket-server.mjs +0 -267
- package/dist/daemon/rpc-socket-server.mjs.map +0 -1
- package/dist/daemon/socket-line.cjs +0 -89
- package/dist/daemon/socket-line.cjs.map +0 -1
- package/dist/daemon/socket-line.d.cts +0 -19
- package/dist/daemon/socket-line.d.cts.map +0 -1
- package/dist/daemon/socket-line.d.mts +0 -19
- package/dist/daemon/socket-line.d.mts.map +0 -1
- package/dist/daemon/socket-line.mjs +0 -84
- package/dist/daemon/socket-line.mjs.map +0 -1
- package/dist/daemon/stop-daemon.cjs +0 -103
- package/dist/daemon/stop-daemon.cjs.map +0 -1
- package/dist/daemon/stop-daemon.d.cts +0 -18
- package/dist/daemon/stop-daemon.d.cts.map +0 -1
- package/dist/daemon/stop-daemon.d.mts +0 -18
- package/dist/daemon/stop-daemon.d.mts.map +0 -1
- package/dist/daemon/stop-daemon.mjs +0 -99
- package/dist/daemon/stop-daemon.mjs.map +0 -1
- package/dist/daemon/utils.cjs +0 -109
- package/dist/daemon/utils.cjs.map +0 -1
- package/dist/daemon/utils.d.cts +0 -50
- package/dist/daemon/utils.d.cts.map +0 -1
- package/dist/daemon/utils.d.mts +0 -50
- package/dist/daemon/utils.d.mts.map +0 -1
- package/dist/daemon/utils.mjs +0 -101
- package/dist/daemon/utils.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-socket-server.mjs","sourceRoot":"","sources":["../../src/daemon/rpc-socket-server.ts"],"names":[],"mappings":"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","sourcesContent":["import { rpcErrors } from '@metamask/rpc-errors';\nimport type {\n JsonRpcId,\n JsonRpcParams,\n JsonRpcResponse,\n} from '@metamask/utils';\nimport { hasProperty, isJsonRpcRequest } from '@metamask/utils';\nimport { chmod, unlink } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport type { Server } from 'node:net';\n\nimport type { RpcHandlerMap } from './types';\nimport { isErrorWithCode } from './utils';\n\nconst CONNECTION_TIMEOUT_MS = 30_000;\n\n/**\n * Handle returned by {@link startRpcSocketServer}.\n */\nexport type RpcSocketServerHandle = {\n close: () => Promise<void>;\n};\n\n/**\n * Options for {@link startRpcSocketServer}.\n */\nexport type StartRpcSocketServerOptions = {\n /** The Unix socket path to listen on. */\n socketPath: string;\n /** Map of RPC method names to handler functions. */\n handlers: RpcHandlerMap;\n /** Callback invoked when a `shutdown` RPC is received. */\n onShutdown?: (() => Promise<void>) | undefined;\n /**\n * Optional logger for server-side diagnostics (unexpected socket errors,\n * unhandled handler rejections, `onShutdown` callback failures). Without\n * this, failures fall back to `process.stderr.write`, which is discarded\n * when the daemon is spawned with `stdio: 'ignore'`.\n */\n log?: ((message: string) => void) | undefined;\n};\n\n/**\n * Start a Unix socket server that processes JSON-RPC requests.\n *\n * Each connection reads one newline-delimited JSON-RPC request, processes it\n * via the provided handler map, writes a JSON-RPC response, and closes.\n *\n * The special `shutdown` method is intercepted before handler dispatch and\n * triggers the provided {@link StartRpcSocketServerOptions.onShutdown} callback\n * after responding.\n *\n * @param options - Server options.\n * @param options.socketPath - The Unix socket path to listen on.\n * @param options.handlers - Map of RPC method names to handler functions.\n * @param options.onShutdown - Optional callback invoked when a `shutdown` RPC is received.\n * @param options.log - Optional logger for server-side diagnostics.\n * @returns A handle with a `close()` function for cleanup.\n */\nexport async function startRpcSocketServer({\n socketPath,\n handlers,\n onShutdown,\n log,\n}: StartRpcSocketServerOptions): Promise<RpcSocketServerHandle> {\n const logFn = log ?? defaultLog;\n\n const server = createServer((socket) => {\n let buffer = '';\n\n // Destroy connections that never send a complete request line. `unref` so\n // the timer alone cannot keep the event loop alive at shutdown.\n const timer = setTimeout(() => {\n socket.destroy();\n }, CONNECTION_TIMEOUT_MS);\n timer.unref();\n\n /**\n * Clear the idle-connection timer. Called from data, close, and error\n * paths so the timer never outlives the connection itself.\n */\n const clearIdleTimer = (): void => {\n clearTimeout(timer);\n };\n\n const onData = (data: Buffer): void => {\n buffer += data.toString();\n const idx = buffer.indexOf('\\n');\n if (idx === -1) {\n return;\n }\n\n clearIdleTimer();\n\n // One request per connection.\n socket.removeListener('data', onData);\n\n const line = buffer.slice(0, idx);\n const remaining = buffer.slice(idx + 1);\n buffer = '';\n\n if (remaining.length > 0) {\n socket.end(\n `${JSON.stringify({\n jsonrpc: '2.0',\n error: rpcErrors\n .invalidRequest({\n message: 'Only one request per connection is allowed',\n })\n .serialize(),\n })}\\n`,\n );\n return;\n }\n\n handleRequest(handlers, line, onShutdown, logFn)\n .then((response) => {\n socket.end(`${JSON.stringify(response)}\\n`);\n return undefined;\n })\n .catch((dispatchError: unknown) => {\n logFn(`Unhandled RPC dispatch error: ${String(dispatchError)}`);\n socket.end(\n `${JSON.stringify({\n jsonrpc: '2.0',\n error: rpcErrors\n .internal({ message: 'Internal error' })\n .serialize(),\n })}\\n`,\n );\n });\n };\n socket.on('data', onData);\n socket.once('close', clearIdleTimer);\n socket.on('error', (socketError: NodeJS.ErrnoException) => {\n clearIdleTimer();\n const { code } = socketError;\n if (code === 'EPIPE' || code === 'ECONNRESET') {\n return; // Expected during probe/disconnect.\n }\n logFn(`Unexpected socket error: ${String(socketError)}`);\n });\n });\n\n await listen(server, socketPath);\n\n return {\n close: async (): Promise<void> => closeServer(server),\n };\n}\n\n/**\n * Default fallback logger: writes to stderr. Daemons spawned with\n * `stdio: 'ignore'` should always pass an explicit `log`.\n *\n * @param message - The message to log.\n */\nfunction defaultLog(message: string): void {\n process.stderr.write(`${message}\\n`);\n}\n\n/**\n * Handle a single JSON-RPC request line, intercepting the `shutdown` method.\n *\n * @param handlers - The RPC handler map.\n * @param line - The raw JSON line from the socket.\n * @param onShutdown - Optional shutdown callback.\n * @param log - Logger for diagnostic messages.\n * @returns A JSON-RPC response object.\n */\nasync function handleRequest(\n handlers: RpcHandlerMap,\n line: string,\n onShutdown: (() => Promise<void>) | undefined,\n log: (message: string) => void,\n): Promise<JsonRpcResponse> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n return {\n jsonrpc: '2.0',\n id: null,\n error: rpcErrors.parse({ message: 'Parse error' }).serialize(),\n };\n }\n\n if (!isJsonRpcRequest(parsed)) {\n const id: JsonRpcId =\n typeof parsed === 'object' &&\n parsed !== null &&\n hasProperty(parsed, 'id') &&\n isValidJsonRpcId(parsed.id)\n ? parsed.id\n : null;\n return {\n jsonrpc: '2.0',\n id,\n error: rpcErrors\n .invalidRequest({ message: 'Invalid JSON-RPC request' })\n .serialize(),\n };\n }\n\n const { id, method, params } = parsed;\n\n try {\n if (method === 'shutdown') {\n if (onShutdown) {\n setTimeout(() => {\n onShutdown().catch((error: unknown) => {\n log(`onShutdown callback failed: ${String(error)}`);\n });\n }, 0);\n }\n return { jsonrpc: '2.0', id, result: { status: 'shutting down' } };\n }\n\n const handler = Object.prototype.hasOwnProperty.call(handlers, method)\n ? handlers[method]\n : undefined;\n if (!handler) {\n return {\n jsonrpc: '2.0',\n id,\n error: rpcErrors\n .methodNotFound({ message: `Method not found: ${method}` })\n .serialize(),\n };\n }\n\n const result = await handler(coerceHandlerParams(params));\n return { jsonrpc: '2.0', id, result: result ?? null };\n } catch (error) {\n log(`RPC handler \"${method}\" failed: ${String(error)}`);\n if (isRpcError(error)) {\n return { jsonrpc: '2.0', id, error };\n }\n const message = error instanceof Error ? error.message : 'Internal error';\n return {\n jsonrpc: '2.0',\n id,\n error: rpcErrors.internal({ message }).serialize(),\n };\n }\n}\n\n/**\n * Narrow `params` to the shape handlers expect. JSON-RPC 2.0 requires\n * `params`, when present, to be an array or object; both are valid `Json`.\n *\n * @param params - The validated `params` field from a JSON-RPC request.\n * @returns The same value, or `null` when absent.\n */\nfunction coerceHandlerParams(\n params: JsonRpcParams | undefined,\n): JsonRpcParams | null {\n return params ?? null;\n}\n\n/**\n * Per JSON-RPC 2.0, `id` must be a string, number, or null. Used when\n * salvaging an `id` from a parse-success-but-not-valid-request payload.\n *\n * @param value - The candidate id.\n * @returns True if the value is an acceptable JSON-RPC id.\n */\nfunction isValidJsonRpcId(value: unknown): value is JsonRpcId {\n return (\n value === null || typeof value === 'string' || typeof value === 'number'\n );\n}\n\n/**\n * Check if an error is an RPC error with a numeric code.\n *\n * @param error - The error to check.\n * @returns True if the error has a numeric code property.\n */\nfunction isRpcError(\n error: unknown,\n): error is { code: number; message: string } {\n return (\n typeof error === 'object' &&\n error !== null &&\n hasProperty(error, 'code') &&\n typeof error.code === 'number' &&\n hasProperty(error, 'message') &&\n typeof error.message === 'string'\n );\n}\n\n/**\n * Start listening on a Unix socket path, removing any stale socket file.\n *\n * @param server - The net.Server instance.\n * @param socketPath - The Unix socket path.\n */\nasync function listen(server: Server, socketPath: string): Promise<void> {\n try {\n await unlink(socketPath);\n } catch (error) {\n if (!isErrorWithCode(error, 'ENOENT')) {\n throw error;\n }\n }\n\n await new Promise<void>((resolve, reject) => {\n server.on('error', reject);\n server.listen(socketPath, () => {\n server.removeListener('error', reject);\n resolve();\n });\n });\n\n // Restrict the socket to its owner. The daemon hosts an unlocked wallet, so\n // a world-connectable socket would let any local user drive it. listen()\n // creates the socket with umask-derived (typically world-accessible) perms.\n try {\n await chmod(socketPath, 0o600);\n } catch (error) {\n // Never leave a possibly world-accessible socket listening with no handle\n // to close it. Cleanup is best-effort so the chmod failure still surfaces.\n await closeServer(server).catch(() => undefined);\n await unlink(socketPath).catch(() => undefined);\n throw error;\n }\n}\n\n/**\n * Close a server, resolving once it stops accepting connections.\n *\n * @param server - The net.Server instance.\n */\nasync function closeServer(server: Server): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n"]}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readLine = exports.writeLine = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Write a newline-delimited line to a socket.
|
|
6
|
-
*
|
|
7
|
-
* @param socket - The socket to write to.
|
|
8
|
-
* @param line - The line to write (without trailing newline).
|
|
9
|
-
*/
|
|
10
|
-
async function writeLine(socket, line) {
|
|
11
|
-
return new Promise((resolve, reject) => {
|
|
12
|
-
// A socket 'error' can fire while the write is in flight without ever
|
|
13
|
-
// reaching the write callback; without this listener Node would treat it
|
|
14
|
-
// as an unhandled 'error' event and crash the process.
|
|
15
|
-
const onError = (error) => {
|
|
16
|
-
socket.removeListener('error', onError);
|
|
17
|
-
reject(error);
|
|
18
|
-
};
|
|
19
|
-
socket.once('error', onError);
|
|
20
|
-
socket.write(`${line}\n`, (error) => {
|
|
21
|
-
socket.removeListener('error', onError);
|
|
22
|
-
if (error) {
|
|
23
|
-
reject(error);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
resolve();
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
exports.writeLine = writeLine;
|
|
32
|
-
/**
|
|
33
|
-
* Read a single newline-delimited line from a socket.
|
|
34
|
-
*
|
|
35
|
-
* @param socket - The socket to read from.
|
|
36
|
-
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
37
|
-
* error if no complete line is received within the limit.
|
|
38
|
-
* @returns The line read (without trailing newline).
|
|
39
|
-
*/
|
|
40
|
-
async function readLine(socket, timeoutMs) {
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
let buffer = '';
|
|
43
|
-
let timer;
|
|
44
|
-
if (timeoutMs !== undefined) {
|
|
45
|
-
timer = setTimeout(() => {
|
|
46
|
-
cleanup();
|
|
47
|
-
reject(new Error('Socket read timed out'));
|
|
48
|
-
}, timeoutMs);
|
|
49
|
-
}
|
|
50
|
-
const onData = (data) => {
|
|
51
|
-
buffer += data.toString();
|
|
52
|
-
const idx = buffer.indexOf('\n');
|
|
53
|
-
if (idx !== -1) {
|
|
54
|
-
cleanup();
|
|
55
|
-
resolve(buffer.slice(0, idx));
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
const onError = (error) => {
|
|
59
|
-
cleanup();
|
|
60
|
-
reject(error);
|
|
61
|
-
};
|
|
62
|
-
const onEnd = () => {
|
|
63
|
-
cleanup();
|
|
64
|
-
reject(new Error('Socket closed before response received'));
|
|
65
|
-
};
|
|
66
|
-
const onClose = () => {
|
|
67
|
-
cleanup();
|
|
68
|
-
reject(new Error('Socket closed before response received'));
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* Remove listeners registered by this call and clear the timeout.
|
|
72
|
-
*/
|
|
73
|
-
function cleanup() {
|
|
74
|
-
if (timer !== undefined) {
|
|
75
|
-
clearTimeout(timer);
|
|
76
|
-
}
|
|
77
|
-
socket.removeListener('data', onData);
|
|
78
|
-
socket.removeListener('error', onError);
|
|
79
|
-
socket.removeListener('end', onEnd);
|
|
80
|
-
socket.removeListener('close', onClose);
|
|
81
|
-
}
|
|
82
|
-
socket.on('data', onData);
|
|
83
|
-
socket.once('error', onError);
|
|
84
|
-
socket.once('end', onEnd);
|
|
85
|
-
socket.once('close', onClose);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
exports.readLine = readLine;
|
|
89
|
-
//# sourceMappingURL=socket-line.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"socket-line.cjs","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Socket } from 'node:net';\n\n/**\n * Write a newline-delimited line to a socket.\n *\n * @param socket - The socket to write to.\n * @param line - The line to write (without trailing newline).\n */\nexport async function writeLine(socket: Socket, line: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // A socket 'error' can fire while the write is in flight without ever\n // reaching the write callback; without this listener Node would treat it\n // as an unhandled 'error' event and crash the process.\n const onError = (error: Error): void => {\n socket.removeListener('error', onError);\n reject(error);\n };\n socket.once('error', onError);\n\n socket.write(`${line}\\n`, (error) => {\n socket.removeListener('error', onError);\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n\n/**\n * Read a single newline-delimited line from a socket.\n *\n * @param socket - The socket to read from.\n * @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout\n * error if no complete line is received within the limit.\n * @returns The line read (without trailing newline).\n */\nexport async function readLine(\n socket: Socket,\n timeoutMs?: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n let buffer = '';\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs !== undefined) {\n timer = setTimeout(() => {\n cleanup();\n reject(new Error('Socket read timed out'));\n }, timeoutMs);\n }\n\n const onData = (data: Buffer): void => {\n buffer += data.toString();\n const idx = buffer.indexOf('\\n');\n if (idx !== -1) {\n cleanup();\n resolve(buffer.slice(0, idx));\n }\n };\n\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n\n const onEnd = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n const onClose = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n /**\n * Remove listeners registered by this call and clear the timeout.\n */\n function cleanup(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n socket.removeListener('data', onData);\n socket.removeListener('error', onError);\n socket.removeListener('end', onEnd);\n socket.removeListener('close', onClose);\n }\n\n socket.on('data', onData);\n socket.once('error', onError);\n socket.once('end', onEnd);\n socket.once('close', onClose);\n });\n}\n"]}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import type { Socket } from "node:net";
|
|
3
|
-
/**
|
|
4
|
-
* Write a newline-delimited line to a socket.
|
|
5
|
-
*
|
|
6
|
-
* @param socket - The socket to write to.
|
|
7
|
-
* @param line - The line to write (without trailing newline).
|
|
8
|
-
*/
|
|
9
|
-
export declare function writeLine(socket: Socket, line: string): Promise<void>;
|
|
10
|
-
/**
|
|
11
|
-
* Read a single newline-delimited line from a socket.
|
|
12
|
-
*
|
|
13
|
-
* @param socket - The socket to read from.
|
|
14
|
-
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
15
|
-
* error if no complete line is received within the limit.
|
|
16
|
-
* @returns The line read (without trailing newline).
|
|
17
|
-
*/
|
|
18
|
-
export declare function readLine(socket: Socket, timeoutMs?: number): Promise<string>;
|
|
19
|
-
//# sourceMappingURL=socket-line.d.cts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"socket-line.d.cts","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB;AAEvC;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAsDjB"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import type { Socket } from "node:net";
|
|
3
|
-
/**
|
|
4
|
-
* Write a newline-delimited line to a socket.
|
|
5
|
-
*
|
|
6
|
-
* @param socket - The socket to write to.
|
|
7
|
-
* @param line - The line to write (without trailing newline).
|
|
8
|
-
*/
|
|
9
|
-
export declare function writeLine(socket: Socket, line: string): Promise<void>;
|
|
10
|
-
/**
|
|
11
|
-
* Read a single newline-delimited line from a socket.
|
|
12
|
-
*
|
|
13
|
-
* @param socket - The socket to read from.
|
|
14
|
-
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
15
|
-
* error if no complete line is received within the limit.
|
|
16
|
-
* @returns The line read (without trailing newline).
|
|
17
|
-
*/
|
|
18
|
-
export declare function readLine(socket: Socket, timeoutMs?: number): Promise<string>;
|
|
19
|
-
//# sourceMappingURL=socket-line.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"socket-line.d.mts","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB;AAEvC;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAsDjB"}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Write a newline-delimited line to a socket.
|
|
3
|
-
*
|
|
4
|
-
* @param socket - The socket to write to.
|
|
5
|
-
* @param line - The line to write (without trailing newline).
|
|
6
|
-
*/
|
|
7
|
-
export async function writeLine(socket, line) {
|
|
8
|
-
return new Promise((resolve, reject) => {
|
|
9
|
-
// A socket 'error' can fire while the write is in flight without ever
|
|
10
|
-
// reaching the write callback; without this listener Node would treat it
|
|
11
|
-
// as an unhandled 'error' event and crash the process.
|
|
12
|
-
const onError = (error) => {
|
|
13
|
-
socket.removeListener('error', onError);
|
|
14
|
-
reject(error);
|
|
15
|
-
};
|
|
16
|
-
socket.once('error', onError);
|
|
17
|
-
socket.write(`${line}\n`, (error) => {
|
|
18
|
-
socket.removeListener('error', onError);
|
|
19
|
-
if (error) {
|
|
20
|
-
reject(error);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
resolve();
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Read a single newline-delimited line from a socket.
|
|
30
|
-
*
|
|
31
|
-
* @param socket - The socket to read from.
|
|
32
|
-
* @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout
|
|
33
|
-
* error if no complete line is received within the limit.
|
|
34
|
-
* @returns The line read (without trailing newline).
|
|
35
|
-
*/
|
|
36
|
-
export async function readLine(socket, timeoutMs) {
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
let buffer = '';
|
|
39
|
-
let timer;
|
|
40
|
-
if (timeoutMs !== undefined) {
|
|
41
|
-
timer = setTimeout(() => {
|
|
42
|
-
cleanup();
|
|
43
|
-
reject(new Error('Socket read timed out'));
|
|
44
|
-
}, timeoutMs);
|
|
45
|
-
}
|
|
46
|
-
const onData = (data) => {
|
|
47
|
-
buffer += data.toString();
|
|
48
|
-
const idx = buffer.indexOf('\n');
|
|
49
|
-
if (idx !== -1) {
|
|
50
|
-
cleanup();
|
|
51
|
-
resolve(buffer.slice(0, idx));
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const onError = (error) => {
|
|
55
|
-
cleanup();
|
|
56
|
-
reject(error);
|
|
57
|
-
};
|
|
58
|
-
const onEnd = () => {
|
|
59
|
-
cleanup();
|
|
60
|
-
reject(new Error('Socket closed before response received'));
|
|
61
|
-
};
|
|
62
|
-
const onClose = () => {
|
|
63
|
-
cleanup();
|
|
64
|
-
reject(new Error('Socket closed before response received'));
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Remove listeners registered by this call and clear the timeout.
|
|
68
|
-
*/
|
|
69
|
-
function cleanup() {
|
|
70
|
-
if (timer !== undefined) {
|
|
71
|
-
clearTimeout(timer);
|
|
72
|
-
}
|
|
73
|
-
socket.removeListener('data', onData);
|
|
74
|
-
socket.removeListener('error', onError);
|
|
75
|
-
socket.removeListener('end', onEnd);
|
|
76
|
-
socket.removeListener('close', onClose);
|
|
77
|
-
}
|
|
78
|
-
socket.on('data', onData);
|
|
79
|
-
socket.once('error', onError);
|
|
80
|
-
socket.once('end', onEnd);
|
|
81
|
-
socket.once('close', onClose);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
//# sourceMappingURL=socket-line.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"socket-line.mjs","sourceRoot":"","sources":["../../src/daemon/socket-line.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Socket } from 'node:net';\n\n/**\n * Write a newline-delimited line to a socket.\n *\n * @param socket - The socket to write to.\n * @param line - The line to write (without trailing newline).\n */\nexport async function writeLine(socket: Socket, line: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // A socket 'error' can fire while the write is in flight without ever\n // reaching the write callback; without this listener Node would treat it\n // as an unhandled 'error' event and crash the process.\n const onError = (error: Error): void => {\n socket.removeListener('error', onError);\n reject(error);\n };\n socket.once('error', onError);\n\n socket.write(`${line}\\n`, (error) => {\n socket.removeListener('error', onError);\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n\n/**\n * Read a single newline-delimited line from a socket.\n *\n * @param socket - The socket to read from.\n * @param timeoutMs - Optional timeout in milliseconds. Rejects with a timeout\n * error if no complete line is received within the limit.\n * @returns The line read (without trailing newline).\n */\nexport async function readLine(\n socket: Socket,\n timeoutMs?: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n let buffer = '';\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs !== undefined) {\n timer = setTimeout(() => {\n cleanup();\n reject(new Error('Socket read timed out'));\n }, timeoutMs);\n }\n\n const onData = (data: Buffer): void => {\n buffer += data.toString();\n const idx = buffer.indexOf('\\n');\n if (idx !== -1) {\n cleanup();\n resolve(buffer.slice(0, idx));\n }\n };\n\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n\n const onEnd = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n const onClose = (): void => {\n cleanup();\n reject(new Error('Socket closed before response received'));\n };\n\n /**\n * Remove listeners registered by this call and clear the timeout.\n */\n function cleanup(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n socket.removeListener('data', onData);\n socket.removeListener('error', onError);\n socket.removeListener('end', onEnd);\n socket.removeListener('close', onClose);\n }\n\n socket.on('data', onData);\n socket.once('error', onError);\n socket.once('end', onEnd);\n socket.once('close', onClose);\n });\n}\n"]}
|
|
@@ -1,103 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,99 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|