@dinoxx/dinox-cli 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -3
- package/dist/cli.js +4 -0
- package/dist/commands/auth/index.js +1 -2
- package/dist/commands/boxes/index.js +37 -13
- package/dist/commands/boxes/operations.d.ts +3 -3
- package/dist/commands/boxes/operations.js +51 -28
- package/dist/commands/info/index.js +30 -5
- package/dist/commands/meta/repo.js +2 -1
- package/dist/commands/notes/actions.js +20 -37
- package/dist/commands/notes/bulk.d.ts +2 -2
- package/dist/commands/notes/bulk.js +3 -18
- package/dist/commands/notes/contentPatch.d.ts +5 -1
- package/dist/commands/notes/contentPatch.js +12 -1
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +29 -8
- package/dist/commands/notes/descriptors.js +19 -16
- package/dist/commands/notes/export.js +3 -0
- package/dist/commands/notes/repo.d.ts +9 -0
- package/dist/commands/notes/repo.js +65 -15
- package/dist/commands/notes/searchOutput.d.ts +2 -1
- package/dist/commands/notes/searchOutput.js +2 -1
- package/dist/commands/notes/searchSql.js +9 -3
- package/dist/commands/prompt/index.d.ts +1 -0
- package/dist/commands/prompt/index.js +26 -11
- package/dist/commands/savedViews/index.d.ts +7 -0
- package/dist/commands/savedViews/index.js +458 -0
- package/dist/commands/savedViews/model.d.ts +87 -0
- package/dist/commands/savedViews/model.js +306 -0
- package/dist/commands/savedViews/repo.d.ts +123 -0
- package/dist/commands/savedViews/repo.js +512 -0
- package/dist/commands/savedViews/semantics.d.ts +46 -0
- package/dist/commands/savedViews/semantics.js +738 -0
- package/dist/commands/schema/renderSkills.d.ts +2 -0
- package/dist/commands/schema/renderSkills.js +69 -10
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/skills/index.d.ts +54 -0
- package/dist/commands/skills/index.js +468 -0
- package/dist/commands/storage/index.js +2 -2
- package/dist/commands/sync.js +2 -1
- package/dist/commands/tags/index.js +30 -11
- package/dist/commands/tags/operations.d.ts +3 -3
- package/dist/commands/tags/operations.js +45 -20
- package/dist/commands/templates/model.d.ts +50 -0
- package/dist/commands/templates/model.js +239 -0
- package/dist/commands/todo/index.js +3 -3
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +46 -9
- package/dist/commands/writeConfirmation.d.ts +13 -0
- package/dist/commands/writeConfirmation.js +54 -0
- package/dist/config/resolve.js +5 -10
- package/dist/config/types.d.ts +3 -6
- package/dist/constants/links.d.ts +18 -1
- package/dist/constants/links.js +26 -1
- package/dist/daemon/client.d.ts +2 -2
- package/dist/daemon/client.js +106 -52
- package/dist/daemon/index.d.ts +5 -3
- package/dist/daemon/index.js +28 -5
- package/dist/daemon/lifecycle.d.ts +7 -2
- package/dist/daemon/lifecycle.js +53 -13
- package/dist/daemon/pid.d.ts +1 -1
- package/dist/daemon/pid.js +6 -3
- package/dist/dinox.js +9 -1
- package/dist/powersync/exclusiveRuntime.d.ts +1 -0
- package/dist/powersync/exclusiveRuntime.js +26 -8
- package/dist/powersync/runtime.d.ts +3 -2
- package/dist/powersync/runtime.js +19 -3
- package/dist/powersync/schema/content.d.ts +1 -78
- package/dist/powersync/schema/content.js +1 -78
- package/dist/powersync/schema/electronCanonical.d.ts +1561 -0
- package/dist/powersync/schema/electronCanonical.js +1154 -0
- package/dist/powersync/schema/index.d.ts +573 -129
- package/dist/powersync/schema/index.js +3 -32
- package/dist/powersync/schema/local.d.ts +1 -56
- package/dist/powersync/schema/local.js +1 -60
- package/dist/powersync/schema/note.d.ts +1 -35
- package/dist/powersync/schema/note.js +1 -35
- package/dist/powersync/schema/notesExtras.d.ts +1 -62
- package/dist/powersync/schema/notesExtras.js +1 -71
- package/dist/powersync/schema/projects.d.ts +1 -104
- package/dist/powersync/schema/projects.js +1 -104
- package/dist/powersync/schema/savedViews.d.ts +1 -0
- package/dist/powersync/schema/savedViews.js +1 -0
- package/dist/powersync/schema/storage.d.ts +1 -24
- package/dist/powersync/schema/storage.js +1 -24
- package/dist/powersync/schema/tags.d.ts +1 -37
- package/dist/powersync/schema/tags.js +1 -37
- package/dist/powersync/syncGate.d.ts +20 -2
- package/dist/powersync/uploader.d.ts +16 -2
- package/dist/powersync/uploader.js +152 -158
- package/dist/skills/check.d.ts +1 -0
- package/dist/skills/check.js +538 -70
- package/dist/skills/retired.d.ts +60 -0
- package/dist/skills/retired.js +58 -0
- package/dist/utils/deadline.d.ts +8 -0
- package/dist/utils/deadline.js +31 -0
- package/dist/utils/dryRun.d.ts +20 -1
- package/dist/utils/dryRun.js +22 -3
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/errors.js +2 -0
- package/dist/utils/processExit.d.ts +1 -0
- package/dist/utils/processExit.js +33 -0
- package/dist/utils/updateNotice.d.ts +1 -0
- package/dist/utils/updateNotice.js +1 -1
- package/package.json +8 -5
|
@@ -1 +1,18 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const SKILLS_REPO_SLUG = "ryzencool/dinox-cli-skills";
|
|
2
|
+
export declare const SKILLS_REPO_URL = "https://github.com/ryzencool/dinox-cli-skills";
|
|
3
|
+
export declare const SKILLS_INSTALLER_NAME = "skills";
|
|
4
|
+
export declare const SKILLS_INSTALLER_VERSION = "1.5.16";
|
|
5
|
+
export declare const SKILLS_INSTALL_AGENTS: readonly ["codex", "claude-code", "hermes-agent", "openclaw"];
|
|
6
|
+
export type SkillsInstallerMetadata = {
|
|
7
|
+
name: typeof SKILLS_INSTALLER_NAME;
|
|
8
|
+
version: typeof SKILLS_INSTALLER_VERSION;
|
|
9
|
+
};
|
|
10
|
+
export type SkillsReleaseMetadata = {
|
|
11
|
+
repository: string;
|
|
12
|
+
version: string;
|
|
13
|
+
tag: string;
|
|
14
|
+
release: string;
|
|
15
|
+
installer: SkillsInstallerMetadata;
|
|
16
|
+
installCommand: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function getSkillsReleaseMetadata(version: string): SkillsReleaseMetadata;
|
package/dist/constants/links.js
CHANGED
|
@@ -1 +1,26 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const SKILLS_REPO_SLUG = 'ryzencool/dinox-cli-skills';
|
|
2
|
+
export const SKILLS_REPO_URL = `https://github.com/${SKILLS_REPO_SLUG}`;
|
|
3
|
+
export const SKILLS_INSTALLER_NAME = 'skills';
|
|
4
|
+
export const SKILLS_INSTALLER_VERSION = '1.5.16';
|
|
5
|
+
export const SKILLS_INSTALL_AGENTS = Object.freeze([
|
|
6
|
+
'codex',
|
|
7
|
+
'claude-code',
|
|
8
|
+
'hermes-agent',
|
|
9
|
+
'openclaw',
|
|
10
|
+
]);
|
|
11
|
+
export function getSkillsReleaseMetadata(version) {
|
|
12
|
+
const normalizedVersion = version.trim();
|
|
13
|
+
const tag = `v${normalizedVersion}`;
|
|
14
|
+
const agentFlags = SKILLS_INSTALL_AGENTS.map((agent) => `-a ${agent}`).join(' ');
|
|
15
|
+
return {
|
|
16
|
+
repository: SKILLS_REPO_URL,
|
|
17
|
+
version: normalizedVersion,
|
|
18
|
+
tag,
|
|
19
|
+
release: `${SKILLS_REPO_URL}/tree/${encodeURIComponent(tag)}`,
|
|
20
|
+
installer: {
|
|
21
|
+
name: SKILLS_INSTALLER_NAME,
|
|
22
|
+
version: SKILLS_INSTALLER_VERSION,
|
|
23
|
+
},
|
|
24
|
+
installCommand: `npx --yes ${SKILLS_INSTALLER_NAME}@${SKILLS_INSTALLER_VERSION} add ${SKILLS_REPO_SLUG}#${tag} -g ${agentFlags} --skill '*' -y`,
|
|
25
|
+
};
|
|
26
|
+
}
|
package/dist/daemon/client.d.ts
CHANGED
|
@@ -2,13 +2,13 @@ import type { DaemonPingResult, DaemonShutdownResult } from './types.js';
|
|
|
2
2
|
export declare function sendDaemonRequest(method: string, params?: unknown, requestOptions?: {
|
|
3
3
|
timeoutMs?: number;
|
|
4
4
|
}): Promise<unknown>;
|
|
5
|
-
export declare function isDaemonRunning(): Promise<boolean>;
|
|
5
|
+
export declare function isDaemonRunning(timeoutMs?: number): Promise<boolean>;
|
|
6
6
|
/**
|
|
7
7
|
* Ping the daemon and return its handshake payload (version + connected identity),
|
|
8
8
|
* or null when no daemon is reachable. Callers use this to detect a stale daemon
|
|
9
9
|
* (wrong version or wrong account) rather than only "running / not running".
|
|
10
10
|
*/
|
|
11
|
-
export declare function getDaemonInfo(): Promise<DaemonPingResult | null>;
|
|
11
|
+
export declare function getDaemonInfo(timeoutMs?: number): Promise<DaemonPingResult | null>;
|
|
12
12
|
/**
|
|
13
13
|
* Whether a reachable daemon matches the current CLI version and signed-in account.
|
|
14
14
|
* A mismatch means the daemon was started by an older build or a different user and
|
package/dist/daemon/client.js
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import { request as httpRequest } from 'node:http';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { getDaemonSocketPath, isLegacyDaemonProcess, isProcessRunning, readPid, removePid, } from './pid.js';
|
|
4
|
+
import { remainingDeadlineMs } from '../utils/deadline.js';
|
|
4
5
|
import { DinoxError } from '../utils/errors.js';
|
|
5
6
|
const DEFAULT_DAEMON_REQUEST_TIMEOUT_MS = 10_000;
|
|
6
7
|
const MAX_DAEMON_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
function normalizedTimeoutMs(timeoutMs, fallbackMs) {
|
|
9
|
+
return Number.isFinite(timeoutMs)
|
|
10
|
+
? Math.max(0, Math.floor(timeoutMs))
|
|
11
|
+
: fallbackMs;
|
|
12
|
+
}
|
|
13
|
+
function createDaemonReleaseTimeoutError(message) {
|
|
14
|
+
return new DinoxError(message, {
|
|
15
|
+
code: 'DAEMON_RELEASE_TIMEOUT',
|
|
16
|
+
suggestion: 'Run `dino daemon stop`, then retry the command.',
|
|
17
|
+
recoverable: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function waitForNextPoll(deadlineAtMs, intervalMs) {
|
|
21
|
+
const waitMs = remainingDeadlineMs(deadlineAtMs, Math.max(0, intervalMs));
|
|
22
|
+
if (waitMs <= 0) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(1, waitMs)));
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
7
28
|
export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
29
|
+
const timeoutMs = Math.max(1, normalizedTimeoutMs(requestOptions.timeoutMs ?? DEFAULT_DAEMON_REQUEST_TIMEOUT_MS, DEFAULT_DAEMON_REQUEST_TIMEOUT_MS));
|
|
8
30
|
const request = {
|
|
9
31
|
id: Date.now().toString(),
|
|
10
32
|
method,
|
|
@@ -12,10 +34,14 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
|
12
34
|
};
|
|
13
35
|
return new Promise((resolve, reject) => {
|
|
14
36
|
let settled = false;
|
|
37
|
+
let deadlineTimer;
|
|
15
38
|
const finalize = (error, value) => {
|
|
16
39
|
if (settled)
|
|
17
40
|
return;
|
|
18
41
|
settled = true;
|
|
42
|
+
if (deadlineTimer) {
|
|
43
|
+
clearTimeout(deadlineTimer);
|
|
44
|
+
}
|
|
19
45
|
if (error) {
|
|
20
46
|
reject(error);
|
|
21
47
|
return;
|
|
@@ -32,7 +58,7 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
|
32
58
|
'Content-Type': 'application/json',
|
|
33
59
|
'Content-Length': Buffer.byteLength(data),
|
|
34
60
|
},
|
|
35
|
-
timeout:
|
|
61
|
+
timeout: timeoutMs,
|
|
36
62
|
};
|
|
37
63
|
const req = httpRequest(options, (res) => {
|
|
38
64
|
const statusCode = res.statusCode ?? 0;
|
|
@@ -70,6 +96,10 @@ export async function sendDaemonRequest(method, params, requestOptions = {}) {
|
|
|
70
96
|
req.destroy(new Error('Daemon request timed out'));
|
|
71
97
|
});
|
|
72
98
|
req.on('error', (error) => finalize(error));
|
|
99
|
+
deadlineTimer = setTimeout(() => {
|
|
100
|
+
req.destroy(new Error('Daemon request timed out'));
|
|
101
|
+
}, timeoutMs);
|
|
102
|
+
deadlineTimer.unref?.();
|
|
73
103
|
req.write(data);
|
|
74
104
|
req.end();
|
|
75
105
|
});
|
|
@@ -97,17 +127,17 @@ function toDaemonError(error) {
|
|
|
97
127
|
}
|
|
98
128
|
return new Error(typeof error === 'string' && error.trim() ? error : 'Unknown error');
|
|
99
129
|
}
|
|
100
|
-
export async function isDaemonRunning() {
|
|
101
|
-
return (await getDaemonInfo()) !== null;
|
|
130
|
+
export async function isDaemonRunning(timeoutMs) {
|
|
131
|
+
return (await getDaemonInfo(timeoutMs)) !== null;
|
|
102
132
|
}
|
|
103
133
|
/**
|
|
104
134
|
* Ping the daemon and return its handshake payload (version + connected identity),
|
|
105
135
|
* or null when no daemon is reachable. Callers use this to detect a stale daemon
|
|
106
136
|
* (wrong version or wrong account) rather than only "running / not running".
|
|
107
137
|
*/
|
|
108
|
-
export async function getDaemonInfo() {
|
|
138
|
+
export async function getDaemonInfo(timeoutMs) {
|
|
109
139
|
try {
|
|
110
|
-
const result = await sendDaemonRequest('ping');
|
|
140
|
+
const result = await sendDaemonRequest('ping', undefined, { timeoutMs });
|
|
111
141
|
if (result && typeof result === 'object') {
|
|
112
142
|
const info = result;
|
|
113
143
|
return {
|
|
@@ -147,25 +177,38 @@ export function isLegacyDaemonInfo(info) {
|
|
|
147
177
|
return info.pid === 0 && info.instanceId.length === 0;
|
|
148
178
|
}
|
|
149
179
|
export async function shutdownLegacyDaemon(initialInfo, timeoutMs = 5_000) {
|
|
180
|
+
const maximumTimeoutMs = normalizedTimeoutMs(timeoutMs, 5_000);
|
|
181
|
+
const deadlineAtMs = Date.now() + maximumTimeoutMs;
|
|
182
|
+
const remainingTimeoutMs = () => remainingDeadlineMs(deadlineAtMs, maximumTimeoutMs);
|
|
183
|
+
const requireRemainingTimeoutMs = () => {
|
|
184
|
+
const remainingMs = remainingTimeoutMs();
|
|
185
|
+
if (remainingMs <= 0) {
|
|
186
|
+
throw createDaemonReleaseTimeoutError('Timed out while verifying the legacy daemon process.');
|
|
187
|
+
}
|
|
188
|
+
return remainingMs;
|
|
189
|
+
};
|
|
150
190
|
const pidInfo = readPid();
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
191
|
+
const legacyProcessVerified = Boolean(isLegacyDaemonInfo(initialInfo)
|
|
192
|
+
&& pidInfo
|
|
193
|
+
&& pidInfo.instanceId === null
|
|
194
|
+
&& isProcessRunning(pidInfo.pid)
|
|
195
|
+
&& await isLegacyDaemonProcess(pidInfo.pid, initialInfo.uptime, requireRemainingTimeoutMs()));
|
|
196
|
+
if (!legacyProcessVerified
|
|
197
|
+
|| !pidInfo) {
|
|
156
198
|
throw new DinoxError('A legacy daemon is responding, but its process identity could not be verified safely.', {
|
|
157
199
|
code: 'DAEMON_LEGACY_UNVERIFIED',
|
|
158
200
|
suggestion: 'Inspect the daemon PID and command line, stop that process manually, then retry.',
|
|
159
201
|
recoverable: true,
|
|
160
202
|
});
|
|
161
203
|
}
|
|
162
|
-
const confirmedInfo = await getDaemonInfo();
|
|
204
|
+
const confirmedInfo = await getDaemonInfo(requireRemainingTimeoutMs());
|
|
163
205
|
const confirmedPidInfo = readPid();
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
206
|
+
const confirmedLegacyProcess = Boolean(confirmedInfo
|
|
207
|
+
&& isLegacyDaemonInfo(confirmedInfo)
|
|
208
|
+
&& confirmedPidInfo?.pid === pidInfo.pid
|
|
209
|
+
&& confirmedPidInfo.instanceId === null
|
|
210
|
+
&& await isLegacyDaemonProcess(pidInfo.pid, confirmedInfo.uptime, requireRemainingTimeoutMs()));
|
|
211
|
+
if (!confirmedLegacyProcess) {
|
|
169
212
|
throw new DinoxError('The legacy daemon changed while its process identity was being verified.', {
|
|
170
213
|
code: 'DAEMON_INSTANCE_CHANGED',
|
|
171
214
|
suggestion: 'Retry after checking `dino daemon status`.',
|
|
@@ -183,9 +226,8 @@ export async function shutdownLegacyDaemon(initialInfo, timeoutMs = 5_000) {
|
|
|
183
226
|
recoverable: true,
|
|
184
227
|
});
|
|
185
228
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const current = await getDaemonInfo();
|
|
229
|
+
while (remainingTimeoutMs() > 0) {
|
|
230
|
+
const current = await getDaemonInfo(requireRemainingTimeoutMs());
|
|
189
231
|
const processRunning = isProcessRunning(pidInfo.pid);
|
|
190
232
|
if (!current && !processRunning) {
|
|
191
233
|
removePid(undefined, { expectedPid: pidInfo.pid });
|
|
@@ -199,37 +241,46 @@ export async function shutdownLegacyDaemon(initialInfo, timeoutMs = 5_000) {
|
|
|
199
241
|
recoverable: true,
|
|
200
242
|
});
|
|
201
243
|
}
|
|
202
|
-
|
|
244
|
+
if (!await waitForNextPoll(deadlineAtMs, 100)) {
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
203
247
|
}
|
|
204
|
-
throw
|
|
205
|
-
code: 'DAEMON_RELEASE_TIMEOUT',
|
|
206
|
-
suggestion: 'Inspect the daemon PID and stop that process manually before retrying.',
|
|
207
|
-
recoverable: true,
|
|
208
|
-
});
|
|
248
|
+
throw createDaemonReleaseTimeoutError('Timed out waiting for the verified legacy daemon to stop.');
|
|
209
249
|
}
|
|
210
250
|
/** Poll until the daemon answers a ping or the timeout elapses. */
|
|
211
251
|
export async function waitForDaemonReady(timeoutMs = 5_000, intervalMs = 250) {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
252
|
+
const maximumTimeoutMs = normalizedTimeoutMs(timeoutMs, 5_000);
|
|
253
|
+
const deadlineAtMs = Date.now() + maximumTimeoutMs;
|
|
254
|
+
while (true) {
|
|
255
|
+
const remainingMs = remainingDeadlineMs(deadlineAtMs, maximumTimeoutMs);
|
|
256
|
+
if (remainingMs <= 0) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (await isDaemonRunning(remainingMs)) {
|
|
215
260
|
return true;
|
|
216
261
|
}
|
|
217
|
-
|
|
262
|
+
if (!await waitForNextPoll(deadlineAtMs, intervalMs)) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
218
265
|
}
|
|
219
|
-
return isDaemonRunning();
|
|
220
266
|
}
|
|
221
267
|
/** Poll until the daemon answers with the expected authenticated identity. */
|
|
222
268
|
export async function waitForFreshDaemon(expectedVersion, expectedUserId, expectedTokenFingerprint, expectedRuntimeConfigFingerprint, timeoutMs = 5_000, intervalMs = 250) {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
269
|
+
const maximumTimeoutMs = normalizedTimeoutMs(timeoutMs, 5_000);
|
|
270
|
+
const deadlineAtMs = Date.now() + maximumTimeoutMs;
|
|
271
|
+
while (true) {
|
|
272
|
+
const remainingMs = remainingDeadlineMs(deadlineAtMs, maximumTimeoutMs);
|
|
273
|
+
if (remainingMs <= 0) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const info = await getDaemonInfo(remainingMs);
|
|
226
277
|
if (info && isDaemonFresh(info, expectedVersion, expectedUserId, expectedTokenFingerprint, expectedRuntimeConfigFingerprint)) {
|
|
227
278
|
return true;
|
|
228
279
|
}
|
|
229
|
-
|
|
280
|
+
if (!await waitForNextPoll(deadlineAtMs, intervalMs)) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
230
283
|
}
|
|
231
|
-
const info = await getDaemonInfo();
|
|
232
|
-
return Boolean(info && isDaemonFresh(info, expectedVersion, expectedUserId, expectedTokenFingerprint, expectedRuntimeConfigFingerprint));
|
|
233
284
|
}
|
|
234
285
|
/** Request shutdown of one exact daemon instance and reject if the socket changed owners. */
|
|
235
286
|
export async function requestDaemonShutdown(expectedInstanceId, timeoutMs = 2_000) {
|
|
@@ -255,7 +306,17 @@ export async function requestDaemonShutdown(expectedInstanceId, timeoutMs = 2_00
|
|
|
255
306
|
}
|
|
256
307
|
/** Stop a reachable daemon before another process acquires the exclusive PowerSync owner lease. */
|
|
257
308
|
export async function shutdownDaemonForExclusiveAccess(timeoutMs = 5_000, expectedInstanceId) {
|
|
258
|
-
const
|
|
309
|
+
const maximumTimeoutMs = normalizedTimeoutMs(timeoutMs, 5_000);
|
|
310
|
+
const deadlineAtMs = Date.now() + maximumTimeoutMs;
|
|
311
|
+
const remainingTimeoutMs = () => remainingDeadlineMs(deadlineAtMs, maximumTimeoutMs);
|
|
312
|
+
const requireRemainingTimeoutMs = () => {
|
|
313
|
+
const remainingMs = remainingTimeoutMs();
|
|
314
|
+
if (remainingMs <= 0) {
|
|
315
|
+
throw createDaemonReleaseTimeoutError('Timed out waiting for the daemon to release the PowerSync runtime.');
|
|
316
|
+
}
|
|
317
|
+
return remainingMs;
|
|
318
|
+
};
|
|
319
|
+
const info = await getDaemonInfo(requireRemainingTimeoutMs());
|
|
259
320
|
if (!info) {
|
|
260
321
|
const pidInfo = readPid();
|
|
261
322
|
const socketExists = process.platform !== 'win32' && existsSync(getDaemonSocketPath());
|
|
@@ -269,7 +330,7 @@ export async function shutdownDaemonForExclusiveAccess(timeoutMs = 5_000, expect
|
|
|
269
330
|
return false;
|
|
270
331
|
}
|
|
271
332
|
if (isLegacyDaemonInfo(info)) {
|
|
272
|
-
return shutdownLegacyDaemon(info,
|
|
333
|
+
return shutdownLegacyDaemon(info, requireRemainingTimeoutMs());
|
|
273
334
|
}
|
|
274
335
|
if (expectedInstanceId && info.instanceId !== expectedInstanceId) {
|
|
275
336
|
throw new DinoxError('Daemon instance changed before exclusive access could be acquired.', {
|
|
@@ -280,7 +341,7 @@ export async function shutdownDaemonForExclusiveAccess(timeoutMs = 5_000, expect
|
|
|
280
341
|
});
|
|
281
342
|
}
|
|
282
343
|
try {
|
|
283
|
-
await requestDaemonShutdown(info.instanceId, Math.min(
|
|
344
|
+
await requestDaemonShutdown(info.instanceId, Math.min(requireRemainingTimeoutMs(), 2_000));
|
|
284
345
|
}
|
|
285
346
|
catch (error) {
|
|
286
347
|
throw new DinoxError('The running daemon could not release the PowerSync runtime.', {
|
|
@@ -290,21 +351,14 @@ export async function shutdownDaemonForExclusiveAccess(timeoutMs = 5_000, expect
|
|
|
290
351
|
recoverable: true,
|
|
291
352
|
});
|
|
292
353
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const current = await getDaemonInfo();
|
|
354
|
+
while (remainingTimeoutMs() > 0) {
|
|
355
|
+
const current = await getDaemonInfo(requireRemainingTimeoutMs());
|
|
296
356
|
if (!current || current.instanceId !== info.instanceId) {
|
|
297
357
|
return true;
|
|
298
358
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (!current || current.instanceId !== info.instanceId) {
|
|
303
|
-
return true;
|
|
359
|
+
if (!await waitForNextPoll(deadlineAtMs, 100)) {
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
304
362
|
}
|
|
305
|
-
throw
|
|
306
|
-
code: 'DAEMON_RELEASE_TIMEOUT',
|
|
307
|
-
suggestion: 'Run `dino daemon stop`, then retry the command.',
|
|
308
|
-
recoverable: true,
|
|
309
|
-
});
|
|
363
|
+
throw createDaemonReleaseTimeoutError('Timed out waiting for the daemon to release the PowerSync runtime.');
|
|
310
364
|
}
|
package/dist/daemon/index.d.ts
CHANGED
|
@@ -8,14 +8,16 @@ type DaemonStartResult = {
|
|
|
8
8
|
detached: boolean;
|
|
9
9
|
alreadyRunning: boolean;
|
|
10
10
|
};
|
|
11
|
+
type DaemonStartOptions = {
|
|
12
|
+
quiet?: boolean;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
};
|
|
11
15
|
type DaemonStopResult = {
|
|
12
16
|
stopped: boolean;
|
|
13
17
|
wasRunning: boolean;
|
|
14
18
|
stalePidFile: boolean;
|
|
15
19
|
};
|
|
16
|
-
export declare function startDaemonLocked(port: number, detached: boolean, options?:
|
|
17
|
-
quiet?: boolean;
|
|
18
|
-
}): Promise<DaemonStartResult>;
|
|
20
|
+
export declare function startDaemonLocked(port: number, detached: boolean, options?: DaemonStartOptions): Promise<DaemonStartResult>;
|
|
19
21
|
/** Caller must hold the daemon start lock. */
|
|
20
22
|
export declare function stopDaemonLocked(): Promise<DaemonStopResult>;
|
|
21
23
|
export declare function stopDaemon(): Promise<DaemonStopResult>;
|
package/dist/daemon/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { loadConfig } from '../config/store.js';
|
|
|
12
12
|
import { fingerprintDaemonRuntimeConfig, resolveConfig } from '../config/resolve.js';
|
|
13
13
|
import { hydrateResolvedAuthIdentity } from '../auth/userInfo.js';
|
|
14
14
|
import { withAsyncRelease } from '../utils/asyncRelease.js';
|
|
15
|
+
import { remainingDeadlineMs } from '../utils/deadline.js';
|
|
15
16
|
import { getPackageVersion } from '../utils/version.js';
|
|
16
17
|
const DAEMON_STOP_POLL_INTERVAL_MS = 100;
|
|
17
18
|
const DAEMON_STOP_TIMEOUT_MS = 5_000;
|
|
@@ -63,9 +64,11 @@ async function waitForDaemonExit(instanceId, timeoutMs) {
|
|
|
63
64
|
const info = await getDaemonInfo();
|
|
64
65
|
return !info || info.instanceId !== instanceId;
|
|
65
66
|
}
|
|
66
|
-
async function expectedDaemonIdentity(baseConfig) {
|
|
67
|
+
async function expectedDaemonIdentity(baseConfig, timeoutMs) {
|
|
67
68
|
const resolved = baseConfig ?? resolveConfig(await loadConfig());
|
|
68
|
-
const { config } = await hydrateResolvedAuthIdentity(resolved, {
|
|
69
|
+
const { config } = await hydrateResolvedAuthIdentity(resolved, {
|
|
70
|
+
timeoutMs: timeoutMs ?? resolved.sync.timeoutMs,
|
|
71
|
+
});
|
|
69
72
|
return {
|
|
70
73
|
version: getPackageVersion(),
|
|
71
74
|
userId: config.auth.userId ?? null,
|
|
@@ -82,9 +85,29 @@ function matchesRuntimeTokenBinding(info, version, config) {
|
|
|
82
85
|
&& (!config.auth.userId || info.userId === config.auth.userId));
|
|
83
86
|
}
|
|
84
87
|
export async function startDaemonLocked(port, detached, options = {}) {
|
|
88
|
+
const maximumTimeoutMs = options.timeoutMs === undefined
|
|
89
|
+
? undefined
|
|
90
|
+
: Math.max(0, Math.floor(options.timeoutMs));
|
|
91
|
+
const deadlineAtMs = maximumTimeoutMs === undefined
|
|
92
|
+
? undefined
|
|
93
|
+
: Date.now() + maximumTimeoutMs;
|
|
94
|
+
const remainingStartTimeoutMs = (phase) => {
|
|
95
|
+
if (deadlineAtMs === undefined || maximumTimeoutMs === undefined) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const remainingMs = remainingDeadlineMs(deadlineAtMs, maximumTimeoutMs);
|
|
99
|
+
if (remainingMs <= 0) {
|
|
100
|
+
throw new DinoxError('Failed to start daemon before the caller deadline expired.', {
|
|
101
|
+
code: 'DAEMON_START_TIMEOUT',
|
|
102
|
+
details: { phase },
|
|
103
|
+
recoverable: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return remainingMs;
|
|
107
|
+
};
|
|
85
108
|
const baseConfig = resolveConfig(await loadConfig());
|
|
86
109
|
const version = getPackageVersion();
|
|
87
|
-
const runningInfo = await getDaemonInfo();
|
|
110
|
+
const runningInfo = await getDaemonInfo(remainingStartTimeoutMs('initial-ping'));
|
|
88
111
|
if (runningInfo) {
|
|
89
112
|
if (matchesRuntimeTokenBinding(runningInfo, version, baseConfig)) {
|
|
90
113
|
return { running: true, port, socketPath: getDaemonSocketPath(), transport: 'socket', detached, alreadyRunning: true };
|
|
@@ -94,7 +117,7 @@ export async function startDaemonLocked(port, detached, options = {}) {
|
|
|
94
117
|
recoverable: true,
|
|
95
118
|
});
|
|
96
119
|
}
|
|
97
|
-
const expected = await expectedDaemonIdentity(baseConfig);
|
|
120
|
+
const expected = await expectedDaemonIdentity(baseConfig, remainingStartTimeoutMs('auth-hydration'));
|
|
98
121
|
if (detached) {
|
|
99
122
|
const args = ['daemon', 'start', '--port', String(port), '--no-detach'];
|
|
100
123
|
const entrypoint = process.argv[1];
|
|
@@ -107,7 +130,7 @@ export async function startDaemonLocked(port, detached, options = {}) {
|
|
|
107
130
|
env: { ...process.env, [DAEMON_CHILD_ENV]: '1' },
|
|
108
131
|
});
|
|
109
132
|
child.unref();
|
|
110
|
-
const ready = await waitForFreshDaemon(expected.version, expected.userId, expected.tokenFingerprint, expected.runtimeConfigFingerprint);
|
|
133
|
+
const ready = await waitForFreshDaemon(expected.version, expected.userId, expected.tokenFingerprint, expected.runtimeConfigFingerprint, remainingStartTimeoutMs('ready-poll'));
|
|
111
134
|
if (!ready) {
|
|
112
135
|
child.kill('SIGTERM');
|
|
113
136
|
throw new DinoxError('Failed to start daemon');
|
|
@@ -28,17 +28,22 @@ type DaemonLifecycleDependencies = {
|
|
|
28
28
|
getPackageVersion: typeof getPackageVersion;
|
|
29
29
|
printWarning: typeof printHumanReadableWarning;
|
|
30
30
|
};
|
|
31
|
-
export declare function captureDaemonSnapshot(overrides?: Partial<DaemonLifecycleDependencies
|
|
31
|
+
export declare function captureDaemonSnapshot(overrides?: Partial<DaemonLifecycleDependencies>, timeoutMs?: number): Promise<DaemonSnapshot>;
|
|
32
32
|
export declare function daemonSnapshotIsFresh(config: ResolvedDinoxConfig, snapshot: DaemonSnapshot, version?: string): boolean;
|
|
33
33
|
/** Caller must hold the daemon start lock until restore completes. */
|
|
34
34
|
export declare function suspendDaemonForExclusiveAccessLocked(config: ResolvedDinoxConfig, timeoutMs: number, options?: {
|
|
35
35
|
snapshot?: DaemonSnapshot;
|
|
36
36
|
forceStop?: boolean;
|
|
37
|
+
deadlineAtMs?: number;
|
|
37
38
|
dependencies?: Partial<DaemonLifecycleDependencies>;
|
|
38
39
|
}): Promise<SuspendedDaemon>;
|
|
39
40
|
/** Caller must hold the daemon start lock. */
|
|
40
|
-
export declare function restoreSuspendedDaemonLocked(suspended: SuspendedDaemon, overrides?: Partial<DaemonLifecycleDependencies
|
|
41
|
+
export declare function restoreSuspendedDaemonLocked(suspended: SuspendedDaemon, overrides?: Partial<DaemonLifecycleDependencies>, options?: {
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
}): Promise<boolean>;
|
|
41
44
|
export declare function withDaemonExclusiveAccess<T>(config: ResolvedDinoxConfig, timeoutMs: number, run: (control: DaemonExclusiveAccessControl) => Promise<T>, overrides?: Partial<DaemonLifecycleDependencies>, options?: {
|
|
42
45
|
deadlineAtMs?: number;
|
|
46
|
+
operationDeadlineAtMs?: number;
|
|
47
|
+
cleanupFailureMode?: 'warn' | 'throw';
|
|
43
48
|
}): Promise<T>;
|
|
44
49
|
export {};
|
package/dist/daemon/lifecycle.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { getPowerSyncDbPath } from '../config/paths.js';
|
|
3
3
|
import { fingerprintDaemonRuntimeConfig } from '../config/resolve.js';
|
|
4
|
+
import { remainingDeadlineMs, runWithinDeadline } from '../utils/deadline.js';
|
|
5
|
+
import { DinoxError } from '../utils/errors.js';
|
|
4
6
|
import { printHumanReadableWarning } from '../utils/output.js';
|
|
5
7
|
import { getPackageVersion } from '../utils/version.js';
|
|
6
8
|
import { getDaemonInfo, isDaemonFresh, shutdownDaemonForExclusiveAccess, } from './client.js';
|
|
@@ -22,6 +24,21 @@ function dependencies(overrides) {
|
|
|
22
24
|
function errorMessage(error) {
|
|
23
25
|
return error instanceof Error ? error.message : String(error);
|
|
24
26
|
}
|
|
27
|
+
function createLifecycleDeadlineError(phase, action) {
|
|
28
|
+
return new DinoxError(`Sync deadline exceeded while ${action}.`, {
|
|
29
|
+
code: 'SYNC_TIMEOUT',
|
|
30
|
+
details: { phase },
|
|
31
|
+
suggestion: 'Run `dino doctor --format json` to verify the local runtime before retrying sync.',
|
|
32
|
+
recoverable: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function requireRemainingDeadlineMs(deadlineAtMs, maximumTimeoutMs, phase, action) {
|
|
36
|
+
const remainingMs = remainingDeadlineMs(deadlineAtMs, maximumTimeoutMs);
|
|
37
|
+
if (deadlineAtMs !== undefined && remainingMs <= 0) {
|
|
38
|
+
throw createLifecycleDeadlineError(phase, action);
|
|
39
|
+
}
|
|
40
|
+
return Math.max(1, remainingMs);
|
|
41
|
+
}
|
|
25
42
|
function snapshotPort(snapshot) {
|
|
26
43
|
const pidFileMatches = Boolean(snapshot.info
|
|
27
44
|
&& snapshot.pidInfo
|
|
@@ -29,10 +46,10 @@ function snapshotPort(snapshot) {
|
|
|
29
46
|
&& snapshot.pidInfo.instanceId === snapshot.info.instanceId);
|
|
30
47
|
return pidFileMatches ? snapshot.pidInfo?.port ?? DEFAULT_DAEMON_PORT : DEFAULT_DAEMON_PORT;
|
|
31
48
|
}
|
|
32
|
-
export async function captureDaemonSnapshot(overrides = {}) {
|
|
49
|
+
export async function captureDaemonSnapshot(overrides = {}, timeoutMs) {
|
|
33
50
|
const deps = dependencies(overrides);
|
|
34
51
|
return {
|
|
35
|
-
info: await deps.getDaemonInfo(),
|
|
52
|
+
info: await deps.getDaemonInfo(timeoutMs),
|
|
36
53
|
pidInfo: deps.readPid(),
|
|
37
54
|
socketExists: process.platform !== 'win32' && existsSync(getDaemonSocketPath()),
|
|
38
55
|
};
|
|
@@ -44,12 +61,13 @@ export function daemonSnapshotIsFresh(config, snapshot, version = getPackageVers
|
|
|
44
61
|
/** Caller must hold the daemon start lock until restore completes. */
|
|
45
62
|
export async function suspendDaemonForExclusiveAccessLocked(config, timeoutMs, options = {}) {
|
|
46
63
|
const deps = dependencies(options.dependencies ?? {});
|
|
47
|
-
const
|
|
64
|
+
const remainingTimeoutMs = (phase, action) => requireRemainingDeadlineMs(options.deadlineAtMs, timeoutMs, phase, action);
|
|
65
|
+
const snapshot = options.snapshot ?? await captureDaemonSnapshot(deps, remainingTimeoutMs('daemon-snapshot', 'inspecting the background daemon'));
|
|
48
66
|
const wasFresh = daemonSnapshotIsFresh(config, snapshot, deps.getPackageVersion());
|
|
49
67
|
const daemonUsesTargetDatabase = !snapshot.info
|
|
50
68
|
|| getPowerSyncDbPath(snapshot.info.userId) === getPowerSyncDbPath(config.auth.userId);
|
|
51
69
|
const wasStopped = options.forceStop || daemonUsesTargetDatabase
|
|
52
|
-
? await deps.shutdownDaemon(Math.min(
|
|
70
|
+
? await deps.shutdownDaemon(Math.min(remainingTimeoutMs('daemon-shutdown', 'stopping the background daemon'), 10_000), snapshot.info?.instanceId)
|
|
53
71
|
: false;
|
|
54
72
|
return {
|
|
55
73
|
snapshot,
|
|
@@ -59,22 +77,26 @@ export async function suspendDaemonForExclusiveAccessLocked(config, timeoutMs, o
|
|
|
59
77
|
};
|
|
60
78
|
}
|
|
61
79
|
/** Caller must hold the daemon start lock. */
|
|
62
|
-
export async function restoreSuspendedDaemonLocked(suspended, overrides = {}) {
|
|
80
|
+
export async function restoreSuspendedDaemonLocked(suspended, overrides = {}, options = {}) {
|
|
63
81
|
if (!suspended.wasStopped || !suspended.wasFresh) {
|
|
64
82
|
return false;
|
|
65
83
|
}
|
|
66
84
|
const deps = dependencies(overrides);
|
|
67
|
-
await deps.startDaemonLocked(suspended.port, true, {
|
|
85
|
+
await deps.startDaemonLocked(suspended.port, true, {
|
|
86
|
+
quiet: true,
|
|
87
|
+
timeoutMs: options.timeoutMs,
|
|
88
|
+
});
|
|
68
89
|
return true;
|
|
69
90
|
}
|
|
70
91
|
export async function withDaemonExclusiveAccess(config, timeoutMs, run, overrides = {}, options = {}) {
|
|
71
92
|
const deps = dependencies(overrides);
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const releaseStartLock = await deps.acquireStartLock(
|
|
93
|
+
const operationDeadlineAtMs = options.operationDeadlineAtMs ?? options.deadlineAtMs;
|
|
94
|
+
const remainingOperationTimeoutMs = (phase, action) => requireRemainingDeadlineMs(operationDeadlineAtMs, timeoutMs, phase, action);
|
|
95
|
+
const remainingCleanupTimeoutMs = () => Math.max(1, remainingDeadlineMs(options.deadlineAtMs, timeoutMs));
|
|
96
|
+
const releaseStartLock = await deps.acquireStartLock(remainingOperationTimeoutMs('daemon-lock-acquire', 'acquiring the daemon lifecycle lock'));
|
|
76
97
|
let suspended = null;
|
|
77
98
|
let restoreAllowed = true;
|
|
99
|
+
let cleanupError;
|
|
78
100
|
let outcome;
|
|
79
101
|
const control = {
|
|
80
102
|
suppressRestore() {
|
|
@@ -82,7 +104,8 @@ export async function withDaemonExclusiveAccess(config, timeoutMs, run, override
|
|
|
82
104
|
},
|
|
83
105
|
};
|
|
84
106
|
try {
|
|
85
|
-
suspended = await suspendDaemonForExclusiveAccessLocked(config,
|
|
107
|
+
suspended = await suspendDaemonForExclusiveAccessLocked(config, remainingOperationTimeoutMs('daemon-suspend', 'suspending the background daemon'), {
|
|
108
|
+
deadlineAtMs: operationDeadlineAtMs,
|
|
86
109
|
dependencies: deps,
|
|
87
110
|
});
|
|
88
111
|
try {
|
|
@@ -97,22 +120,39 @@ export async function withDaemonExclusiveAccess(config, timeoutMs, run, override
|
|
|
97
120
|
}
|
|
98
121
|
finally {
|
|
99
122
|
if (suspended && restoreAllowed) {
|
|
123
|
+
const suspendedDaemon = suspended;
|
|
100
124
|
try {
|
|
101
|
-
await restoreSuspendedDaemonLocked(
|
|
125
|
+
await runWithinDeadline(() => restoreSuspendedDaemonLocked(suspendedDaemon, deps, {
|
|
126
|
+
timeoutMs: remainingCleanupTimeoutMs(),
|
|
127
|
+
}), {
|
|
128
|
+
deadlineAtMs: options.deadlineAtMs,
|
|
129
|
+
maximumTimeoutMs: timeoutMs,
|
|
130
|
+
createTimeoutError: () => createLifecycleDeadlineError('daemon-restore', 'restoring the background daemon'),
|
|
131
|
+
});
|
|
102
132
|
}
|
|
103
133
|
catch (error) {
|
|
134
|
+
cleanupError = error;
|
|
104
135
|
deps.printWarning(`warning: failed to restore daemon after local command: ${errorMessage(error)}`);
|
|
105
136
|
}
|
|
106
137
|
}
|
|
107
138
|
try {
|
|
108
|
-
await releaseStartLock
|
|
139
|
+
await runWithinDeadline(releaseStartLock, {
|
|
140
|
+
deadlineAtMs: options.deadlineAtMs,
|
|
141
|
+
maximumTimeoutMs: timeoutMs,
|
|
142
|
+
createTimeoutError: () => createLifecycleDeadlineError('daemon-lock-release', 'releasing the daemon lifecycle lock'),
|
|
143
|
+
runWhenExpired: true,
|
|
144
|
+
});
|
|
109
145
|
}
|
|
110
146
|
catch (error) {
|
|
147
|
+
cleanupError ??= error;
|
|
111
148
|
deps.printWarning(`warning: failed to release daemon lifecycle lock: ${errorMessage(error)}`);
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
151
|
if (!outcome.ok) {
|
|
115
152
|
throw outcome.error;
|
|
116
153
|
}
|
|
154
|
+
if (cleanupError && options.cleanupFailureMode === 'throw') {
|
|
155
|
+
throw cleanupError;
|
|
156
|
+
}
|
|
117
157
|
return outcome.value;
|
|
118
158
|
}
|
package/dist/daemon/pid.d.ts
CHANGED
|
@@ -19,5 +19,5 @@ export declare function removePid(expectedInstanceId?: string, options?: {
|
|
|
19
19
|
expectedPid?: number;
|
|
20
20
|
}): boolean;
|
|
21
21
|
export declare function isProcessRunning(pid: number): boolean;
|
|
22
|
-
export declare function isLegacyDaemonProcess(pid: number, daemonUptimeMs: number): Promise<boolean>;
|
|
22
|
+
export declare function isLegacyDaemonProcess(pid: number, daemonUptimeMs: number, timeoutMs?: number): Promise<boolean>;
|
|
23
23
|
export declare function acquireDaemonStartLock(timeoutMs?: number): Promise<() => Promise<void>>;
|
package/dist/daemon/pid.js
CHANGED
|
@@ -139,10 +139,13 @@ function commandLooksLikeLegacyDaemon(command) {
|
|
|
139
139
|
return /(?:^|[\\/\s])dino(?:x)?(?:\.[cm]?[jt]s)?["']?\s+daemon\s+start(?:\s|$)/i.test(command)
|
|
140
140
|
&& /(?:^|\s)--no-detach(?:\s|$)/.test(command);
|
|
141
141
|
}
|
|
142
|
-
export async function isLegacyDaemonProcess(pid, daemonUptimeMs) {
|
|
142
|
+
export async function isLegacyDaemonProcess(pid, daemonUptimeMs, timeoutMs = 2_000) {
|
|
143
143
|
if (!Number.isInteger(pid) || pid <= 0 || !Number.isFinite(daemonUptimeMs) || daemonUptimeMs < 0) {
|
|
144
144
|
return false;
|
|
145
145
|
}
|
|
146
|
+
const commandTimeoutMs = Number.isFinite(timeoutMs)
|
|
147
|
+
? Math.max(1, Math.min(2_000, Math.floor(timeoutMs)))
|
|
148
|
+
: 2_000;
|
|
146
149
|
try {
|
|
147
150
|
if (process.platform === 'win32') {
|
|
148
151
|
const command = [
|
|
@@ -152,7 +155,7 @@ export async function isLegacyDaemonProcess(pid, daemonUptimeMs) {
|
|
|
152
155
|
' [Console]::Write($p.CommandLine);',
|
|
153
156
|
'}',
|
|
154
157
|
].join(' ');
|
|
155
|
-
const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], { encoding: 'utf8', timeout:
|
|
158
|
+
const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], { encoding: 'utf8', timeout: commandTimeoutMs, maxBuffer: 64 * 1024 });
|
|
156
159
|
const newline = stdout.indexOf('\n');
|
|
157
160
|
if (newline < 0) {
|
|
158
161
|
return false;
|
|
@@ -163,7 +166,7 @@ export async function isLegacyDaemonProcess(pid, daemonUptimeMs) {
|
|
|
163
166
|
&& Math.abs(processUptimeMs - daemonUptimeMs) <= LEGACY_DAEMON_UPTIME_TOLERANCE_MS
|
|
164
167
|
&& commandLooksLikeLegacyDaemon(stdout.slice(newline + 1).trim());
|
|
165
168
|
}
|
|
166
|
-
const { stdout } = await execFileAsync('ps', ['-ww', '-p', String(pid), '-o', 'etime=', '-o', 'command='], { encoding: 'utf8', timeout:
|
|
169
|
+
const { stdout } = await execFileAsync('ps', ['-ww', '-p', String(pid), '-o', 'etime=', '-o', 'command='], { encoding: 'utf8', timeout: commandTimeoutMs, maxBuffer: 64 * 1024 });
|
|
167
170
|
const match = stdout.trim().match(/^(\S+)\s+([\s\S]+)$/);
|
|
168
171
|
if (!match) {
|
|
169
172
|
return false;
|