libretto 0.6.13 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/auth.js +43 -33
- package/dist/cli/commands/billing.js +3 -5
- package/dist/cli/commands/browser.js +3 -6
- package/dist/cli/commands/deploy.js +54 -45
- package/dist/cli/commands/execution.js +7 -4
- package/dist/cli/commands/experiments.js +1 -1
- package/dist/cli/commands/setup.js +1 -1
- package/dist/cli/commands/shared.js +1 -1
- package/dist/cli/commands/snapshot.js +1 -1
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/core/auth-fetch.js +11 -6
- package/dist/cli/core/browser.js +10 -5
- package/dist/cli/core/daemon/daemon.js +63 -10
- package/dist/cli/core/daemon/exec-repl.js +133 -0
- package/dist/cli/core/daemon/exec.js +6 -21
- package/dist/cli/core/daemon/ipc.js +47 -4
- package/dist/cli/core/daemon/ipc.spec.js +21 -0
- package/dist/cli/core/exec-compiler.js +8 -3
- package/dist/cli/core/providers/index.js +13 -4
- package/dist/cli/core/providers/kernel.js +3 -3
- package/dist/cli/core/providers/libretto-cloud.js +178 -26
- package/dist/cli/router.js +9 -4
- package/dist/shared/ipc/socket-transport.d.ts +2 -1
- package/dist/shared/ipc/socket-transport.js +16 -5
- package/dist/shared/ipc/socket-transport.spec.js +5 -0
- package/package.json +2 -2
- package/skills/libretto/SKILL.md +33 -29
- package/skills/libretto/references/code-generation-rules.md +6 -0
- package/skills/libretto/references/configuration-file-reference.md +8 -0
- package/skills/libretto/references/site-security-review.md +6 -6
- package/skills/libretto-readonly/SKILL.md +1 -1
- package/src/cli/commands/auth.ts +46 -33
- package/src/cli/commands/billing.ts +3 -5
- package/src/cli/commands/browser.ts +5 -9
- package/src/cli/commands/deploy.ts +55 -49
- package/src/cli/commands/execution.ts +7 -4
- package/src/cli/commands/experiments.ts +1 -1
- package/src/cli/commands/setup.ts +1 -1
- package/src/cli/commands/shared.ts +1 -1
- package/src/cli/commands/snapshot.ts +1 -1
- package/src/cli/commands/status.ts +1 -1
- package/src/cli/core/auth-fetch.ts +9 -4
- package/src/cli/core/browser.ts +12 -5
- package/src/cli/core/daemon/daemon.ts +81 -9
- package/src/cli/core/daemon/exec-repl.ts +189 -0
- package/src/cli/core/daemon/exec.ts +8 -43
- package/src/cli/core/daemon/ipc.spec.ts +27 -0
- package/src/cli/core/daemon/ipc.ts +76 -7
- package/src/cli/core/exec-compiler.ts +8 -3
- package/src/cli/core/providers/index.ts +17 -4
- package/src/cli/core/providers/kernel.ts +4 -3
- package/src/cli/core/providers/libretto-cloud.ts +224 -36
- package/src/cli/router.ts +9 -4
- package/src/shared/ipc/socket-transport.spec.ts +6 -0
- package/src/shared/ipc/socket-transport.ts +20 -5
- package/dist/cli/framework/simple-cli.js +0 -880
- package/src/cli/framework/simple-cli.ts +0 -1459
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { ProviderApi } from "./types.js";
|
|
2
2
|
|
|
3
|
+
const KERNEL_API_ENDPOINT = "https://api.onkernel.com";
|
|
4
|
+
|
|
3
5
|
export function createKernelProvider(): ProviderApi {
|
|
4
6
|
const apiKey = process.env.KERNEL_API_KEY;
|
|
5
7
|
if (!apiKey)
|
|
6
8
|
throw new Error("KERNEL_API_KEY is required for Kernel provider.");
|
|
7
|
-
const endpoint = process.env.KERNEL_ENDPOINT ?? "https://api.onkernel.com";
|
|
8
9
|
|
|
9
10
|
return {
|
|
10
11
|
async createSession() {
|
|
11
|
-
const resp = await fetch(`${
|
|
12
|
+
const resp = await fetch(`${KERNEL_API_ENDPOINT}/browsers`, {
|
|
12
13
|
method: "POST",
|
|
13
14
|
headers: {
|
|
14
15
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -34,7 +35,7 @@ export function createKernelProvider(): ProviderApi {
|
|
|
34
35
|
};
|
|
35
36
|
},
|
|
36
37
|
async closeSession(sessionId) {
|
|
37
|
-
const resp = await fetch(`${
|
|
38
|
+
const resp = await fetch(`${KERNEL_API_ENDPOINT}/browsers/${sessionId}`, {
|
|
38
39
|
method: "DELETE",
|
|
39
40
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
40
41
|
});
|
|
@@ -1,20 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveHostedApiUrl } from "../auth-fetch.js";
|
|
2
2
|
import type { ProviderApi } from "./types.js";
|
|
3
3
|
|
|
4
|
+
type CloudSessionResponse = {
|
|
5
|
+
session_id: string;
|
|
6
|
+
status: string;
|
|
7
|
+
cdp_url: string | null;
|
|
8
|
+
live_view_url: string | null;
|
|
9
|
+
recording_url: string | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
13
|
+
const DEFAULT_BROWSER_SESSION_TIMEOUT_SECONDS = 3_600;
|
|
14
|
+
const QUEUE_WAIT_TIMEOUT_MS = 10 * 60_000;
|
|
15
|
+
|
|
4
16
|
export function createLibrettoCloudProvider(): ProviderApi {
|
|
5
17
|
const apiKey = process.env.LIBRETTO_API_KEY;
|
|
6
18
|
if (!apiKey)
|
|
7
19
|
throw new Error(
|
|
8
20
|
"LIBRETTO_API_KEY is required for the Libretto Cloud provider.",
|
|
9
21
|
);
|
|
10
|
-
const endpoint =
|
|
22
|
+
const endpoint = resolveHostedApiUrl();
|
|
11
23
|
|
|
12
24
|
// The Libretto Cloud API is an oRPC RPCHandler, not plain REST, so inputs
|
|
13
25
|
// must be wrapped as { json: ... } and outputs arrive the same way.
|
|
14
26
|
return {
|
|
15
27
|
async createSession() {
|
|
16
|
-
const
|
|
17
|
-
|
|
28
|
+
const browserSessionTimeoutSeconds = readPositiveNumberEnv(
|
|
29
|
+
"LIBRETTO_TIMEOUT_SECONDS",
|
|
30
|
+
DEFAULT_BROWSER_SESSION_TIMEOUT_SECONDS,
|
|
18
31
|
);
|
|
19
32
|
const resp = await fetch(`${endpoint}/v1/sessions/create`, {
|
|
20
33
|
method: "POST",
|
|
@@ -23,48 +36,223 @@ export function createLibrettoCloudProvider(): ProviderApi {
|
|
|
23
36
|
"Content-Type": "application/json",
|
|
24
37
|
},
|
|
25
38
|
body: JSON.stringify({
|
|
26
|
-
json: { timeout_seconds:
|
|
39
|
+
json: { timeout_seconds: browserSessionTimeoutSeconds },
|
|
27
40
|
}),
|
|
28
41
|
});
|
|
29
42
|
if (!resp.ok) {
|
|
30
43
|
const body = await resp.text();
|
|
31
|
-
throw new Error(
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
throw new Error(`Libretto Cloud API error (${resp.status}): ${body}`);
|
|
45
|
+
}
|
|
46
|
+
const { json } = (await resp.json()) as { json: CloudSessionResponse };
|
|
47
|
+
const startupCleanup = createStartupSessionCleanup(
|
|
48
|
+
endpoint,
|
|
49
|
+
apiKey,
|
|
50
|
+
json.session_id,
|
|
51
|
+
);
|
|
52
|
+
let readySession: CloudSessionResponse & { cdp_url: string };
|
|
53
|
+
try {
|
|
54
|
+
readySession = await waitForCloudSessionReady({
|
|
55
|
+
endpoint,
|
|
56
|
+
apiKey,
|
|
57
|
+
session: json,
|
|
58
|
+
timeoutMs: QUEUE_WAIT_TIMEOUT_MS,
|
|
59
|
+
isCancelled: startupCleanup.isCancelled,
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (startupCleanup.isCancelled()) {
|
|
63
|
+
await startupCleanup.waitForClose();
|
|
64
|
+
} else {
|
|
65
|
+
await closeCloudSession(endpoint, apiKey, json.session_id).catch(
|
|
66
|
+
() => {},
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
} finally {
|
|
71
|
+
startupCleanup.dispose();
|
|
34
72
|
}
|
|
35
|
-
const { json } = (await resp.json()) as {
|
|
36
|
-
json: {
|
|
37
|
-
session_id: string;
|
|
38
|
-
cdp_url: string;
|
|
39
|
-
live_view_url: string | null;
|
|
40
|
-
recording_url: string | null;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
73
|
return {
|
|
44
|
-
sessionId:
|
|
45
|
-
cdpEndpoint:
|
|
46
|
-
liveViewUrl:
|
|
74
|
+
sessionId: readySession.session_id,
|
|
75
|
+
cdpEndpoint: readySession.cdp_url,
|
|
76
|
+
liveViewUrl: readySession.live_view_url ?? undefined,
|
|
47
77
|
};
|
|
48
78
|
},
|
|
49
79
|
async closeSession(sessionId) {
|
|
50
|
-
const
|
|
51
|
-
method: "POST",
|
|
52
|
-
headers: {
|
|
53
|
-
"x-api-key": apiKey,
|
|
54
|
-
"Content-Type": "application/json",
|
|
55
|
-
},
|
|
56
|
-
body: JSON.stringify({ json: { session_id: sessionId } }),
|
|
57
|
-
});
|
|
58
|
-
if (!resp.ok) {
|
|
59
|
-
const body = await resp.text();
|
|
60
|
-
throw new Error(
|
|
61
|
-
`Libretto Cloud API error closing session ${sessionId} (${resp.status}): ${body}`,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
const { json } = (await resp.json()) as {
|
|
65
|
-
json: { replay_url: string | null };
|
|
66
|
-
};
|
|
80
|
+
const json = await closeCloudSession(endpoint, apiKey, sessionId);
|
|
67
81
|
return { replayUrl: json.replay_url ?? undefined };
|
|
68
82
|
},
|
|
69
83
|
};
|
|
70
84
|
}
|
|
85
|
+
|
|
86
|
+
async function waitForCloudSessionReady(args: {
|
|
87
|
+
endpoint: string;
|
|
88
|
+
apiKey: string;
|
|
89
|
+
session: CloudSessionResponse;
|
|
90
|
+
timeoutMs: number;
|
|
91
|
+
isCancelled?: () => boolean;
|
|
92
|
+
}): Promise<CloudSessionResponse & { cdp_url: string }> {
|
|
93
|
+
let session = args.session;
|
|
94
|
+
if (args.isCancelled?.()) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Libretto Cloud session ${session.session_id} was cancelled before browser capacity was available.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (session.cdp_url) {
|
|
100
|
+
return { ...session, cdp_url: session.cdp_url };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
sendStartupStatus(
|
|
104
|
+
`Libretto Cloud browser session queued (session: ${session.session_id}). Waiting for browser capacity...`,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const pollIntervalMs = readPositiveNumberEnv(
|
|
108
|
+
"LIBRETTO_CLOUD_SESSION_POLL_INTERVAL_MS",
|
|
109
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
110
|
+
);
|
|
111
|
+
const deadline = Date.now() + args.timeoutMs;
|
|
112
|
+
|
|
113
|
+
while (Date.now() < deadline) {
|
|
114
|
+
if (args.isCancelled?.()) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Libretto Cloud session ${session.session_id} was cancelled before browser capacity was available.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
await sleep(pollIntervalMs);
|
|
120
|
+
if (args.isCancelled?.()) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Libretto Cloud session ${session.session_id} was cancelled before browser capacity was available.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
session = await getCloudSession(
|
|
126
|
+
args.endpoint,
|
|
127
|
+
args.apiKey,
|
|
128
|
+
session.session_id,
|
|
129
|
+
);
|
|
130
|
+
if (session.cdp_url) {
|
|
131
|
+
sendStartupStatus(
|
|
132
|
+
`Libretto Cloud browser capacity available (session: ${session.session_id}). Connecting...`,
|
|
133
|
+
);
|
|
134
|
+
return { ...session, cdp_url: session.cdp_url };
|
|
135
|
+
}
|
|
136
|
+
if (!["queued", "starting"].includes(session.status)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Libretto Cloud session ${session.session_id} entered status "${session.status}" before a CDP URL was available.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Timed out waiting for Libretto Cloud browser capacity after ${Math.ceil(args.timeoutMs / 1_000)}s (session: ${session.session_id}).`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function getCloudSession(
|
|
149
|
+
endpoint: string,
|
|
150
|
+
apiKey: string,
|
|
151
|
+
sessionId: string,
|
|
152
|
+
): Promise<CloudSessionResponse> {
|
|
153
|
+
const resp = await fetch(`${endpoint}/v1/sessions/get`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: {
|
|
156
|
+
"x-api-key": apiKey,
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify({ json: { session_id: sessionId } }),
|
|
160
|
+
});
|
|
161
|
+
if (!resp.ok) {
|
|
162
|
+
const body = await resp.text();
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Libretto Cloud API error reading session ${sessionId} (${resp.status}): ${body}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const { json } = (await resp.json()) as { json: CloudSessionResponse };
|
|
168
|
+
return json;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function closeCloudSession(
|
|
172
|
+
endpoint: string,
|
|
173
|
+
apiKey: string,
|
|
174
|
+
sessionId: string,
|
|
175
|
+
): Promise<{ replay_url: string | null }> {
|
|
176
|
+
const resp = await fetch(`${endpoint}/v1/sessions/close`, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: {
|
|
179
|
+
"x-api-key": apiKey,
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({ json: { session_id: sessionId } }),
|
|
183
|
+
});
|
|
184
|
+
if (!resp.ok) {
|
|
185
|
+
const body = await resp.text();
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Libretto Cloud API error closing session ${sessionId} (${resp.status}): ${body}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const { json } = (await resp.json()) as {
|
|
191
|
+
json: { replay_url: string | null };
|
|
192
|
+
};
|
|
193
|
+
return json;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function createStartupSessionCleanup(
|
|
197
|
+
endpoint: string,
|
|
198
|
+
apiKey: string,
|
|
199
|
+
sessionId: string,
|
|
200
|
+
): {
|
|
201
|
+
isCancelled: () => boolean;
|
|
202
|
+
waitForClose: () => Promise<void>;
|
|
203
|
+
dispose: () => void;
|
|
204
|
+
} {
|
|
205
|
+
let cancelled = false;
|
|
206
|
+
let closePromise: Promise<void> | null = null;
|
|
207
|
+
|
|
208
|
+
const requestClose = (reason: string): void => {
|
|
209
|
+
if (cancelled) return;
|
|
210
|
+
cancelled = true;
|
|
211
|
+
sendStartupStatus(
|
|
212
|
+
`Libretto Cloud browser session cancelled (${reason}). Cleaning up queued session...`,
|
|
213
|
+
);
|
|
214
|
+
closePromise = closeCloudSession(endpoint, apiKey, sessionId).then(
|
|
215
|
+
() => {},
|
|
216
|
+
() => {},
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const onDisconnect = (): void => requestClose("parent command disconnected");
|
|
221
|
+
const onSigint = (): void => requestClose("received SIGINT");
|
|
222
|
+
const onSigterm = (): void => requestClose("received SIGTERM");
|
|
223
|
+
|
|
224
|
+
if (typeof process.send === "function") {
|
|
225
|
+
process.once("disconnect", onDisconnect);
|
|
226
|
+
}
|
|
227
|
+
process.once("SIGINT", onSigint);
|
|
228
|
+
process.once("SIGTERM", onSigterm);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
isCancelled: () => cancelled,
|
|
232
|
+
waitForClose: async () => {
|
|
233
|
+
await closePromise;
|
|
234
|
+
},
|
|
235
|
+
dispose: () => {
|
|
236
|
+
process.off("disconnect", onDisconnect);
|
|
237
|
+
process.off("SIGINT", onSigint);
|
|
238
|
+
process.off("SIGTERM", onSigterm);
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function readPositiveNumberEnv(name: string, fallback: number): number {
|
|
244
|
+
const raw = process.env[name];
|
|
245
|
+
if (!raw) return fallback;
|
|
246
|
+
const parsed = Number(raw);
|
|
247
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function sendStartupStatus(message: string): void {
|
|
251
|
+
if (typeof process.send === "function") {
|
|
252
|
+
process.send({ type: "startup-status", message });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function sleep(ms: number): Promise<void> {
|
|
257
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
258
|
+
}
|
package/src/cli/router.ts
CHANGED
|
@@ -8,15 +8,20 @@ import { setupCommand } from "./commands/setup.js";
|
|
|
8
8
|
import { statusCommand } from "./commands/status.js";
|
|
9
9
|
import { snapshotCommand } from "./commands/snapshot.js";
|
|
10
10
|
import { librettoCommand } from "../shared/package-manager.js";
|
|
11
|
-
import { SimpleCLI } from "
|
|
11
|
+
import { SimpleCLI } from "affordance";
|
|
12
12
|
|
|
13
13
|
export const cliRoutes = {
|
|
14
14
|
...browserCommands,
|
|
15
|
-
|
|
15
|
+
cloud: SimpleCLI.group({
|
|
16
|
+
description: "Libretto Cloud commands",
|
|
17
|
+
routes: {
|
|
18
|
+
deploy: deployCommand,
|
|
19
|
+
auth: authCommands,
|
|
20
|
+
billing: billingCommands,
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
16
23
|
experiments: experimentsCommand,
|
|
17
24
|
...executionCommands,
|
|
18
|
-
auth: authCommands,
|
|
19
|
-
billing: billingCommands,
|
|
20
25
|
setup: setupCommand,
|
|
21
26
|
status: statusCommand,
|
|
22
27
|
snapshot: snapshotCommand,
|
|
@@ -6,6 +6,7 @@ import { expect, test as base } from "vitest";
|
|
|
6
6
|
import { createIpcPeer, type IpcPeer } from "./ipc.js";
|
|
7
7
|
import {
|
|
8
8
|
connectToIpcSocket,
|
|
9
|
+
isWindowsNamedPipePath,
|
|
9
10
|
listenForIpcConnections,
|
|
10
11
|
} from "./socket-transport.js";
|
|
11
12
|
|
|
@@ -64,6 +65,11 @@ test("sends concurrent calls over one socket", async ({ socketPath }) => {
|
|
|
64
65
|
await expect(stat(socketPath)).rejects.toThrow();
|
|
65
66
|
});
|
|
66
67
|
|
|
68
|
+
test("recognizes Windows named pipe paths", () => {
|
|
69
|
+
expect(isWindowsNamedPipePath("\\\\.\\pipe\\libretto-abc123")).toBe(true);
|
|
70
|
+
expect(isWindowsNamedPipePath("/tmp/libretto-501-abc123.sock")).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
67
73
|
test("rejects pending calls when the socket closes", async ({ socketPath }) => {
|
|
68
74
|
const serverPeers: Array<IpcPeer<ClientApi>> = [];
|
|
69
75
|
const server = await listenForIpcConnections(socketPath, (transport) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rm } from "node:fs/promises";
|
|
1
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
2
2
|
import {
|
|
3
3
|
createServer,
|
|
4
4
|
createConnection,
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
type Socket,
|
|
7
7
|
} from "node:net";
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
|
-
import { mkdir } from "node:fs/promises";
|
|
10
9
|
import type { IpcProtocolMessage, IpcTransport } from "./ipc.js";
|
|
11
10
|
|
|
12
11
|
function createJsonSocketTransport(
|
|
@@ -109,13 +108,12 @@ export async function listenOnIpcSocket(
|
|
|
109
108
|
server: Server,
|
|
110
109
|
socketPath: string,
|
|
111
110
|
): Promise<void> {
|
|
112
|
-
await
|
|
113
|
-
await rm(socketPath, { force: true });
|
|
111
|
+
await prepareIpcSocketPath(socketPath);
|
|
114
112
|
|
|
115
113
|
const originalClose = server.close.bind(server);
|
|
116
114
|
server.close = ((callback?: (error?: Error) => void) => {
|
|
117
115
|
return originalClose((error?: Error) => {
|
|
118
|
-
void
|
|
116
|
+
void removeStaleSocketFile(socketPath).finally(() => callback?.(error));
|
|
119
117
|
});
|
|
120
118
|
}) as Server["close"];
|
|
121
119
|
|
|
@@ -135,6 +133,23 @@ export async function listenOnIpcSocket(
|
|
|
135
133
|
});
|
|
136
134
|
}
|
|
137
135
|
|
|
136
|
+
export function isWindowsNamedPipePath(socketPath: string): boolean {
|
|
137
|
+
return socketPath.startsWith("\\\\.\\pipe\\");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function prepareIpcSocketPath(socketPath: string): Promise<void> {
|
|
141
|
+
if (isWindowsNamedPipePath(socketPath)) return;
|
|
142
|
+
|
|
143
|
+
await mkdir(dirname(socketPath), { recursive: true });
|
|
144
|
+
await removeStaleSocketFile(socketPath);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function removeStaleSocketFile(socketPath: string): Promise<void> {
|
|
148
|
+
if (isWindowsNamedPipePath(socketPath)) return;
|
|
149
|
+
|
|
150
|
+
await rm(socketPath, { force: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
138
153
|
async function connectSocket(socketPath: string): Promise<Socket> {
|
|
139
154
|
const socket = createConnection(socketPath);
|
|
140
155
|
|