@runalabs/rill-cli 0.1.13 → 0.1.14
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 +13 -7
- package/dist/cli.js +790 -296
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,23 +5,109 @@ var __export = (target, all) => {
|
|
|
5
5
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// src/windows-credentials.ts
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import { createHash, randomUUID } from "crypto";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { join, win32 } from "path";
|
|
13
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
14
|
+
var script = String.raw`$ErrorActionPreference='Stop'; try {
|
|
15
|
+
Add-Type -AssemblyName System.Security
|
|
16
|
+
$inputData=[Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
17
|
+
$bytes=[Convert]::FromBase64String($inputData.data)
|
|
18
|
+
$entropy=[Convert]::FromBase64String($inputData.entropy)
|
|
19
|
+
if ($inputData.operation -eq 'protect') {
|
|
20
|
+
$result=[System.Security.Cryptography.ProtectedData]::Protect($bytes,$entropy,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)
|
|
21
|
+
} elseif ($inputData.operation -eq 'unprotect') {
|
|
22
|
+
$result=[System.Security.Cryptography.ProtectedData]::Unprotect($bytes,$entropy,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)
|
|
23
|
+
} else { throw 'Invalid operation' }
|
|
24
|
+
[Console]::Out.Write([Convert]::ToBase64String($result))
|
|
25
|
+
} catch { [Console]::Error.Write('Windows credential protection failed.'); exit 1 }`;
|
|
26
|
+
var transformWindowsSecret = async (operation, data, entropy) => {
|
|
27
|
+
if (process.platform !== "win32") throw new Error("Windows credential protection requires Windows.");
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const executable = win32.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
30
|
+
const child = spawn(executable, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
31
|
+
let output = "";
|
|
32
|
+
let failed = false;
|
|
33
|
+
const fail2 = () => {
|
|
34
|
+
if (failed) return;
|
|
35
|
+
failed = true;
|
|
36
|
+
child.kill();
|
|
37
|
+
reject(new Error("Cannot access Windows protected credentials. Your saved identity has not been replaced."));
|
|
38
|
+
};
|
|
39
|
+
const timer = setTimeout(fail2, 15e3);
|
|
40
|
+
child.stdout.on("data", (chunk) => {
|
|
41
|
+
output += chunk.toString("ascii");
|
|
42
|
+
if (output.length > 256 * 1024) fail2();
|
|
43
|
+
});
|
|
44
|
+
child.stderr.resume();
|
|
45
|
+
child.once("error", fail2);
|
|
46
|
+
child.stdin.once("error", fail2);
|
|
47
|
+
child.once("close", (code) => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
if (failed) return;
|
|
50
|
+
if (code !== 0 || !output || !/^[A-Za-z0-9+/]+={0,2}$/.test(output)) {
|
|
51
|
+
fail2();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
resolve(Buffer.from(output, "base64"));
|
|
55
|
+
});
|
|
56
|
+
child.stdin.end(JSON.stringify({ operation, data: data.toString("base64"), entropy: entropy.toString("base64") }));
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
var credentialRoot = () => join(process.env.RILL_HOME ?? join(homedir(), ".rill"), "windows-credentials");
|
|
60
|
+
var key = (apiUrl) => createHash("sha256").update(apiUrl).digest();
|
|
61
|
+
async function saveWindowsCredential(apiUrl, value, root = credentialRoot(), transform2 = transformWindowsSecret) {
|
|
62
|
+
if (!value || Buffer.byteLength(value) > 64 * 1024) throw new Error("The Windows credential is empty or too large.");
|
|
63
|
+
const entropy = key(apiUrl), protectedBytes = await transform2("protect", Buffer.from(value, "utf8"), entropy);
|
|
64
|
+
if (!protectedBytes.length || protectedBytes.length > 192 * 1024) throw new Error("Windows credential protection returned invalid data.");
|
|
65
|
+
await mkdir(root, { recursive: true, mode: 448 });
|
|
66
|
+
const path = join(root, entropy.toString("hex") + ".dpapi"), temporary = path + "." + randomUUID() + ".tmp";
|
|
67
|
+
try {
|
|
68
|
+
await writeFile(temporary, JSON.stringify({ version: 1, ciphertext: protectedBytes.toString("base64") }), { mode: 384, flag: "wx" });
|
|
69
|
+
await rename(temporary, path);
|
|
70
|
+
} finally {
|
|
71
|
+
await rm(temporary, { force: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function loadWindowsCredential(apiUrl, root = credentialRoot(), transform2 = transformWindowsSecret) {
|
|
75
|
+
const entropy = key(apiUrl), path = join(root, entropy.toString("hex") + ".dpapi");
|
|
76
|
+
try {
|
|
77
|
+
const info = await stat(path);
|
|
78
|
+
if (!info.isFile() || info.size > 256 * 1024) throw new Error("Invalid credential file.");
|
|
79
|
+
const stored = JSON.parse(await readFile(path, "utf8"));
|
|
80
|
+
if (stored.version !== 1 || typeof stored.ciphertext !== "string" || !stored.ciphertext || !/^[A-Za-z0-9+/]+={0,2}$/.test(stored.ciphertext)) throw new Error("Invalid protected credential.");
|
|
81
|
+
const plain = await transform2("unprotect", Buffer.from(stored.ciphertext, "base64"), entropy);
|
|
82
|
+
if (!plain.length || plain.length > 64 * 1024) throw new Error("Invalid credential size.");
|
|
83
|
+
return plain.toString("utf8");
|
|
84
|
+
} catch (error51) {
|
|
85
|
+
if (error51.code === "ENOENT") return null;
|
|
86
|
+
throw new Error("Cannot read Windows protected credentials. Your saved identity has not been replaced.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
8
90
|
// src/agent-session.ts
|
|
9
|
-
import { hostname, homedir } from "os";
|
|
10
|
-
import { mkdir, rm, stat } from "fs/promises";
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
import { createHash, randomBytes } from "crypto";
|
|
91
|
+
import { hostname, homedir as homedir2 } from "os";
|
|
92
|
+
import { mkdir as mkdir2, rm as rm2, stat as stat2 } from "fs/promises";
|
|
93
|
+
import { join as join2 } from "path";
|
|
94
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
13
95
|
|
|
14
96
|
// src/recording-guidance.ts
|
|
15
|
-
function
|
|
16
|
-
const quote = (value) => /^[a-zA-Z0-9_./:@=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
97
|
+
function cliCommandForPlatform(platform, apiUrl, ...args) {
|
|
98
|
+
const quote = (value) => /^[a-zA-Z0-9_./:@=-]+$/.test(value) ? value : platform === "win32" ? `'${value.replaceAll("'", "''")}'` : `'${value.replaceAll("'", "'\\''")}'`;
|
|
17
99
|
return ["rill", "--api-url", apiUrl, ...args].map(quote).join(" ");
|
|
18
100
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
101
|
+
function cliCommand(apiUrl, ...args) {
|
|
102
|
+
return cliCommandForPlatform(process.platform, apiUrl, ...args);
|
|
103
|
+
}
|
|
104
|
+
var transportRecovery = "Unset RILL_SOCKET on Windows so Rill can use its authenticated named pipe. Use RILL_HOME to choose a separate recorder identity directory.";
|
|
105
|
+
function recorderTransportCheck(platform = process.platform, socketOverride = process.env.RILL_SOCKET) {
|
|
106
|
+
if (platform === "win32" && socketOverride) return { name: "recorder_transport", status: "fail", message: "Windows requires the authenticated recorder pipe.", recovery: transportRecovery };
|
|
107
|
+
return { name: "recorder_transport", status: "pass", message: platform === "win32" ? "The local recorder uses an authenticated Windows named pipe." : "The local recorder uses a Unix-domain socket." };
|
|
22
108
|
}
|
|
23
|
-
function assertRecorderTransport(platform = process.platform) {
|
|
24
|
-
const check2 = recorderTransportCheck(platform);
|
|
109
|
+
function assertRecorderTransport(platform = process.platform, socketOverride = process.env.RILL_SOCKET) {
|
|
110
|
+
const check2 = recorderTransportCheck(platform, socketOverride);
|
|
25
111
|
if (check2.status === "fail") throw Object.assign(new Error(`unsupported_platform: ${check2.message}`), { code: "unsupported_platform", recovery: check2.recovery });
|
|
26
112
|
}
|
|
27
113
|
function recordingNextStep(apiUrl, recordingId, status, options = {}) {
|
|
@@ -48,6 +134,7 @@ var AgentAuthError = class extends Error {
|
|
|
48
134
|
super(`${code}: ${message}`);
|
|
49
135
|
this.code = code;
|
|
50
136
|
}
|
|
137
|
+
code;
|
|
51
138
|
};
|
|
52
139
|
function parseAuthorization(value) {
|
|
53
140
|
if (!value?.startsWith("{")) return null;
|
|
@@ -63,7 +150,7 @@ async function request(apiUrl, path, body, form = false) {
|
|
|
63
150
|
redirect: "error",
|
|
64
151
|
signal: AbortSignal.timeout(15e3),
|
|
65
152
|
headers: { "Content-Type": form ? "application/x-www-form-urlencoded" : "application/json" },
|
|
66
|
-
body: form ? new URLSearchParams(Object.entries(body).map(([
|
|
153
|
+
body: form ? new URLSearchParams(Object.entries(body).map(([key2, value2]) => [key2, String(value2)])) : JSON.stringify(body)
|
|
67
154
|
});
|
|
68
155
|
const value = await response.json();
|
|
69
156
|
if (!response.ok) throw new AgentAuthError(value.error ?? "authentication_failed", value.error_description ?? "Could not authorize Rill.");
|
|
@@ -107,19 +194,19 @@ async function ensureAnonymousCredential(apiUrl) {
|
|
|
107
194
|
return withAuthorizationLock(apiUrl, () => createAnonymousCredential(apiUrl));
|
|
108
195
|
}
|
|
109
196
|
async function withAuthorizationLock(apiUrl, action) {
|
|
110
|
-
const root =
|
|
111
|
-
await
|
|
112
|
-
const lock =
|
|
197
|
+
const root = join2(homedir2(), ".rill", "auth-locks");
|
|
198
|
+
await mkdir2(root, { recursive: true, mode: 448 });
|
|
199
|
+
const lock = join2(root, createHash2("sha256").update(apiUrl).digest("hex"));
|
|
113
200
|
const deadline = Date.now() + 3e4;
|
|
114
201
|
while (true) {
|
|
115
202
|
try {
|
|
116
|
-
await
|
|
203
|
+
await mkdir2(lock, { mode: 448 });
|
|
117
204
|
break;
|
|
118
205
|
} catch (error51) {
|
|
119
206
|
if (error51.code !== "EEXIST") throw error51;
|
|
120
|
-
const info = await
|
|
207
|
+
const info = await stat2(lock).catch(() => null);
|
|
121
208
|
if (info && Date.now() - info.mtimeMs > 12e4) {
|
|
122
|
-
await
|
|
209
|
+
await rm2(lock, { recursive: true, force: true });
|
|
123
210
|
continue;
|
|
124
211
|
}
|
|
125
212
|
if (Date.now() > deadline) throw new Error("Another Rill process is setting up authorization. Retry shortly.");
|
|
@@ -129,7 +216,7 @@ async function withAuthorizationLock(apiUrl, action) {
|
|
|
129
216
|
try {
|
|
130
217
|
return await action();
|
|
131
218
|
} finally {
|
|
132
|
-
await
|
|
219
|
+
await rm2(lock, { recursive: true, force: true });
|
|
133
220
|
}
|
|
134
221
|
}
|
|
135
222
|
async function createAnonymousCredential(apiUrl) {
|
|
@@ -246,9 +333,10 @@ async function pollSavedConnection(apiUrl, authorizationId) {
|
|
|
246
333
|
}
|
|
247
334
|
|
|
248
335
|
// src/keychain.ts
|
|
249
|
-
import { spawn } from "child_process";
|
|
336
|
+
import { spawn as spawn2 } from "child_process";
|
|
250
337
|
var service = "rill";
|
|
251
338
|
async function saveCredential(apiUrl, token) {
|
|
339
|
+
if (process.platform === "win32") return saveWindowsCredential(apiUrl, token);
|
|
252
340
|
if (process.platform === "darwin") {
|
|
253
341
|
await run("security", ["add-generic-password", "-U", "-a", apiUrl, "-s", service, "-w", token]);
|
|
254
342
|
return;
|
|
@@ -263,6 +351,7 @@ function loadCredential(apiUrl) {
|
|
|
263
351
|
return loadAgentCredential(apiUrl);
|
|
264
352
|
}
|
|
265
353
|
async function loadStoredCredential(apiUrl) {
|
|
354
|
+
if (process.platform === "win32") return loadWindowsCredential(apiUrl);
|
|
266
355
|
try {
|
|
267
356
|
if (process.platform === "darwin") return (await run("security", ["find-generic-password", "-a", apiUrl, "-s", service, "-w"])).trim() || null;
|
|
268
357
|
if (process.platform === "linux") return (await run("secret-tool", ["lookup", "service", service, "api-url", apiUrl])).trim() || null;
|
|
@@ -276,7 +365,7 @@ async function loadStoredCredential(apiUrl) {
|
|
|
276
365
|
}
|
|
277
366
|
function run(command, args, input) {
|
|
278
367
|
return new Promise((resolve, reject) => {
|
|
279
|
-
const child =
|
|
368
|
+
const child = spawn2(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
280
369
|
const stdout = [];
|
|
281
370
|
const stderr = [];
|
|
282
371
|
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
|
@@ -290,7 +379,7 @@ function run(command, args, input) {
|
|
|
290
379
|
|
|
291
380
|
// src/control-plane.ts
|
|
292
381
|
import { createReadStream } from "fs";
|
|
293
|
-
import { readFile, stat as
|
|
382
|
+
import { readFile as readFile2, stat as stat3 } from "fs/promises";
|
|
294
383
|
import { Upload } from "tus-js-client";
|
|
295
384
|
var ControlPlaneError = class extends Error {
|
|
296
385
|
constructor(code, message, recovery, details, httpStatus) {
|
|
@@ -301,6 +390,10 @@ var ControlPlaneError = class extends Error {
|
|
|
301
390
|
this.httpStatus = httpStatus;
|
|
302
391
|
this.name = "ControlPlaneError";
|
|
303
392
|
}
|
|
393
|
+
code;
|
|
394
|
+
recovery;
|
|
395
|
+
details;
|
|
396
|
+
httpStatus;
|
|
304
397
|
};
|
|
305
398
|
var ControlPlaneHttpError = class extends Error {
|
|
306
399
|
constructor(httpStatus) {
|
|
@@ -308,12 +401,15 @@ var ControlPlaneHttpError = class extends Error {
|
|
|
308
401
|
this.httpStatus = httpStatus;
|
|
309
402
|
this.name = "ControlPlaneHttpError";
|
|
310
403
|
}
|
|
404
|
+
httpStatus;
|
|
311
405
|
};
|
|
312
406
|
var ControlPlaneClient = class {
|
|
313
407
|
constructor(apiUrl, token) {
|
|
314
408
|
this.apiUrl = apiUrl;
|
|
315
409
|
this.token = token;
|
|
316
410
|
}
|
|
411
|
+
apiUrl;
|
|
412
|
+
token;
|
|
317
413
|
recordingStatusCommand(id) {
|
|
318
414
|
return cliCommand(this.apiUrl, "record", "status", id);
|
|
319
415
|
}
|
|
@@ -402,7 +498,7 @@ var ControlPlaneClient = class {
|
|
|
402
498
|
throw new ControlPlaneError("processing_pending", "Upload complete; the recording is not playable yet.", `Run ${cliCommand(this.apiUrl, "record", "status", recordingId)} to continue checking.`, { recordingId, nextCommand: cliCommand(this.apiUrl, "record", "status", recordingId) });
|
|
403
499
|
}
|
|
404
500
|
async uploadVideo(uploadUrl, filePath) {
|
|
405
|
-
const file2 = await
|
|
501
|
+
const file2 = await stat3(filePath);
|
|
406
502
|
if (uploadUrl.startsWith(this.apiUrl)) {
|
|
407
503
|
const response = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Length": String(file2.size) }, body: createReadStream(filePath), duplex: "half" });
|
|
408
504
|
if (!response.ok) throw new Error(`Video upload failed (${response.status}).`);
|
|
@@ -424,7 +520,7 @@ var ControlPlaneClient = class {
|
|
|
424
520
|
process.stderr.write("\nUpload complete; playback processing may continue.\n");
|
|
425
521
|
}
|
|
426
522
|
async uploadDiagnostics(recordingId, filePath, summary) {
|
|
427
|
-
const body = await
|
|
523
|
+
const body = await readFile2(filePath);
|
|
428
524
|
return this.request(`/api/recordings/${recordingId}/diagnostics`, { method: "PUT", headers: { "Content-Encoding": "gzip", "Content-Type": "application/x-ndjson", "Content-Length": String(body.length), "X-Rill-Summary": JSON.stringify(summary) }, body });
|
|
429
525
|
}
|
|
430
526
|
listRecordings() {
|
|
@@ -436,16 +532,33 @@ var ControlPlaneClient = class {
|
|
|
436
532
|
pollDeviceAuthorization(deviceCode) {
|
|
437
533
|
return this.request("/api/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) }, false);
|
|
438
534
|
}
|
|
439
|
-
inspect(shareUrl, cursor) {
|
|
535
|
+
async inspect(shareUrl, cursor) {
|
|
440
536
|
if (/^rec_[a-zA-Z0-9_-]+$/.test(shareUrl)) return this.request(`/api/recordings/${encodeURIComponent(shareUrl)}`);
|
|
441
537
|
const url2 = new URL(shareUrl);
|
|
442
538
|
const slug = url2.pathname.split("/").filter(Boolean).at(-1);
|
|
443
539
|
if (!slug) throw new Error("The share URL does not contain a slug.");
|
|
444
|
-
return fetch(`${url2.origin}/api/public/shares/${encodeURIComponent(slug)}/context${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`, { headers:
|
|
540
|
+
return fetch(`${url2.origin}/api/public/shares/${encodeURIComponent(slug)}/context${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`, { headers: await this.reviewHeaders(url2.origin) }).then(async (response) => {
|
|
445
541
|
if (!response.ok) throw new Error(`Could not inspect the share link (${response.status}).`);
|
|
446
542
|
return response.json();
|
|
447
543
|
});
|
|
448
544
|
}
|
|
545
|
+
async reviewHeaders(origin) {
|
|
546
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
547
|
+
if (origin !== new URL(this.apiUrl).origin) return headers;
|
|
548
|
+
const token = typeof this.token === "function" ? await this.token() : this.token;
|
|
549
|
+
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
550
|
+
this.identifyAgent(headers);
|
|
551
|
+
const id = process.env.RILL_AGENT_ID;
|
|
552
|
+
if (id && /^[a-zA-Z0-9_-]{8,80}$/.test(id)) headers.set("x-rill-agent-id", id);
|
|
553
|
+
if (id && process.env.RILL_ANONYMOUS_ANALYTICS !== "0" && process.env.DO_NOT_TRACK !== "1") headers.set("x-rill-analytics", "1");
|
|
554
|
+
return headers;
|
|
555
|
+
}
|
|
556
|
+
identifyAgent(headers) {
|
|
557
|
+
if (process.env.DO_NOT_TRACK === "1" || process.env.RILL_ANONYMOUS_ANALYTICS === "0") headers.set("x-rill-analytics", "0");
|
|
558
|
+
const client = process.env.RILL_AGENT_CLIENT || (process.env.CODEX_THREAD_ID ? "Codex" : process.env.CLAUDECODE ? "Claude" : process.env.CURSOR_TRACE_ID ? "Cursor" : void 0);
|
|
559
|
+
if (client) headers.set("x-rill-agent-client", client.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 80));
|
|
560
|
+
if (process.env.RILL_AGENT_NAME) headers.set("x-rill-agent-name", process.env.RILL_AGENT_NAME.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 48));
|
|
561
|
+
}
|
|
449
562
|
reviewPath(shareUrl) {
|
|
450
563
|
const url2 = new URL(shareUrl);
|
|
451
564
|
if (url2.origin !== new URL(this.apiUrl).origin) throw new Error("The share URL must match --api-url before sending agent credentials.");
|
|
@@ -461,6 +574,7 @@ var ControlPlaneClient = class {
|
|
|
461
574
|
}
|
|
462
575
|
async request(path, init = {}, authenticated = true) {
|
|
463
576
|
const headers = new Headers(init.headers);
|
|
577
|
+
if (authenticated) this.identifyAgent(headers);
|
|
464
578
|
if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
|
|
465
579
|
if (authenticated) {
|
|
466
580
|
const token = typeof this.token === "function" ? await this.token() : this.token;
|
|
@@ -500,8 +614,8 @@ async function whoami(apiUrl) {
|
|
|
500
614
|
}
|
|
501
615
|
|
|
502
616
|
// src/remote-encoding.ts
|
|
503
|
-
import { open, readFile as
|
|
504
|
-
import { join as
|
|
617
|
+
import { open, readFile as readFile3 } from "fs/promises";
|
|
618
|
+
import { join as join3 } from "path";
|
|
505
619
|
|
|
506
620
|
// ../../node_modules/zod/v4/classic/external.js
|
|
507
621
|
var external_exports = {};
|
|
@@ -1225,9 +1339,9 @@ function floatSafeRemainder(val, step) {
|
|
|
1225
1339
|
return ratio - roundedRatio;
|
|
1226
1340
|
}
|
|
1227
1341
|
var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
|
|
1228
|
-
function defineLazy(object2,
|
|
1342
|
+
function defineLazy(object2, key2, getter) {
|
|
1229
1343
|
let value = void 0;
|
|
1230
|
-
Object.defineProperty(object2,
|
|
1344
|
+
Object.defineProperty(object2, key2, {
|
|
1231
1345
|
get() {
|
|
1232
1346
|
if (value === EVALUATING) {
|
|
1233
1347
|
return void 0;
|
|
@@ -1239,7 +1353,7 @@ function defineLazy(object2, key, getter) {
|
|
|
1239
1353
|
return value;
|
|
1240
1354
|
},
|
|
1241
1355
|
set(v) {
|
|
1242
|
-
Object.defineProperty(object2,
|
|
1356
|
+
Object.defineProperty(object2, key2, {
|
|
1243
1357
|
value: v
|
|
1244
1358
|
// configurable: true,
|
|
1245
1359
|
});
|
|
@@ -1272,11 +1386,11 @@ function cloneDef(schema) {
|
|
|
1272
1386
|
function getElementAtPath(obj, path) {
|
|
1273
1387
|
if (!path)
|
|
1274
1388
|
return obj;
|
|
1275
|
-
return path.reduce((acc,
|
|
1389
|
+
return path.reduce((acc, key2) => acc?.[key2], obj);
|
|
1276
1390
|
}
|
|
1277
1391
|
function promiseAllObject(promisesObj) {
|
|
1278
1392
|
const keys = Object.keys(promisesObj);
|
|
1279
|
-
const promises = keys.map((
|
|
1393
|
+
const promises = keys.map((key2) => promisesObj[key2]);
|
|
1280
1394
|
return Promise.all(promises).then((results) => {
|
|
1281
1395
|
const resolvedObj = {};
|
|
1282
1396
|
for (let i = 0; i < keys.length; i++) {
|
|
@@ -1348,8 +1462,8 @@ function shallowClone(o) {
|
|
|
1348
1462
|
}
|
|
1349
1463
|
function numKeys(data) {
|
|
1350
1464
|
let keyCount = 0;
|
|
1351
|
-
for (const
|
|
1352
|
-
if (Object.prototype.hasOwnProperty.call(data,
|
|
1465
|
+
for (const key2 in data) {
|
|
1466
|
+
if (Object.prototype.hasOwnProperty.call(data, key2)) {
|
|
1353
1467
|
keyCount++;
|
|
1354
1468
|
}
|
|
1355
1469
|
}
|
|
@@ -1499,13 +1613,13 @@ function pick(schema, mask) {
|
|
|
1499
1613
|
const def = mergeDefs(schema._zod.def, {
|
|
1500
1614
|
get shape() {
|
|
1501
1615
|
const newShape = {};
|
|
1502
|
-
for (const
|
|
1503
|
-
if (!(
|
|
1504
|
-
throw new Error(`Unrecognized key: "${
|
|
1616
|
+
for (const key2 in mask) {
|
|
1617
|
+
if (!(key2 in currDef.shape)) {
|
|
1618
|
+
throw new Error(`Unrecognized key: "${key2}"`);
|
|
1505
1619
|
}
|
|
1506
|
-
if (!mask[
|
|
1620
|
+
if (!mask[key2])
|
|
1507
1621
|
continue;
|
|
1508
|
-
newShape[
|
|
1622
|
+
newShape[key2] = currDef.shape[key2];
|
|
1509
1623
|
}
|
|
1510
1624
|
assignProp(this, "shape", newShape);
|
|
1511
1625
|
return newShape;
|
|
@@ -1524,13 +1638,13 @@ function omit(schema, mask) {
|
|
|
1524
1638
|
const def = mergeDefs(schema._zod.def, {
|
|
1525
1639
|
get shape() {
|
|
1526
1640
|
const newShape = { ...schema._zod.def.shape };
|
|
1527
|
-
for (const
|
|
1528
|
-
if (!(
|
|
1529
|
-
throw new Error(`Unrecognized key: "${
|
|
1641
|
+
for (const key2 in mask) {
|
|
1642
|
+
if (!(key2 in currDef.shape)) {
|
|
1643
|
+
throw new Error(`Unrecognized key: "${key2}"`);
|
|
1530
1644
|
}
|
|
1531
|
-
if (!mask[
|
|
1645
|
+
if (!mask[key2])
|
|
1532
1646
|
continue;
|
|
1533
|
-
delete newShape[
|
|
1647
|
+
delete newShape[key2];
|
|
1534
1648
|
}
|
|
1535
1649
|
assignProp(this, "shape", newShape);
|
|
1536
1650
|
return newShape;
|
|
@@ -1547,8 +1661,8 @@ function extend(schema, shape) {
|
|
|
1547
1661
|
const hasChecks = checks && checks.length > 0;
|
|
1548
1662
|
if (hasChecks) {
|
|
1549
1663
|
const existingShape = schema._zod.def.shape;
|
|
1550
|
-
for (const
|
|
1551
|
-
if (Object.getOwnPropertyDescriptor(existingShape,
|
|
1664
|
+
for (const key2 in shape) {
|
|
1665
|
+
if (Object.getOwnPropertyDescriptor(existingShape, key2) !== void 0) {
|
|
1552
1666
|
throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
|
|
1553
1667
|
}
|
|
1554
1668
|
}
|
|
@@ -1604,23 +1718,23 @@ function partial(Class2, schema, mask) {
|
|
|
1604
1718
|
const oldShape = schema._zod.def.shape;
|
|
1605
1719
|
const shape = { ...oldShape };
|
|
1606
1720
|
if (mask) {
|
|
1607
|
-
for (const
|
|
1608
|
-
if (!(
|
|
1609
|
-
throw new Error(`Unrecognized key: "${
|
|
1721
|
+
for (const key2 in mask) {
|
|
1722
|
+
if (!(key2 in oldShape)) {
|
|
1723
|
+
throw new Error(`Unrecognized key: "${key2}"`);
|
|
1610
1724
|
}
|
|
1611
|
-
if (!mask[
|
|
1725
|
+
if (!mask[key2])
|
|
1612
1726
|
continue;
|
|
1613
|
-
shape[
|
|
1727
|
+
shape[key2] = Class2 ? new Class2({
|
|
1614
1728
|
type: "optional",
|
|
1615
|
-
innerType: oldShape[
|
|
1616
|
-
}) : oldShape[
|
|
1729
|
+
innerType: oldShape[key2]
|
|
1730
|
+
}) : oldShape[key2];
|
|
1617
1731
|
}
|
|
1618
1732
|
} else {
|
|
1619
|
-
for (const
|
|
1620
|
-
shape[
|
|
1733
|
+
for (const key2 in oldShape) {
|
|
1734
|
+
shape[key2] = Class2 ? new Class2({
|
|
1621
1735
|
type: "optional",
|
|
1622
|
-
innerType: oldShape[
|
|
1623
|
-
}) : oldShape[
|
|
1736
|
+
innerType: oldShape[key2]
|
|
1737
|
+
}) : oldShape[key2];
|
|
1624
1738
|
}
|
|
1625
1739
|
}
|
|
1626
1740
|
assignProp(this, "shape", shape);
|
|
@@ -1636,22 +1750,22 @@ function required(Class2, schema, mask) {
|
|
|
1636
1750
|
const oldShape = schema._zod.def.shape;
|
|
1637
1751
|
const shape = { ...oldShape };
|
|
1638
1752
|
if (mask) {
|
|
1639
|
-
for (const
|
|
1640
|
-
if (!(
|
|
1641
|
-
throw new Error(`Unrecognized key: "${
|
|
1753
|
+
for (const key2 in mask) {
|
|
1754
|
+
if (!(key2 in shape)) {
|
|
1755
|
+
throw new Error(`Unrecognized key: "${key2}"`);
|
|
1642
1756
|
}
|
|
1643
|
-
if (!mask[
|
|
1757
|
+
if (!mask[key2])
|
|
1644
1758
|
continue;
|
|
1645
|
-
shape[
|
|
1759
|
+
shape[key2] = new Class2({
|
|
1646
1760
|
type: "nonoptional",
|
|
1647
|
-
innerType: oldShape[
|
|
1761
|
+
innerType: oldShape[key2]
|
|
1648
1762
|
});
|
|
1649
1763
|
}
|
|
1650
1764
|
} else {
|
|
1651
|
-
for (const
|
|
1652
|
-
shape[
|
|
1765
|
+
for (const key2 in oldShape) {
|
|
1766
|
+
shape[key2] = new Class2({
|
|
1653
1767
|
type: "nonoptional",
|
|
1654
|
-
innerType: oldShape[
|
|
1768
|
+
innerType: oldShape[key2]
|
|
1655
1769
|
});
|
|
1656
1770
|
}
|
|
1657
1771
|
}
|
|
@@ -3432,13 +3546,13 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
|
|
|
3432
3546
|
return payload;
|
|
3433
3547
|
};
|
|
3434
3548
|
});
|
|
3435
|
-
function handlePropertyResult(result, final,
|
|
3436
|
-
const isPresent =
|
|
3549
|
+
function handlePropertyResult(result, final, key2, input, isOptionalIn, isOptionalOut) {
|
|
3550
|
+
const isPresent = key2 in input;
|
|
3437
3551
|
if (result.issues.length) {
|
|
3438
3552
|
if (isOptionalIn && isOptionalOut && !isPresent) {
|
|
3439
3553
|
return;
|
|
3440
3554
|
}
|
|
3441
|
-
final.issues.push(...prefixIssues(
|
|
3555
|
+
final.issues.push(...prefixIssues(key2, result.issues));
|
|
3442
3556
|
}
|
|
3443
3557
|
if (!isPresent && !isOptionalIn) {
|
|
3444
3558
|
if (!result.issues.length) {
|
|
@@ -3446,17 +3560,17 @@ function handlePropertyResult(result, final, key, input, isOptionalIn, isOptiona
|
|
|
3446
3560
|
code: "invalid_type",
|
|
3447
3561
|
expected: "nonoptional",
|
|
3448
3562
|
input: void 0,
|
|
3449
|
-
path: [
|
|
3563
|
+
path: [key2]
|
|
3450
3564
|
});
|
|
3451
3565
|
}
|
|
3452
3566
|
return;
|
|
3453
3567
|
}
|
|
3454
3568
|
if (result.value === void 0) {
|
|
3455
3569
|
if (isPresent) {
|
|
3456
|
-
final.value[
|
|
3570
|
+
final.value[key2] = void 0;
|
|
3457
3571
|
}
|
|
3458
3572
|
} else {
|
|
3459
|
-
final.value[
|
|
3573
|
+
final.value[key2] = result.value;
|
|
3460
3574
|
}
|
|
3461
3575
|
}
|
|
3462
3576
|
function normalizeDef(def) {
|
|
@@ -3482,20 +3596,20 @@ function handleCatchall(proms, input, payload, ctx, def, inst) {
|
|
|
3482
3596
|
const t = _catchall.def.type;
|
|
3483
3597
|
const isOptionalIn = _catchall.optin === "optional";
|
|
3484
3598
|
const isOptionalOut = _catchall.optout === "optional";
|
|
3485
|
-
for (const
|
|
3486
|
-
if (
|
|
3599
|
+
for (const key2 in input) {
|
|
3600
|
+
if (key2 === "__proto__")
|
|
3487
3601
|
continue;
|
|
3488
|
-
if (keySet.has(
|
|
3602
|
+
if (keySet.has(key2))
|
|
3489
3603
|
continue;
|
|
3490
3604
|
if (t === "never") {
|
|
3491
|
-
unrecognized.push(
|
|
3605
|
+
unrecognized.push(key2);
|
|
3492
3606
|
continue;
|
|
3493
3607
|
}
|
|
3494
|
-
const r = _catchall.run({ value: input[
|
|
3608
|
+
const r = _catchall.run({ value: input[key2], issues: [] }, ctx);
|
|
3495
3609
|
if (r instanceof Promise) {
|
|
3496
|
-
proms.push(r.then((r2) => handlePropertyResult(r2, payload,
|
|
3610
|
+
proms.push(r.then((r2) => handlePropertyResult(r2, payload, key2, input, isOptionalIn, isOptionalOut)));
|
|
3497
3611
|
} else {
|
|
3498
|
-
handlePropertyResult(r, payload,
|
|
3612
|
+
handlePropertyResult(r, payload, key2, input, isOptionalIn, isOptionalOut);
|
|
3499
3613
|
}
|
|
3500
3614
|
}
|
|
3501
3615
|
if (unrecognized.length) {
|
|
@@ -3531,12 +3645,12 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
3531
3645
|
defineLazy(inst._zod, "propValues", () => {
|
|
3532
3646
|
const shape = def.shape;
|
|
3533
3647
|
const propValues = {};
|
|
3534
|
-
for (const
|
|
3535
|
-
const field = shape[
|
|
3648
|
+
for (const key2 in shape) {
|
|
3649
|
+
const field = shape[key2]._zod;
|
|
3536
3650
|
if (field.values) {
|
|
3537
|
-
propValues[
|
|
3651
|
+
propValues[key2] ?? (propValues[key2] = /* @__PURE__ */ new Set());
|
|
3538
3652
|
for (const v of field.values)
|
|
3539
|
-
propValues[
|
|
3653
|
+
propValues[key2].add(v);
|
|
3540
3654
|
}
|
|
3541
3655
|
}
|
|
3542
3656
|
return propValues;
|
|
@@ -3559,15 +3673,15 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
3559
3673
|
payload.value = {};
|
|
3560
3674
|
const proms = [];
|
|
3561
3675
|
const shape = value.shape;
|
|
3562
|
-
for (const
|
|
3563
|
-
const el = shape[
|
|
3676
|
+
for (const key2 of value.keys) {
|
|
3677
|
+
const el = shape[key2];
|
|
3564
3678
|
const isOptionalIn = el._zod.optin === "optional";
|
|
3565
3679
|
const isOptionalOut = el._zod.optout === "optional";
|
|
3566
|
-
const r = el._zod.run({ value: input[
|
|
3680
|
+
const r = el._zod.run({ value: input[key2], issues: [] }, ctx);
|
|
3567
3681
|
if (r instanceof Promise) {
|
|
3568
|
-
proms.push(r.then((r2) => handlePropertyResult(r2, payload,
|
|
3682
|
+
proms.push(r.then((r2) => handlePropertyResult(r2, payload, key2, input, isOptionalIn, isOptionalOut)));
|
|
3569
3683
|
} else {
|
|
3570
|
-
handlePropertyResult(r, payload,
|
|
3684
|
+
handlePropertyResult(r, payload, key2, input, isOptionalIn, isOptionalOut);
|
|
3571
3685
|
}
|
|
3572
3686
|
}
|
|
3573
3687
|
if (!catchall) {
|
|
@@ -3583,24 +3697,24 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
3583
3697
|
const generateFastpass = (shape) => {
|
|
3584
3698
|
const doc = new Doc(["shape", "payload", "ctx"]);
|
|
3585
3699
|
const normalized = _normalized.value;
|
|
3586
|
-
const parseStr = (
|
|
3587
|
-
const k = esc(
|
|
3700
|
+
const parseStr = (key2) => {
|
|
3701
|
+
const k = esc(key2);
|
|
3588
3702
|
return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
|
|
3589
3703
|
};
|
|
3590
3704
|
doc.write(`const input = payload.value;`);
|
|
3591
3705
|
const ids = /* @__PURE__ */ Object.create(null);
|
|
3592
3706
|
let counter = 0;
|
|
3593
|
-
for (const
|
|
3594
|
-
ids[
|
|
3707
|
+
for (const key2 of normalized.keys) {
|
|
3708
|
+
ids[key2] = `key_${counter++}`;
|
|
3595
3709
|
}
|
|
3596
3710
|
doc.write(`const newResult = {};`);
|
|
3597
|
-
for (const
|
|
3598
|
-
const id = ids[
|
|
3599
|
-
const k = esc(
|
|
3600
|
-
const schema = shape[
|
|
3711
|
+
for (const key2 of normalized.keys) {
|
|
3712
|
+
const id = ids[key2];
|
|
3713
|
+
const k = esc(key2);
|
|
3714
|
+
const schema = shape[key2];
|
|
3601
3715
|
const isOptionalIn = schema?._zod?.optin === "optional";
|
|
3602
3716
|
const isOptionalOut = schema?._zod?.optout === "optional";
|
|
3603
|
-
doc.write(`const ${id} = ${parseStr(
|
|
3717
|
+
doc.write(`const ${id} = ${parseStr(key2)};`);
|
|
3604
3718
|
if (isOptionalIn && isOptionalOut) {
|
|
3605
3719
|
doc.write(`
|
|
3606
3720
|
if (${id}.issues.length) {
|
|
@@ -3912,17 +4026,17 @@ function mergeValues(a, b) {
|
|
|
3912
4026
|
}
|
|
3913
4027
|
if (isPlainObject(a) && isPlainObject(b)) {
|
|
3914
4028
|
const bKeys = Object.keys(b);
|
|
3915
|
-
const sharedKeys = Object.keys(a).filter((
|
|
4029
|
+
const sharedKeys = Object.keys(a).filter((key2) => bKeys.indexOf(key2) !== -1);
|
|
3916
4030
|
const newObj = { ...a, ...b };
|
|
3917
|
-
for (const
|
|
3918
|
-
const sharedValue = mergeValues(a[
|
|
4031
|
+
for (const key2 of sharedKeys) {
|
|
4032
|
+
const sharedValue = mergeValues(a[key2], b[key2]);
|
|
3919
4033
|
if (!sharedValue.valid) {
|
|
3920
4034
|
return {
|
|
3921
4035
|
valid: false,
|
|
3922
|
-
mergeErrorPath: [
|
|
4036
|
+
mergeErrorPath: [key2, ...sharedValue.mergeErrorPath]
|
|
3923
4037
|
};
|
|
3924
4038
|
}
|
|
3925
|
-
newObj[
|
|
4039
|
+
newObj[key2] = sharedValue.data;
|
|
3926
4040
|
}
|
|
3927
4041
|
return { valid: true, data: newObj };
|
|
3928
4042
|
}
|
|
@@ -4057,9 +4171,9 @@ var $ZodTuple = /* @__PURE__ */ $constructor("$ZodTuple", (inst, def) => {
|
|
|
4057
4171
|
return handleTupleResults(itemResults, payload, items, input, optoutStart);
|
|
4058
4172
|
};
|
|
4059
4173
|
});
|
|
4060
|
-
function getTupleOptStart(items,
|
|
4174
|
+
function getTupleOptStart(items, key2) {
|
|
4061
4175
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
4062
|
-
if (items[i]._zod[
|
|
4176
|
+
if (items[i]._zod[key2] !== "optional")
|
|
4063
4177
|
return i + 1;
|
|
4064
4178
|
}
|
|
4065
4179
|
return 0;
|
|
@@ -4110,10 +4224,10 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
4110
4224
|
if (values) {
|
|
4111
4225
|
payload.value = {};
|
|
4112
4226
|
const recordKeys = /* @__PURE__ */ new Set();
|
|
4113
|
-
for (const
|
|
4114
|
-
if (typeof
|
|
4115
|
-
recordKeys.add(typeof
|
|
4116
|
-
const keyResult = def.keyType._zod.run({ value:
|
|
4227
|
+
for (const key2 of values) {
|
|
4228
|
+
if (typeof key2 === "string" || typeof key2 === "number" || typeof key2 === "symbol") {
|
|
4229
|
+
recordKeys.add(typeof key2 === "number" ? key2.toString() : key2);
|
|
4230
|
+
const keyResult = def.keyType._zod.run({ value: key2, issues: [] }, ctx);
|
|
4117
4231
|
if (keyResult instanceof Promise) {
|
|
4118
4232
|
throw new Error("Async schemas not supported in object keys currently");
|
|
4119
4233
|
}
|
|
@@ -4122,34 +4236,34 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
4122
4236
|
code: "invalid_key",
|
|
4123
4237
|
origin: "record",
|
|
4124
4238
|
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
4125
|
-
input:
|
|
4126
|
-
path: [
|
|
4239
|
+
input: key2,
|
|
4240
|
+
path: [key2],
|
|
4127
4241
|
inst
|
|
4128
4242
|
});
|
|
4129
4243
|
continue;
|
|
4130
4244
|
}
|
|
4131
4245
|
const outKey = keyResult.value;
|
|
4132
|
-
const result = def.valueType._zod.run({ value: input[
|
|
4246
|
+
const result = def.valueType._zod.run({ value: input[key2], issues: [] }, ctx);
|
|
4133
4247
|
if (result instanceof Promise) {
|
|
4134
4248
|
proms.push(result.then((result2) => {
|
|
4135
4249
|
if (result2.issues.length) {
|
|
4136
|
-
payload.issues.push(...prefixIssues(
|
|
4250
|
+
payload.issues.push(...prefixIssues(key2, result2.issues));
|
|
4137
4251
|
}
|
|
4138
4252
|
payload.value[outKey] = result2.value;
|
|
4139
4253
|
}));
|
|
4140
4254
|
} else {
|
|
4141
4255
|
if (result.issues.length) {
|
|
4142
|
-
payload.issues.push(...prefixIssues(
|
|
4256
|
+
payload.issues.push(...prefixIssues(key2, result.issues));
|
|
4143
4257
|
}
|
|
4144
4258
|
payload.value[outKey] = result.value;
|
|
4145
4259
|
}
|
|
4146
4260
|
}
|
|
4147
4261
|
}
|
|
4148
4262
|
let unrecognized;
|
|
4149
|
-
for (const
|
|
4150
|
-
if (!recordKeys.has(
|
|
4263
|
+
for (const key2 in input) {
|
|
4264
|
+
if (!recordKeys.has(key2)) {
|
|
4151
4265
|
unrecognized = unrecognized ?? [];
|
|
4152
|
-
unrecognized.push(
|
|
4266
|
+
unrecognized.push(key2);
|
|
4153
4267
|
}
|
|
4154
4268
|
}
|
|
4155
4269
|
if (unrecognized && unrecognized.length > 0) {
|
|
@@ -4162,18 +4276,18 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
4162
4276
|
}
|
|
4163
4277
|
} else {
|
|
4164
4278
|
payload.value = {};
|
|
4165
|
-
for (const
|
|
4166
|
-
if (
|
|
4279
|
+
for (const key2 of Reflect.ownKeys(input)) {
|
|
4280
|
+
if (key2 === "__proto__")
|
|
4167
4281
|
continue;
|
|
4168
|
-
if (!Object.prototype.propertyIsEnumerable.call(input,
|
|
4282
|
+
if (!Object.prototype.propertyIsEnumerable.call(input, key2))
|
|
4169
4283
|
continue;
|
|
4170
|
-
let keyResult = def.keyType._zod.run({ value:
|
|
4284
|
+
let keyResult = def.keyType._zod.run({ value: key2, issues: [] }, ctx);
|
|
4171
4285
|
if (keyResult instanceof Promise) {
|
|
4172
4286
|
throw new Error("Async schemas not supported in object keys currently");
|
|
4173
4287
|
}
|
|
4174
|
-
const checkNumericKey = typeof
|
|
4288
|
+
const checkNumericKey = typeof key2 === "string" && number.test(key2) && keyResult.issues.length;
|
|
4175
4289
|
if (checkNumericKey) {
|
|
4176
|
-
const retryResult = def.keyType._zod.run({ value: Number(
|
|
4290
|
+
const retryResult = def.keyType._zod.run({ value: Number(key2), issues: [] }, ctx);
|
|
4177
4291
|
if (retryResult instanceof Promise) {
|
|
4178
4292
|
throw new Error("Async schemas not supported in object keys currently");
|
|
4179
4293
|
}
|
|
@@ -4183,30 +4297,30 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
4183
4297
|
}
|
|
4184
4298
|
if (keyResult.issues.length) {
|
|
4185
4299
|
if (def.mode === "loose") {
|
|
4186
|
-
payload.value[
|
|
4300
|
+
payload.value[key2] = input[key2];
|
|
4187
4301
|
} else {
|
|
4188
4302
|
payload.issues.push({
|
|
4189
4303
|
code: "invalid_key",
|
|
4190
4304
|
origin: "record",
|
|
4191
4305
|
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
4192
|
-
input:
|
|
4193
|
-
path: [
|
|
4306
|
+
input: key2,
|
|
4307
|
+
path: [key2],
|
|
4194
4308
|
inst
|
|
4195
4309
|
});
|
|
4196
4310
|
}
|
|
4197
4311
|
continue;
|
|
4198
4312
|
}
|
|
4199
|
-
const result = def.valueType._zod.run({ value: input[
|
|
4313
|
+
const result = def.valueType._zod.run({ value: input[key2], issues: [] }, ctx);
|
|
4200
4314
|
if (result instanceof Promise) {
|
|
4201
4315
|
proms.push(result.then((result2) => {
|
|
4202
4316
|
if (result2.issues.length) {
|
|
4203
|
-
payload.issues.push(...prefixIssues(
|
|
4317
|
+
payload.issues.push(...prefixIssues(key2, result2.issues));
|
|
4204
4318
|
}
|
|
4205
4319
|
payload.value[keyResult.value] = result2.value;
|
|
4206
4320
|
}));
|
|
4207
4321
|
} else {
|
|
4208
4322
|
if (result.issues.length) {
|
|
4209
|
-
payload.issues.push(...prefixIssues(
|
|
4323
|
+
payload.issues.push(...prefixIssues(key2, result.issues));
|
|
4210
4324
|
}
|
|
4211
4325
|
payload.value[keyResult.value] = result.value;
|
|
4212
4326
|
}
|
|
@@ -4233,15 +4347,15 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
|
|
|
4233
4347
|
}
|
|
4234
4348
|
const proms = [];
|
|
4235
4349
|
payload.value = /* @__PURE__ */ new Map();
|
|
4236
|
-
for (const [
|
|
4237
|
-
const keyResult = def.keyType._zod.run({ value:
|
|
4350
|
+
for (const [key2, value] of input) {
|
|
4351
|
+
const keyResult = def.keyType._zod.run({ value: key2, issues: [] }, ctx);
|
|
4238
4352
|
const valueResult = def.valueType._zod.run({ value, issues: [] }, ctx);
|
|
4239
4353
|
if (keyResult instanceof Promise || valueResult instanceof Promise) {
|
|
4240
4354
|
proms.push(Promise.all([keyResult, valueResult]).then(([keyResult2, valueResult2]) => {
|
|
4241
|
-
handleMapResult(keyResult2, valueResult2, payload,
|
|
4355
|
+
handleMapResult(keyResult2, valueResult2, payload, key2, input, inst, ctx);
|
|
4242
4356
|
}));
|
|
4243
4357
|
} else {
|
|
4244
|
-
handleMapResult(keyResult, valueResult, payload,
|
|
4358
|
+
handleMapResult(keyResult, valueResult, payload, key2, input, inst, ctx);
|
|
4245
4359
|
}
|
|
4246
4360
|
}
|
|
4247
4361
|
if (proms.length)
|
|
@@ -4249,10 +4363,10 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
|
|
|
4249
4363
|
return payload;
|
|
4250
4364
|
};
|
|
4251
4365
|
});
|
|
4252
|
-
function handleMapResult(keyResult, valueResult, final,
|
|
4366
|
+
function handleMapResult(keyResult, valueResult, final, key2, input, inst, ctx) {
|
|
4253
4367
|
if (keyResult.issues.length) {
|
|
4254
|
-
if (propertyKeyTypes.has(typeof
|
|
4255
|
-
final.issues.push(...prefixIssues(
|
|
4368
|
+
if (propertyKeyTypes.has(typeof key2)) {
|
|
4369
|
+
final.issues.push(...prefixIssues(key2, keyResult.issues));
|
|
4256
4370
|
} else {
|
|
4257
4371
|
final.issues.push({
|
|
4258
4372
|
code: "invalid_key",
|
|
@@ -4264,15 +4378,15 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
|
|
|
4264
4378
|
}
|
|
4265
4379
|
}
|
|
4266
4380
|
if (valueResult.issues.length) {
|
|
4267
|
-
if (propertyKeyTypes.has(typeof
|
|
4268
|
-
final.issues.push(...prefixIssues(
|
|
4381
|
+
if (propertyKeyTypes.has(typeof key2)) {
|
|
4382
|
+
final.issues.push(...prefixIssues(key2, valueResult.issues));
|
|
4269
4383
|
} else {
|
|
4270
4384
|
final.issues.push({
|
|
4271
4385
|
origin: "map",
|
|
4272
4386
|
code: "invalid_element",
|
|
4273
4387
|
input,
|
|
4274
4388
|
inst,
|
|
4275
|
-
key,
|
|
4389
|
+
key: key2,
|
|
4276
4390
|
issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
|
|
4277
4391
|
});
|
|
4278
4392
|
}
|
|
@@ -12033,8 +12147,8 @@ function extractDefs(ctx, schema) {
|
|
|
12033
12147
|
if (defId)
|
|
12034
12148
|
seen.defId = defId;
|
|
12035
12149
|
const schema2 = seen.schema;
|
|
12036
|
-
for (const
|
|
12037
|
-
delete schema2[
|
|
12150
|
+
for (const key2 in schema2) {
|
|
12151
|
+
delete schema2[key2];
|
|
12038
12152
|
}
|
|
12039
12153
|
schema2.$ref = ref;
|
|
12040
12154
|
};
|
|
@@ -12103,20 +12217,20 @@ function finalize(ctx, schema) {
|
|
|
12103
12217
|
Object.assign(schema2, _cached);
|
|
12104
12218
|
const isParentRef = zodSchema._zod.parent === ref;
|
|
12105
12219
|
if (isParentRef) {
|
|
12106
|
-
for (const
|
|
12107
|
-
if (
|
|
12220
|
+
for (const key2 in schema2) {
|
|
12221
|
+
if (key2 === "$ref" || key2 === "allOf")
|
|
12108
12222
|
continue;
|
|
12109
|
-
if (!(
|
|
12110
|
-
delete schema2[
|
|
12223
|
+
if (!(key2 in _cached)) {
|
|
12224
|
+
delete schema2[key2];
|
|
12111
12225
|
}
|
|
12112
12226
|
}
|
|
12113
12227
|
}
|
|
12114
12228
|
if (refSchema.$ref && refSeen.def) {
|
|
12115
|
-
for (const
|
|
12116
|
-
if (
|
|
12229
|
+
for (const key2 in schema2) {
|
|
12230
|
+
if (key2 === "$ref" || key2 === "allOf")
|
|
12117
12231
|
continue;
|
|
12118
|
-
if (
|
|
12119
|
-
delete schema2[
|
|
12232
|
+
if (key2 in refSeen.def && JSON.stringify(schema2[key2]) === JSON.stringify(refSeen.def[key2])) {
|
|
12233
|
+
delete schema2[key2];
|
|
12120
12234
|
}
|
|
12121
12235
|
}
|
|
12122
12236
|
}
|
|
@@ -12128,11 +12242,11 @@ function finalize(ctx, schema) {
|
|
|
12128
12242
|
if (parentSeen?.schema.$ref) {
|
|
12129
12243
|
schema2.$ref = parentSeen.schema.$ref;
|
|
12130
12244
|
if (parentSeen.def) {
|
|
12131
|
-
for (const
|
|
12132
|
-
if (
|
|
12245
|
+
for (const key2 in schema2) {
|
|
12246
|
+
if (key2 === "$ref" || key2 === "allOf")
|
|
12133
12247
|
continue;
|
|
12134
|
-
if (
|
|
12135
|
-
delete schema2[
|
|
12248
|
+
if (key2 in parentSeen.def && JSON.stringify(schema2[key2]) === JSON.stringify(parentSeen.def[key2])) {
|
|
12249
|
+
delete schema2[key2];
|
|
12136
12250
|
}
|
|
12137
12251
|
}
|
|
12138
12252
|
}
|
|
@@ -12233,8 +12347,8 @@ function isTransforming(_schema, _ctx) {
|
|
|
12233
12347
|
return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);
|
|
12234
12348
|
}
|
|
12235
12349
|
if (def.type === "object") {
|
|
12236
|
-
for (const
|
|
12237
|
-
if (isTransforming(def.shape[
|
|
12350
|
+
for (const key2 in def.shape) {
|
|
12351
|
+
if (isTransforming(def.shape[key2], ctx))
|
|
12238
12352
|
return true;
|
|
12239
12353
|
}
|
|
12240
12354
|
return false;
|
|
@@ -12523,15 +12637,15 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
12523
12637
|
json3.type = "object";
|
|
12524
12638
|
json3.properties = {};
|
|
12525
12639
|
const shape = def.shape;
|
|
12526
|
-
for (const
|
|
12527
|
-
json3.properties[
|
|
12640
|
+
for (const key2 in shape) {
|
|
12641
|
+
json3.properties[key2] = process2(shape[key2], ctx, {
|
|
12528
12642
|
...params,
|
|
12529
|
-
path: [...params.path, "properties",
|
|
12643
|
+
path: [...params.path, "properties", key2]
|
|
12530
12644
|
});
|
|
12531
12645
|
}
|
|
12532
12646
|
const allKeys = new Set(Object.keys(shape));
|
|
12533
|
-
const requiredKeys = new Set([...allKeys].filter((
|
|
12534
|
-
const v = def.shape[
|
|
12647
|
+
const requiredKeys = new Set([...allKeys].filter((key2) => {
|
|
12648
|
+
const v = def.shape[key2]._zod;
|
|
12535
12649
|
if (ctx.io === "input") {
|
|
12536
12650
|
return v.optin === void 0;
|
|
12537
12651
|
} else {
|
|
@@ -12797,9 +12911,9 @@ function toJSONSchema(input, params) {
|
|
|
12797
12911
|
};
|
|
12798
12912
|
ctx2.external = external;
|
|
12799
12913
|
for (const entry of registry2._idmap.entries()) {
|
|
12800
|
-
const [
|
|
12914
|
+
const [key2, schema] = entry;
|
|
12801
12915
|
extractDefs(ctx2, schema);
|
|
12802
|
-
schemas[
|
|
12916
|
+
schemas[key2] = finalize(ctx2, schema);
|
|
12803
12917
|
}
|
|
12804
12918
|
if (Object.keys(defs).length > 0) {
|
|
12805
12919
|
const defsSegment = ctx2.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
@@ -13205,14 +13319,14 @@ function _installLazyMethods(inst, group, methods) {
|
|
|
13205
13319
|
if (installed.has(group))
|
|
13206
13320
|
return;
|
|
13207
13321
|
installed.add(group);
|
|
13208
|
-
for (const
|
|
13209
|
-
const fn = methods[
|
|
13210
|
-
Object.defineProperty(proto,
|
|
13322
|
+
for (const key2 in methods) {
|
|
13323
|
+
const fn = methods[key2];
|
|
13324
|
+
Object.defineProperty(proto, key2, {
|
|
13211
13325
|
configurable: true,
|
|
13212
13326
|
enumerable: false,
|
|
13213
13327
|
get() {
|
|
13214
13328
|
const bound = fn.bind(this);
|
|
13215
|
-
Object.defineProperty(this,
|
|
13329
|
+
Object.defineProperty(this, key2, {
|
|
13216
13330
|
configurable: true,
|
|
13217
13331
|
writable: true,
|
|
13218
13332
|
enumerable: true,
|
|
@@ -13221,7 +13335,7 @@ function _installLazyMethods(inst, group, methods) {
|
|
|
13221
13335
|
return bound;
|
|
13222
13336
|
},
|
|
13223
13337
|
set(v) {
|
|
13224
|
-
Object.defineProperty(this,
|
|
13338
|
+
Object.defineProperty(this, key2, {
|
|
13225
13339
|
configurable: true,
|
|
13226
13340
|
writable: true,
|
|
13227
13341
|
enumerable: true,
|
|
@@ -14609,11 +14723,11 @@ function resolveRef(ref, ctx) {
|
|
|
14609
14723
|
}
|
|
14610
14724
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
14611
14725
|
if (path[0] === defsKey) {
|
|
14612
|
-
const
|
|
14613
|
-
if (!
|
|
14726
|
+
const key2 = path[1];
|
|
14727
|
+
if (!key2 || !ctx.defs[key2]) {
|
|
14614
14728
|
throw new Error(`Reference not found: ${ref}`);
|
|
14615
14729
|
}
|
|
14616
|
-
return ctx.defs[
|
|
14730
|
+
return ctx.defs[key2];
|
|
14617
14731
|
}
|
|
14618
14732
|
throw new Error(`Reference not found: ${ref}`);
|
|
14619
14733
|
}
|
|
@@ -14799,9 +14913,9 @@ function convertBaseSchema(schema, ctx) {
|
|
|
14799
14913
|
const shape = {};
|
|
14800
14914
|
const properties = schema.properties || {};
|
|
14801
14915
|
const requiredSet = new Set(schema.required || []);
|
|
14802
|
-
for (const [
|
|
14916
|
+
for (const [key2, propSchema] of Object.entries(properties)) {
|
|
14803
14917
|
const propZodSchema = convertSchema(propSchema, ctx);
|
|
14804
|
-
shape[
|
|
14918
|
+
shape[key2] = requiredSet.has(key2) ? propZodSchema : propZodSchema.optional();
|
|
14805
14919
|
}
|
|
14806
14920
|
if (schema.propertyNames) {
|
|
14807
14921
|
const keySchema = convertSchema(schema.propertyNames, ctx);
|
|
@@ -14942,20 +15056,20 @@ function convertSchema(schema, ctx) {
|
|
|
14942
15056
|
}
|
|
14943
15057
|
const extraMeta = {};
|
|
14944
15058
|
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
|
14945
|
-
for (const
|
|
14946
|
-
if (
|
|
14947
|
-
extraMeta[
|
|
15059
|
+
for (const key2 of coreMetadataKeys) {
|
|
15060
|
+
if (key2 in schema) {
|
|
15061
|
+
extraMeta[key2] = schema[key2];
|
|
14948
15062
|
}
|
|
14949
15063
|
}
|
|
14950
15064
|
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
|
14951
|
-
for (const
|
|
14952
|
-
if (
|
|
14953
|
-
extraMeta[
|
|
15065
|
+
for (const key2 of contentMetadataKeys) {
|
|
15066
|
+
if (key2 in schema) {
|
|
15067
|
+
extraMeta[key2] = schema[key2];
|
|
14954
15068
|
}
|
|
14955
15069
|
}
|
|
14956
|
-
for (const
|
|
14957
|
-
if (!RECOGNIZED_KEYS.has(
|
|
14958
|
-
extraMeta[
|
|
15070
|
+
for (const key2 of Object.keys(schema)) {
|
|
15071
|
+
if (!RECOGNIZED_KEYS.has(key2)) {
|
|
15072
|
+
extraMeta[key2] = schema[key2];
|
|
14959
15073
|
}
|
|
14960
15074
|
}
|
|
14961
15075
|
if (Object.keys(extraMeta).length > 0) {
|
|
@@ -15050,18 +15164,28 @@ Frame upload interrupted; retrying in ${delay / 1e3}s.
|
|
|
15050
15164
|
}
|
|
15051
15165
|
}
|
|
15052
15166
|
async function submitFrameCapture(client, id, directory) {
|
|
15053
|
-
const manifest = frameManifestSchema.parse(JSON.parse(await
|
|
15167
|
+
const manifest = frameManifestSchema.parse(JSON.parse(await readFile3(join3(directory, "capture-manifest.json"), "utf8")));
|
|
15054
15168
|
let status = await client.beginEncoding(id, manifest);
|
|
15055
15169
|
if (status.status === "failed") status = await client.retryEncoding(id);
|
|
15056
15170
|
if (status.status !== "uploading") {
|
|
15057
15171
|
if (["expired", "cancelled"].includes(status.status)) throw new Error(`Capture ${status.status}; it cannot be retried.`);
|
|
15058
15172
|
return status;
|
|
15059
15173
|
}
|
|
15060
|
-
const file2 = await open(
|
|
15174
|
+
const file2 = await open(join3(directory, "frames.bin"), "r");
|
|
15061
15175
|
try {
|
|
15062
15176
|
const total = manifest.frames.reduce((n, f) => n + f.size, 0);
|
|
15063
15177
|
if ((await file2.stat()).size !== total) throw new Error("Capture bytes do not match the manifest.");
|
|
15064
15178
|
const uploaded = new Set(status.uploadedParts);
|
|
15179
|
+
let acceptedBytes = [...uploaded].reduce((sum, part) => sum + (Number.isInteger(part) && part >= 0 && part < Math.ceil(total / ENCODING_PART_BYTES) ? Math.min(ENCODING_PART_BYTES, total - part * ENCODING_PART_BYTES) : 0), 0);
|
|
15180
|
+
let transferredBytes = 0;
|
|
15181
|
+
const startedAt = Date.now();
|
|
15182
|
+
const progress = () => {
|
|
15183
|
+
const seconds = (Date.now() - startedAt) / 1e3;
|
|
15184
|
+
const rate = seconds > 0 ? transferredBytes / seconds : 0;
|
|
15185
|
+
const remaining = rate > 0 ? ` \xB7 about ${Math.ceil((total - acceptedBytes) / rate)}s left` : "";
|
|
15186
|
+
process.stderr.write(`\ruploading frames ${Math.floor(acceptedBytes / total * 100)}% \xB7 ${(acceptedBytes / 1048576).toFixed(2)}/${(total / 1048576).toFixed(2)} MiB${rate > 0 ? ` \xB7 ${(rate / 1048576).toFixed(2)} MiB/s` : ""}${remaining}`);
|
|
15187
|
+
};
|
|
15188
|
+
progress();
|
|
15065
15189
|
for (let part = 0; part < Math.ceil(total / ENCODING_PART_BYTES); part++) {
|
|
15066
15190
|
if (uploaded.has(part)) continue;
|
|
15067
15191
|
const bytes = Buffer.alloc(Math.min(ENCODING_PART_BYTES, total - part * ENCODING_PART_BYTES));
|
|
@@ -15072,7 +15196,9 @@ async function submitFrameCapture(client, id, directory) {
|
|
|
15072
15196
|
offset += r.bytesRead;
|
|
15073
15197
|
}
|
|
15074
15198
|
await withFrameUploadRetry(() => client.uploadEncodingPart(id, part, bytes));
|
|
15075
|
-
|
|
15199
|
+
acceptedBytes += bytes.length;
|
|
15200
|
+
transferredBytes += bytes.length;
|
|
15201
|
+
progress();
|
|
15076
15202
|
}
|
|
15077
15203
|
} finally {
|
|
15078
15204
|
await file2.close();
|
|
@@ -15093,35 +15219,129 @@ async function waitForEncoding(client, id, timeoutMs = 30 * 6e4) {
|
|
|
15093
15219
|
}
|
|
15094
15220
|
|
|
15095
15221
|
// src/cli.ts
|
|
15096
|
-
import { spawn as
|
|
15097
|
-
import { hostname as hostname4 } from "os";
|
|
15222
|
+
import { spawn as spawn8 } from "child_process";
|
|
15223
|
+
import { hostname as hostname4, tmpdir as tmpdir3 } from "os";
|
|
15098
15224
|
import { Command } from "commander";
|
|
15099
|
-
import { basename, dirname as dirname2 } from "path";
|
|
15100
|
-
import { readdir as readdir2, rm as
|
|
15225
|
+
import { basename, dirname as dirname2, join as join11 } from "path";
|
|
15226
|
+
import { readdir as readdir2, rm as rm7, stat as stat5 } from "fs/promises";
|
|
15101
15227
|
|
|
15102
|
-
// ../recorder/src/
|
|
15103
|
-
import {
|
|
15104
|
-
import {
|
|
15228
|
+
// ../recorder/src/windows-ipc.ts
|
|
15229
|
+
import { spawn as spawn3 } from "child_process";
|
|
15230
|
+
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
15231
|
+
import { link, lstat, mkdir as mkdir3, readFile as readFile4, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
15232
|
+
import { join as join5, win32 as win322 } from "path";
|
|
15105
15233
|
|
|
15106
15234
|
// ../recorder/src/paths.ts
|
|
15107
|
-
import { homedir as
|
|
15108
|
-
import { join as
|
|
15109
|
-
var rillDirectory = () => process.env.RILL_HOME ??
|
|
15110
|
-
var daemonSocketPath = () => process.env.RILL_SOCKET ??
|
|
15111
|
-
var daemonStatePath = () =>
|
|
15112
|
-
var recordingTempRoot = () => process.env.RILL_TEMP ??
|
|
15235
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
15236
|
+
import { join as join4 } from "path";
|
|
15237
|
+
var rillDirectory = () => process.env.RILL_HOME ?? join4(homedir3(), ".rill");
|
|
15238
|
+
var daemonSocketPath = () => process.env.RILL_SOCKET ?? join4(rillDirectory(), "recorder-v2.sock");
|
|
15239
|
+
var daemonStatePath = () => join4(rillDirectory(), "sessions-v2.json");
|
|
15240
|
+
var recordingTempRoot = () => process.env.RILL_TEMP ?? join4(tmpdir(), "rill-recordings");
|
|
15241
|
+
|
|
15242
|
+
// ../recorder/src/windows-ipc.ts
|
|
15243
|
+
var aclScript = String.raw`$ErrorActionPreference='Stop';try {
|
|
15244
|
+
$path=([Console]::In.ReadToEnd() | ConvertFrom-Json).path
|
|
15245
|
+
$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
15246
|
+
$acl=[System.Security.AccessControl.DirectorySecurity]::new()
|
|
15247
|
+
$acl.SetOwner($sid)
|
|
15248
|
+
$acl.SetAccessRuleProtection($true,$false)
|
|
15249
|
+
$rule=[System.Security.AccessControl.FileSystemAccessRule]::new($sid,[System.Security.AccessControl.FileSystemRights]::FullControl,[System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',[System.Security.AccessControl.PropagationFlags]::None,[System.Security.AccessControl.AccessControlType]::Allow)
|
|
15250
|
+
$acl.AddAccessRule($rule)
|
|
15251
|
+
([System.IO.DirectoryInfo]::new($path)).SetAccessControl($acl)
|
|
15252
|
+
[Console]::Out.Write('secured')
|
|
15253
|
+
}catch{[Console]::Error.Write('Recorder directory protection failed.');exit 1}`;
|
|
15254
|
+
async function secureWindowsIpcDirectory(path) {
|
|
15255
|
+
if (process.platform !== "win32") throw new Error("Windows IPC directory protection requires Windows.");
|
|
15256
|
+
await new Promise((resolve, reject) => {
|
|
15257
|
+
const executable = win322.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
15258
|
+
const child = spawn3(executable, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", aclScript], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
15259
|
+
let output = "", failed = false;
|
|
15260
|
+
const fail2 = () => {
|
|
15261
|
+
if (failed) return;
|
|
15262
|
+
failed = true;
|
|
15263
|
+
child.kill();
|
|
15264
|
+
reject(new Error("Cannot secure the Windows recorder directory. Recording has not started."));
|
|
15265
|
+
};
|
|
15266
|
+
const timer = setTimeout(fail2, 15e3);
|
|
15267
|
+
child.once("error", fail2);
|
|
15268
|
+
child.stdin.once("error", fail2);
|
|
15269
|
+
child.stderr.resume();
|
|
15270
|
+
child.stdout.on("data", (chunk) => {
|
|
15271
|
+
output += chunk.toString("ascii");
|
|
15272
|
+
if (output.length > 1024) fail2();
|
|
15273
|
+
});
|
|
15274
|
+
child.once("close", (code) => {
|
|
15275
|
+
clearTimeout(timer);
|
|
15276
|
+
if (failed) return;
|
|
15277
|
+
if (code !== 0 || output !== "secured") {
|
|
15278
|
+
fail2();
|
|
15279
|
+
return;
|
|
15280
|
+
}
|
|
15281
|
+
resolve();
|
|
15282
|
+
});
|
|
15283
|
+
child.stdin.end(JSON.stringify({ path }));
|
|
15284
|
+
});
|
|
15285
|
+
}
|
|
15286
|
+
function validWindowsIpcIdentity(value) {
|
|
15287
|
+
const v = value;
|
|
15288
|
+
return v?.version === 1 && typeof v.pipe === "string" && /^\\\\\.\\pipe\\rill-v3-[a-f0-9]{64}$/.test(v.pipe) && typeof v.token === "string" && /^[a-f0-9]{64}$/.test(v.token);
|
|
15289
|
+
}
|
|
15290
|
+
function acceptsWindowsIpcToken(expected, header) {
|
|
15291
|
+
if (typeof header !== "string" || !/^Bearer [a-f0-9]{64}$/.test(header)) return false;
|
|
15292
|
+
const supplied = header.slice(7);
|
|
15293
|
+
return supplied.length === expected.length && timingSafeEqual(Buffer.from(supplied), Buffer.from(expected));
|
|
15294
|
+
}
|
|
15295
|
+
async function windowsIpcIdentity(directory = join5(rillDirectory(), "windows-ipc-v3"), secure = secureWindowsIpcDirectory) {
|
|
15296
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
15297
|
+
if ((await lstat(directory)).isSymbolicLink()) throw new Error("Recorder directory cannot be a symbolic link.");
|
|
15298
|
+
await secure(directory);
|
|
15299
|
+
const path = join5(directory, "identity.json");
|
|
15300
|
+
const load = async () => {
|
|
15301
|
+
const info = await lstat(path);
|
|
15302
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 4096) throw new Error("Invalid recorder identity.");
|
|
15303
|
+
const value = JSON.parse(await readFile4(path, "utf8"));
|
|
15304
|
+
if (!validWindowsIpcIdentity(value)) throw new Error("Invalid recorder identity.");
|
|
15305
|
+
return value;
|
|
15306
|
+
};
|
|
15307
|
+
try {
|
|
15308
|
+
return await load();
|
|
15309
|
+
} catch (e) {
|
|
15310
|
+
if (e.code !== "ENOENT") throw new Error("Cannot read the protected recorder identity.");
|
|
15311
|
+
}
|
|
15312
|
+
const identity = { version: 1, pipe: "\\\\.\\pipe\\rill-v3-" + randomBytes2(32).toString("hex"), token: randomBytes2(32).toString("hex") };
|
|
15313
|
+
const temporary = join5(directory, randomBytes2(16).toString("hex") + ".tmp");
|
|
15314
|
+
try {
|
|
15315
|
+
await writeFile2(temporary, JSON.stringify(identity), { mode: 384, flag: "wx" });
|
|
15316
|
+
try {
|
|
15317
|
+
await link(temporary, path);
|
|
15318
|
+
} catch (e) {
|
|
15319
|
+
if (e.code !== "EEXIST") throw e;
|
|
15320
|
+
}
|
|
15321
|
+
return await load();
|
|
15322
|
+
} finally {
|
|
15323
|
+
await rm3(temporary, { force: true });
|
|
15324
|
+
}
|
|
15325
|
+
}
|
|
15113
15326
|
|
|
15114
15327
|
// ../recorder/src/client.ts
|
|
15328
|
+
import { request as request2 } from "http";
|
|
15329
|
+
import { spawn as spawn4 } from "child_process";
|
|
15115
15330
|
async function ensureDaemon(cliEntryPath) {
|
|
15116
15331
|
try {
|
|
15117
15332
|
await callDaemon("GET", "/health");
|
|
15118
15333
|
return;
|
|
15119
15334
|
} catch {
|
|
15120
15335
|
}
|
|
15121
|
-
const child =
|
|
15336
|
+
const child = spawn4(process.execPath, [cliEntryPath, "daemon"], { detached: true, stdio: "ignore", env: process.env, windowsHide: true });
|
|
15337
|
+
let startupError;
|
|
15338
|
+
child.once("error", () => {
|
|
15339
|
+
startupError = new Error("The local recorder process could not be launched.");
|
|
15340
|
+
});
|
|
15122
15341
|
child.unref();
|
|
15123
15342
|
const deadline = Date.now() + 1e4;
|
|
15124
15343
|
while (Date.now() < deadline) {
|
|
15344
|
+
if (startupError) throw startupError;
|
|
15125
15345
|
try {
|
|
15126
15346
|
await callDaemon("GET", "/health");
|
|
15127
15347
|
return;
|
|
@@ -15140,10 +15360,16 @@ function stopRecording(recordingId) {
|
|
|
15140
15360
|
function recordingStatus(recordingId) {
|
|
15141
15361
|
return callDaemon("GET", `/sessions/${encodeURIComponent(recordingId)}`);
|
|
15142
15362
|
}
|
|
15143
|
-
|
|
15363
|
+
var windowsIdentityPromise;
|
|
15364
|
+
async function callDaemon(method, path, body) {
|
|
15365
|
+
if (process.platform === "win32" && process.env.RILL_SOCKET) throw new Error("RILL_SOCKET overrides are not supported by authenticated Windows IPC. Unset it before recording.");
|
|
15366
|
+
const identity = process.platform === "win32" ? await (windowsIdentityPromise ??= windowsIpcIdentity().catch((error51) => {
|
|
15367
|
+
windowsIdentityPromise = void 0;
|
|
15368
|
+
throw error51;
|
|
15369
|
+
})) : void 0;
|
|
15144
15370
|
return new Promise((resolve, reject) => {
|
|
15145
15371
|
const payload = body === void 0 ? void 0 : Buffer.from(JSON.stringify(body));
|
|
15146
|
-
const outgoing = request2({ socketPath: daemonSocketPath(), method, path, headers: payload ? { "Content-Type": "application/json", "Content-Length": payload.length } :
|
|
15372
|
+
const outgoing = request2({ socketPath: identity?.pipe ?? daemonSocketPath(), method, path, headers: { ...payload ? { "Content-Type": "application/json", "Content-Length": payload.length } : {}, ...identity ? { Authorization: "Bearer " + identity.token } : {} } }, (incoming) => {
|
|
15147
15373
|
const chunks = [];
|
|
15148
15374
|
incoming.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
15149
15375
|
incoming.on("end", () => {
|
|
@@ -15156,6 +15382,7 @@ function callDaemon(method, path, body) {
|
|
|
15156
15382
|
}
|
|
15157
15383
|
});
|
|
15158
15384
|
});
|
|
15385
|
+
outgoing.setTimeout(path === "/health" ? 2e3 : 12e4, () => outgoing.destroy(new Error("Local recorder request timed out.")));
|
|
15159
15386
|
outgoing.once("error", reject);
|
|
15160
15387
|
if (payload) outgoing.write(payload);
|
|
15161
15388
|
outgoing.end();
|
|
@@ -15163,17 +15390,17 @@ function callDaemon(method, path, body) {
|
|
|
15163
15390
|
}
|
|
15164
15391
|
|
|
15165
15392
|
// ../recorder/src/retention.ts
|
|
15166
|
-
import { readdir, readFile as
|
|
15167
|
-
import { join as
|
|
15393
|
+
import { readdir, readFile as readFile5, rm as rm4 } from "fs/promises";
|
|
15394
|
+
import { join as join6 } from "path";
|
|
15168
15395
|
async function purgeExpiredRemoteCaptures(root, now = Date.now()) {
|
|
15169
15396
|
const removed = [];
|
|
15170
15397
|
for (const id of await readdir(root).catch(() => [])) {
|
|
15171
15398
|
if (!/^rec_[a-zA-Z0-9_-]+$/.test(id)) continue;
|
|
15172
|
-
const directory =
|
|
15399
|
+
const directory = join6(root, id);
|
|
15173
15400
|
try {
|
|
15174
|
-
const marker = JSON.parse(await
|
|
15401
|
+
const marker = JSON.parse(await readFile5(join6(directory, "remote-capture.json"), "utf8"));
|
|
15175
15402
|
if (typeof marker.expiresAt !== "number" || marker.expiresAt > now) continue;
|
|
15176
|
-
await
|
|
15403
|
+
await rm4(directory, { recursive: true, force: true });
|
|
15177
15404
|
removed.push(id);
|
|
15178
15405
|
} catch {
|
|
15179
15406
|
}
|
|
@@ -15184,7 +15411,7 @@ async function hasRetainedRemoteCaptures(root) {
|
|
|
15184
15411
|
for (const id of await readdir(root).catch(() => [])) {
|
|
15185
15412
|
if (!/^rec_[a-zA-Z0-9_-]+$/.test(id)) continue;
|
|
15186
15413
|
try {
|
|
15187
|
-
const marker = JSON.parse(await
|
|
15414
|
+
const marker = JSON.parse(await readFile5(join6(root, id, "remote-capture.json"), "utf8"));
|
|
15188
15415
|
if (typeof marker.expiresAt === "number" && Number.isFinite(marker.expiresAt)) return true;
|
|
15189
15416
|
} catch {
|
|
15190
15417
|
}
|
|
@@ -15194,8 +15421,189 @@ async function hasRetainedRemoteCaptures(root) {
|
|
|
15194
15421
|
|
|
15195
15422
|
// ../recorder/src/daemon.ts
|
|
15196
15423
|
import { createServer as createServer2 } from "http";
|
|
15197
|
-
import { mkdir as
|
|
15198
|
-
import { dirname, join as
|
|
15424
|
+
import { mkdir as mkdir7, readFile as readFile7, unlink, writeFile as writeFile6, rename as rename2, stat as stat4, chmod } from "fs/promises";
|
|
15425
|
+
import { dirname, join as join10 } from "path";
|
|
15426
|
+
|
|
15427
|
+
// ../contracts/src/cloud-blocklist.ts
|
|
15428
|
+
var blockedCloudDomains = [
|
|
15429
|
+
// Torrent indexes and unofficial software-download destinations.
|
|
15430
|
+
// Explicit domains only: do not block legitimate torrent clients or all downloads.
|
|
15431
|
+
"yasdl.com",
|
|
15432
|
+
"soft98.ir",
|
|
15433
|
+
"downloadha.com",
|
|
15434
|
+
"patoghu.com",
|
|
15435
|
+
"thepiratebay.org",
|
|
15436
|
+
"1337x.to",
|
|
15437
|
+
"yts.mx",
|
|
15438
|
+
"eztvx.to",
|
|
15439
|
+
"fitgirl-repacks.site",
|
|
15440
|
+
"dodi-repacks.site",
|
|
15441
|
+
// Search and reference
|
|
15442
|
+
"google.com",
|
|
15443
|
+
"google.co.uk",
|
|
15444
|
+
"google.co.in",
|
|
15445
|
+
"google.com.ng",
|
|
15446
|
+
"google.ca",
|
|
15447
|
+
"google.com.au",
|
|
15448
|
+
"google.de",
|
|
15449
|
+
"google.fr",
|
|
15450
|
+
"google.co.jp",
|
|
15451
|
+
"google.com.br",
|
|
15452
|
+
"google.es",
|
|
15453
|
+
"bing.com",
|
|
15454
|
+
"yahoo.com",
|
|
15455
|
+
"duckduckgo.com",
|
|
15456
|
+
"baidu.com",
|
|
15457
|
+
"yandex.com",
|
|
15458
|
+
"yandex.ru",
|
|
15459
|
+
"wikipedia.org",
|
|
15460
|
+
"quora.com",
|
|
15461
|
+
// Social networks and media
|
|
15462
|
+
"reddit.com",
|
|
15463
|
+
"redd.it",
|
|
15464
|
+
"facebook.com",
|
|
15465
|
+
"fb.com",
|
|
15466
|
+
"instagram.com",
|
|
15467
|
+
"threads.net",
|
|
15468
|
+
"threads.com",
|
|
15469
|
+
"twitter.com",
|
|
15470
|
+
"x.com",
|
|
15471
|
+
"t.co",
|
|
15472
|
+
"linkedin.com",
|
|
15473
|
+
"tiktok.com",
|
|
15474
|
+
"pinterest.com",
|
|
15475
|
+
"snapchat.com",
|
|
15476
|
+
"youtube.com",
|
|
15477
|
+
"youtu.be",
|
|
15478
|
+
"twitch.tv",
|
|
15479
|
+
"discord.com",
|
|
15480
|
+
"discord.gg",
|
|
15481
|
+
"telegram.org",
|
|
15482
|
+
"t.me",
|
|
15483
|
+
"whatsapp.com",
|
|
15484
|
+
// Shopping and marketplaces
|
|
15485
|
+
"amazon.com",
|
|
15486
|
+
"amazon.co.uk",
|
|
15487
|
+
"amazon.ca",
|
|
15488
|
+
"amazon.de",
|
|
15489
|
+
"amazon.fr",
|
|
15490
|
+
"amazon.in",
|
|
15491
|
+
"amazon.co.jp",
|
|
15492
|
+
"amazon.com.au",
|
|
15493
|
+
"amazon.it",
|
|
15494
|
+
"amazon.es",
|
|
15495
|
+
"ebay.com",
|
|
15496
|
+
"ebay.co.uk",
|
|
15497
|
+
"etsy.com",
|
|
15498
|
+
"walmart.com",
|
|
15499
|
+
"target.com",
|
|
15500
|
+
"aliexpress.com",
|
|
15501
|
+
"alibaba.com",
|
|
15502
|
+
"temu.com",
|
|
15503
|
+
"shein.com",
|
|
15504
|
+
"craigslist.org",
|
|
15505
|
+
"facebookmarketplace.com",
|
|
15506
|
+
"bestbuy.com",
|
|
15507
|
+
"costco.com",
|
|
15508
|
+
"homedepot.com",
|
|
15509
|
+
"wayfair.com",
|
|
15510
|
+
"mercadolibre.com",
|
|
15511
|
+
"shopee.com",
|
|
15512
|
+
"lazada.com",
|
|
15513
|
+
"jiji.ng",
|
|
15514
|
+
"jumia.com.ng",
|
|
15515
|
+
"konga.com",
|
|
15516
|
+
"propertypro.ng",
|
|
15517
|
+
"property24.com",
|
|
15518
|
+
// Travel, property, jobs and business directories
|
|
15519
|
+
"airbnb.com",
|
|
15520
|
+
"airbnb.co.uk",
|
|
15521
|
+
"airbnb.ca",
|
|
15522
|
+
"airbnb.com.au",
|
|
15523
|
+
"airbnb.fr",
|
|
15524
|
+
"airbnb.de",
|
|
15525
|
+
"booking.com",
|
|
15526
|
+
"expedia.com",
|
|
15527
|
+
"tripadvisor.com",
|
|
15528
|
+
"agoda.com",
|
|
15529
|
+
"hotels.com",
|
|
15530
|
+
"kayak.com",
|
|
15531
|
+
"skyscanner.net",
|
|
15532
|
+
"zillow.com",
|
|
15533
|
+
"realtor.com",
|
|
15534
|
+
"rightmove.co.uk",
|
|
15535
|
+
"zoopla.co.uk",
|
|
15536
|
+
"indeed.com",
|
|
15537
|
+
"glassdoor.com",
|
|
15538
|
+
"yelp.com",
|
|
15539
|
+
"yellowpages.com",
|
|
15540
|
+
"trustpilot.com",
|
|
15541
|
+
"crunchbase.com",
|
|
15542
|
+
"g2.com",
|
|
15543
|
+
"capterra.com",
|
|
15544
|
+
"clutch.co",
|
|
15545
|
+
"zoominfo.com",
|
|
15546
|
+
"apollo.io",
|
|
15547
|
+
"monster.com",
|
|
15548
|
+
"ziprecruiter.com",
|
|
15549
|
+
"seek.com.au",
|
|
15550
|
+
"naukri.com",
|
|
15551
|
+
"redfin.com",
|
|
15552
|
+
"trulia.com",
|
|
15553
|
+
"apartments.com",
|
|
15554
|
+
"realtor.ca",
|
|
15555
|
+
"realestate.com.au",
|
|
15556
|
+
"vrbo.com",
|
|
15557
|
+
"trip.com",
|
|
15558
|
+
"trivago.com",
|
|
15559
|
+
"hostelworld.com"
|
|
15560
|
+
];
|
|
15561
|
+
var blockedCloudTargetMessage = "This website is not supported for cloud runs. Use your own website or app instead.";
|
|
15562
|
+
function isBlockedCloudTarget(value) {
|
|
15563
|
+
try {
|
|
15564
|
+
const hostname5 = new URL(value).hostname.toLowerCase().replace(/\.+$/, "");
|
|
15565
|
+
return blockedCloudDomains.some((domain2) => hostname5 === domain2 || hostname5.endsWith(`.${domain2}`));
|
|
15566
|
+
} catch {
|
|
15567
|
+
return false;
|
|
15568
|
+
}
|
|
15569
|
+
}
|
|
15570
|
+
|
|
15571
|
+
// ../contracts/src/cloud-run.ts
|
|
15572
|
+
var publicTarget = external_exports.string().url().max(2048).refine((value) => {
|
|
15573
|
+
let u;
|
|
15574
|
+
try {
|
|
15575
|
+
u = new URL(value);
|
|
15576
|
+
} catch {
|
|
15577
|
+
return false;
|
|
15578
|
+
}
|
|
15579
|
+
return ["http:", "https:"].includes(u.protocol) && !u.username && !u.password && !u.port && !u.hash && u.hostname.includes(".") && !/^[0-9.]+$/.test(u.hostname) && !u.hostname.endsWith(".exe.xyz") && !u.hostname.endsWith(".local") && !u.hostname.endsWith(".internal") && !u.hostname.endsWith(".localhost");
|
|
15580
|
+
}, "Use a public HTTP(S) URL without credentials, fragments, or a custom port.");
|
|
15581
|
+
var cloudRunTarget = publicTarget.refine((value) => !isBlockedCloudTarget(value), blockedCloudTargetMessage);
|
|
15582
|
+
var interactionFields = {
|
|
15583
|
+
mode: external_exports.enum(["browse", "interact"]).optional(),
|
|
15584
|
+
interactionConfirmed: external_exports.boolean().optional(),
|
|
15585
|
+
// Legacy payload compatibility only; Interact automatically permits API destinations.
|
|
15586
|
+
allowedWriteOrigins: external_exports.array(cloudRunTarget.refine((v) => new URL(v).origin === v, "Use an exact origin.")).max(5).optional()
|
|
15587
|
+
};
|
|
15588
|
+
var sideEffectSchema = external_exports.object({
|
|
15589
|
+
action: external_exports.enum(["fill", "check", "select", "press", "click"]),
|
|
15590
|
+
page: external_exports.string().max(2048),
|
|
15591
|
+
target: external_exports.string().max(160),
|
|
15592
|
+
status: external_exports.enum(["performed", "unknown"])
|
|
15593
|
+
}).strict();
|
|
15594
|
+
var interactionReportFields = {
|
|
15595
|
+
mode: external_exports.enum(["browse", "interact"]).optional(),
|
|
15596
|
+
sideEffects: external_exports.array(sideEffectSchema).max(30).optional(),
|
|
15597
|
+
cleanup: external_exports.literal("not_attempted").optional()
|
|
15598
|
+
};
|
|
15599
|
+
var cloudSubmission = external_exports.object({ ...interactionFields, id: external_exports.string().uuid(), url: cloudRunTarget, prompt: external_exports.string().trim().min(1).max(2e3), expectation: external_exports.string().trim().min(1).max(1e3).optional(), account: external_exports.string().regex(/^[a-z0-9-]{1,60}$/).optional() }).strict();
|
|
15600
|
+
var siteAccountSchema = external_exports.object({
|
|
15601
|
+
name: external_exports.string().regex(/^[a-z0-9-]{1,60}$/),
|
|
15602
|
+
origin: publicTarget.refine((v) => new URL(v).protocol === "https:" && new URL(v).origin === v, "Use an exact HTTPS origin."),
|
|
15603
|
+
loginUrl: publicTarget.refine((v) => new URL(v).protocol === "https:").optional(),
|
|
15604
|
+
username: external_exports.string().min(1).max(320),
|
|
15605
|
+
password: external_exports.string().min(1).max(1e3)
|
|
15606
|
+
}).strict().refine((v) => !v.loginUrl || new URL(v.loginUrl).origin === v.origin, "Login must use the saved website origin.");
|
|
15199
15607
|
|
|
15200
15608
|
// ../contracts/src/recording.ts
|
|
15201
15609
|
var cursorAppearanceSchema = external_exports.object({
|
|
@@ -15236,6 +15644,7 @@ var recordingSchema = external_exports.object({
|
|
|
15236
15644
|
})
|
|
15237
15645
|
});
|
|
15238
15646
|
var agentOutcomeSchema = external_exports.object({
|
|
15647
|
+
...interactionReportFields,
|
|
15239
15648
|
outcome: external_exports.enum(["walkthrough", "passed", "failed", "inconclusive"]),
|
|
15240
15649
|
summary: external_exports.string().min(1).max(1e3),
|
|
15241
15650
|
evidence: external_exports.string().max(1e3)
|
|
@@ -15323,15 +15732,15 @@ async function attachCursorOverlay(page, cdp, input) {
|
|
|
15323
15732
|
const appearance = cursorAppearanceSchema.parse(input ?? {});
|
|
15324
15733
|
if (!appearance.enabled) return async () => {
|
|
15325
15734
|
};
|
|
15326
|
-
const
|
|
15327
|
-
const source = `(${installCursorOverlay.toString()})(${JSON.stringify({ ...appearance, key })})`;
|
|
15735
|
+
const key2 = `__rillCursor_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
15736
|
+
const source = `(${installCursorOverlay.toString()})(${JSON.stringify({ ...appearance, key: key2 })})`;
|
|
15328
15737
|
await cdp.send("Page.enable");
|
|
15329
15738
|
const { identifier } = await cdp.send("Page.addScriptToEvaluateOnNewDocument", { source });
|
|
15330
15739
|
let stopped = false;
|
|
15331
|
-
const removeFromFrame = (frame) => frame.evaluate((
|
|
15332
|
-
const cleanup2 = window[
|
|
15740
|
+
const removeFromFrame = (frame) => frame.evaluate((key3) => {
|
|
15741
|
+
const cleanup2 = window[key3];
|
|
15333
15742
|
if (typeof cleanup2 === "function") cleanup2();
|
|
15334
|
-
},
|
|
15743
|
+
}, key2).catch(() => void 0);
|
|
15335
15744
|
const onNavigation = (frame) => {
|
|
15336
15745
|
if (stopped) return;
|
|
15337
15746
|
void frame.evaluate(source).then(() => {
|
|
@@ -15356,10 +15765,10 @@ async function attachCursorOverlay(page, cdp, input) {
|
|
|
15356
15765
|
|
|
15357
15766
|
// ../recorder/src/session.ts
|
|
15358
15767
|
import { chromium } from "playwright-core";
|
|
15359
|
-
import { spawn as
|
|
15768
|
+
import { spawn as spawn6 } from "child_process";
|
|
15360
15769
|
import { createServer } from "net";
|
|
15361
|
-
import { mkdir as
|
|
15362
|
-
import { join as
|
|
15770
|
+
import { mkdir as mkdir6, mkdtemp, rm as rm5, writeFile as writeFile5 } from "fs/promises";
|
|
15771
|
+
import { join as join9 } from "path";
|
|
15363
15772
|
import { tmpdir as tmpdir2 } from "os";
|
|
15364
15773
|
|
|
15365
15774
|
// ../recorder/src/bundle.ts
|
|
@@ -15385,11 +15794,16 @@ async function writeBundle(bundle, outputPath) {
|
|
|
15385
15794
|
|
|
15386
15795
|
// ../recorder/src/environment.ts
|
|
15387
15796
|
import { existsSync } from "fs";
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
if (
|
|
15797
|
+
import { win32 as win323 } from "path";
|
|
15798
|
+
function findChromeExecutable(platform = process.platform, env = process.env, exists = existsSync) {
|
|
15799
|
+
if (env.RILL_CHROME_PATH) return env.RILL_CHROME_PATH;
|
|
15800
|
+
if (platform === "darwin") return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
15801
|
+
if (platform === "win32") {
|
|
15802
|
+
const candidates2 = [env.PROGRAMFILES ?? env.ProgramFiles, env["PROGRAMFILES(X86)"] ?? env["ProgramFiles(x86)"], env.LOCALAPPDATA].filter((root) => Boolean(root)).map((root) => win323.join(root, "Google", "Chrome", "Application", "chrome.exe"));
|
|
15803
|
+
return candidates2.find(exists) ?? win323.join(env.PROGRAMFILES ?? env.ProgramFiles ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe");
|
|
15804
|
+
}
|
|
15391
15805
|
const candidates = ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser"];
|
|
15392
|
-
return candidates.find(
|
|
15806
|
+
return candidates.find(exists) ?? "google-chrome";
|
|
15393
15807
|
}
|
|
15394
15808
|
|
|
15395
15809
|
// ../recorder/src/redaction.ts
|
|
@@ -15401,7 +15815,7 @@ function sanitizeUrl(input) {
|
|
|
15401
15815
|
url2.username = "";
|
|
15402
15816
|
url2.password = "";
|
|
15403
15817
|
url2.hash = "";
|
|
15404
|
-
for (const
|
|
15818
|
+
for (const key2 of [...url2.searchParams.keys()]) url2.searchParams.set(key2, "[REDACTED]");
|
|
15405
15819
|
return url2.toString();
|
|
15406
15820
|
} catch {
|
|
15407
15821
|
return "[invalid-url]";
|
|
@@ -15432,16 +15846,16 @@ function redactJson(value, depth = 0) {
|
|
|
15432
15846
|
if (depth > 12) return "[TRUNCATED]";
|
|
15433
15847
|
if (Array.isArray(value)) return value.slice(0, 100).map((item) => redactJson(item, depth + 1));
|
|
15434
15848
|
if (value && typeof value === "object") {
|
|
15435
|
-
return Object.fromEntries(Object.entries(value).slice(0, 200).map(([
|
|
15849
|
+
return Object.fromEntries(Object.entries(value).slice(0, 200).map(([key2, child]) => [key2, secretKeyPattern.test(key2) ? "[REDACTED]" : redactJson(child, depth + 1)]));
|
|
15436
15850
|
}
|
|
15437
15851
|
if (typeof value === "string") return redactText(value, 8e3);
|
|
15438
15852
|
return value;
|
|
15439
15853
|
}
|
|
15440
15854
|
|
|
15441
15855
|
// ../recorder/src/encoding.ts
|
|
15442
|
-
import { open as open2, writeFile, mkdir as
|
|
15443
|
-
import { spawn as
|
|
15444
|
-
import { join as
|
|
15856
|
+
import { open as open2, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
|
|
15857
|
+
import { spawn as spawn5 } from "child_process";
|
|
15858
|
+
import { join as join7 } from "path";
|
|
15445
15859
|
var active = 0;
|
|
15446
15860
|
var waiting = [];
|
|
15447
15861
|
function encodingConcurrency() {
|
|
@@ -15449,8 +15863,22 @@ function encodingConcurrency() {
|
|
|
15449
15863
|
if (!Number.isInteger(n) || n < 1 || n > 8) throw new Error("Invalid RILL_ENCODING_CONCURRENCY");
|
|
15450
15864
|
return n;
|
|
15451
15865
|
}
|
|
15452
|
-
async function withEncodingSlot(work) {
|
|
15453
|
-
|
|
15866
|
+
async function withEncodingSlot(work, signal) {
|
|
15867
|
+
signal?.throwIfAborted();
|
|
15868
|
+
if (active >= encodingConcurrency()) await new Promise((resolve, reject) => {
|
|
15869
|
+
const granted = () => {
|
|
15870
|
+
signal?.removeEventListener("abort", abort);
|
|
15871
|
+
resolve();
|
|
15872
|
+
};
|
|
15873
|
+
const abort = () => {
|
|
15874
|
+
const i = waiting.indexOf(granted);
|
|
15875
|
+
if (i >= 0) waiting.splice(i, 1);
|
|
15876
|
+
reject(new Error("encoding_aborted"));
|
|
15877
|
+
};
|
|
15878
|
+
waiting.push(granted);
|
|
15879
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
15880
|
+
if (signal?.aborted) abort();
|
|
15881
|
+
});
|
|
15454
15882
|
else active++;
|
|
15455
15883
|
try {
|
|
15456
15884
|
return await work();
|
|
@@ -15471,11 +15899,15 @@ async function encodeFrames(directory, manifest, output, signal) {
|
|
|
15471
15899
|
lines.push(`file 'frame-${String(i).padStart(6, "0")}.jpg'`, `duration ${Math.max(1 / 60, duration3).toFixed(6)}`);
|
|
15472
15900
|
}
|
|
15473
15901
|
lines.push(`file 'frame-${String(manifest.frames.length - 1).padStart(6, "0")}.jpg'`);
|
|
15474
|
-
await
|
|
15902
|
+
await writeFile3(join7(directory, "frames.ffconcat"), lines.join("\n") + "\n", { mode: 384 });
|
|
15475
15903
|
const threads = Number(process.env.RILL_FFMPEG_THREADS ?? 1);
|
|
15476
15904
|
if (!Number.isInteger(threads) || threads < 1 || threads > 16) throw new Error("Invalid RILL_FFMPEG_THREADS");
|
|
15905
|
+
signal?.throwIfAborted();
|
|
15906
|
+
const dimensions = `scale=${manifest.width}:${manifest.height}:force_original_aspect_ratio=decrease,pad=${manifest.width}:${manifest.height}:(ow-iw)/2:(oh-ih)/2,setsar=1`;
|
|
15907
|
+
const filters = output.endsWith(".mp4") ? `${dimensions},fps=30,tpad=stop_mode=clone:stop_duration=1` : dimensions;
|
|
15908
|
+
const codec2 = output.endsWith(".mp4") ? ["-c:v", "libx264", "-preset", "veryfast", "-crf", "23", "-movflags", "+faststart", "-fps_mode", "cfr", "-t", String(Math.min(60, manifest.durationSeconds))] : ["-c:v", "libvpx-vp9", "-deadline", "good", "-cpu-used", "4", "-b:v", "0", "-crf", "32"];
|
|
15477
15909
|
await new Promise((resolve, reject) => {
|
|
15478
|
-
const child =
|
|
15910
|
+
const child = spawn5(process.env.RILL_FFMPEG ?? "ffmpeg", ["-hide_banner", "-loglevel", "error", "-protocol_whitelist", "file", "-f", "concat", "-safe", "1", "-i", "frames.ffconcat", "-vsync", "vfr", "-pix_fmt", "yuv420p", "-vf", filters, ...codec2, "-threads", String(threads), "-y", output], { cwd: directory, windowsHide: true, stdio: ["ignore", "ignore", "pipe"] });
|
|
15479
15911
|
let stderr = "";
|
|
15480
15912
|
child.stderr.on("data", (chunk) => {
|
|
15481
15913
|
stderr = (stderr + String(chunk)).slice(-2e3);
|
|
@@ -15483,6 +15915,7 @@ async function encodeFrames(directory, manifest, output, signal) {
|
|
|
15483
15915
|
const abort = () => child.kill("SIGKILL");
|
|
15484
15916
|
const timer = setTimeout(abort, 20 * 6e4);
|
|
15485
15917
|
signal?.addEventListener("abort", abort, { once: true });
|
|
15918
|
+
if (signal?.aborted) abort();
|
|
15486
15919
|
const clean = () => {
|
|
15487
15920
|
clearTimeout(timer);
|
|
15488
15921
|
signal?.removeEventListener("abort", abort);
|
|
@@ -15496,12 +15929,12 @@ async function encodeFrames(directory, manifest, output, signal) {
|
|
|
15496
15929
|
code === 0 ? resolve() : reject(new Error(`ffmpeg failed (${code}): ${stderr}`));
|
|
15497
15930
|
});
|
|
15498
15931
|
});
|
|
15499
|
-
});
|
|
15932
|
+
}, signal);
|
|
15500
15933
|
}
|
|
15501
15934
|
|
|
15502
15935
|
// ../recorder/src/screencast.ts
|
|
15503
|
-
import { mkdir as
|
|
15504
|
-
import { join as
|
|
15936
|
+
import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile6, open as open3 } from "fs/promises";
|
|
15937
|
+
import { join as join8 } from "path";
|
|
15505
15938
|
var ScreencastRecorder = class {
|
|
15506
15939
|
constructor(session, directory, width, height) {
|
|
15507
15940
|
this.session = session;
|
|
@@ -15509,6 +15942,10 @@ var ScreencastRecorder = class {
|
|
|
15509
15942
|
this.width = width;
|
|
15510
15943
|
this.height = height;
|
|
15511
15944
|
}
|
|
15945
|
+
session;
|
|
15946
|
+
directory;
|
|
15947
|
+
width;
|
|
15948
|
+
height;
|
|
15512
15949
|
frames = [];
|
|
15513
15950
|
pendingWrites = /* @__PURE__ */ new Set();
|
|
15514
15951
|
started = false;
|
|
@@ -15516,7 +15953,7 @@ var ScreencastRecorder = class {
|
|
|
15516
15953
|
frameBytes = 0;
|
|
15517
15954
|
captureError;
|
|
15518
15955
|
async start() {
|
|
15519
|
-
await
|
|
15956
|
+
await mkdir5(this.directory, { recursive: true, mode: 448 });
|
|
15520
15957
|
this.session.on("Page.screencastFrame", (payload) => {
|
|
15521
15958
|
const bytes = Buffer.from(payload.data, "base64");
|
|
15522
15959
|
if (this.frameCounter >= 36e3 || this.frameBytes + bytes.length > ENCODING_MAX_BYTES || bytes.length > ENCODING_PART_BYTES) {
|
|
@@ -15525,9 +15962,9 @@ var ScreencastRecorder = class {
|
|
|
15525
15962
|
return;
|
|
15526
15963
|
}
|
|
15527
15964
|
this.frameBytes += bytes.length;
|
|
15528
|
-
const path =
|
|
15965
|
+
const path = join8(this.directory, `frame-${String(this.frameCounter).padStart(6, "0")}.jpg`);
|
|
15529
15966
|
this.frameCounter += 1;
|
|
15530
|
-
const write =
|
|
15967
|
+
const write = writeFile4(path, bytes, { mode: 384 }).then(() => {
|
|
15531
15968
|
this.frames.push({ path, timestamp: payload.metadata.timestamp ?? Date.now() / 1e3 });
|
|
15532
15969
|
}).catch((error51) => {
|
|
15533
15970
|
this.captureError = error51 instanceof Error ? error51 : new Error(String(error51));
|
|
@@ -15541,7 +15978,7 @@ var ScreencastRecorder = class {
|
|
|
15541
15978
|
await this.session.send("Page.startScreencast", { format: "jpeg", quality: 82, maxWidth: this.width, maxHeight: this.height, everyNthFrame: 1 });
|
|
15542
15979
|
this.started = true;
|
|
15543
15980
|
}
|
|
15544
|
-
async stop(outputPath, expectedDurationSeconds, captureOnly = false) {
|
|
15981
|
+
async stop(outputPath, expectedDurationSeconds, captureOnly = false, signal) {
|
|
15545
15982
|
if (this.started) await this.session.send("Page.stopScreencast").catch(() => void 0);
|
|
15546
15983
|
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
15547
15984
|
await Promise.all(this.pendingWrites);
|
|
@@ -15549,10 +15986,10 @@ var ScreencastRecorder = class {
|
|
|
15549
15986
|
if (!this.frames.length) throw new Error("No video frames were captured.");
|
|
15550
15987
|
const ordered = this.frames.toSorted((a, b) => a.timestamp - b.timestamp);
|
|
15551
15988
|
const frames = [];
|
|
15552
|
-
const bundle = await open3(
|
|
15989
|
+
const bundle = await open3(join8(this.directory, "frames.bin"), "w", 384);
|
|
15553
15990
|
try {
|
|
15554
15991
|
for (const frame of ordered) {
|
|
15555
|
-
const bytes = await
|
|
15992
|
+
const bytes = await readFile6(frame.path);
|
|
15556
15993
|
await bundle.writeFile(bytes);
|
|
15557
15994
|
frames.push({ timestamp: frame.timestamp, size: bytes.length });
|
|
15558
15995
|
}
|
|
@@ -15560,8 +15997,8 @@ var ScreencastRecorder = class {
|
|
|
15560
15997
|
await bundle.close();
|
|
15561
15998
|
}
|
|
15562
15999
|
const manifest = frameManifestSchema.parse({ version: 1, durationSeconds: Math.min(600, expectedDurationSeconds), width: this.width, height: this.height, frames });
|
|
15563
|
-
await
|
|
15564
|
-
if (!captureOnly) await encodeFrames(this.directory, manifest, outputPath);
|
|
16000
|
+
await writeFile4(join8(this.directory, "capture-manifest.json"), JSON.stringify(manifest), { mode: 384 });
|
|
16001
|
+
if (!captureOnly) await encodeFrames(this.directory, manifest, outputPath, signal);
|
|
15565
16002
|
return { frameCount: this.frames.length, outputPath };
|
|
15566
16003
|
}
|
|
15567
16004
|
};
|
|
@@ -15585,6 +16022,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
15585
16022
|
requestEvents = /* @__PURE__ */ new Map();
|
|
15586
16023
|
stopPromise;
|
|
15587
16024
|
maxTimer;
|
|
16025
|
+
maxDurationSeconds;
|
|
15588
16026
|
removeCursorOverlay;
|
|
15589
16027
|
diagnosticsPaused = false;
|
|
15590
16028
|
secretValues = [];
|
|
@@ -15595,7 +16033,10 @@ var RecordingSession = class _RecordingSession {
|
|
|
15595
16033
|
bodyBytes = 0;
|
|
15596
16034
|
truncated = false;
|
|
15597
16035
|
constructor(options, runtime) {
|
|
16036
|
+
this.maxDurationSeconds = options.maxDurationSeconds ?? 600;
|
|
15598
16037
|
this.encoding = options.encoding ?? "local";
|
|
16038
|
+
this.videoFormat = options.videoFormat ?? "webm";
|
|
16039
|
+
this.captureSignal = options.signal;
|
|
15599
16040
|
this.recordingId = options.recordingId;
|
|
15600
16041
|
this.cdpUrl = runtime.cdpUrl;
|
|
15601
16042
|
this.browser = runtime.browser;
|
|
@@ -15609,15 +16050,17 @@ var RecordingSession = class _RecordingSession {
|
|
|
15609
16050
|
static async start(options) {
|
|
15610
16051
|
const width = options.width ?? 1280;
|
|
15611
16052
|
const height = options.height ?? 720;
|
|
15612
|
-
await
|
|
15613
|
-
if (options.encoding === "remote") await
|
|
15614
|
-
const runtime = options.cdpUrl ? await connectAttached(options.cdpUrl, options.outputDirectory) : await launchManaged({ headed: options.headed ?? false, width, height, outputDirectory: options.outputDirectory, proxyServer: options.proxyServer });
|
|
16053
|
+
await mkdir6(options.outputDirectory, { recursive: true, mode: 448 });
|
|
16054
|
+
if (options.encoding === "remote") await writeFile5(join9(options.outputDirectory, "remote-capture.json"), JSON.stringify({ expiresAt: Date.now() + 864e5 }), { mode: 384 });
|
|
16055
|
+
const runtime = options.cdpUrl ? await connectAttached(options.cdpUrl, options.outputDirectory) : await launchManaged({ headed: options.headed ?? false, width, height, outputDirectory: options.outputDirectory, proxyServer: options.proxyServer, blockServiceWorkers: options.blockServiceWorkers });
|
|
15615
16056
|
const cdpSession = await runtime.context.newCDPSession(runtime.page);
|
|
15616
|
-
const screencast = new ScreencastRecorder(cdpSession,
|
|
16057
|
+
const screencast = new ScreencastRecorder(cdpSession, join9(options.outputDirectory, "frames"), width, height);
|
|
15617
16058
|
const session = new _RecordingSession(options, { ...runtime, screencast });
|
|
15618
16059
|
try {
|
|
15619
16060
|
await session.attachDiagnostics(options.captureNetworkBodies ?? false);
|
|
15620
|
-
session.removeCursorOverlay = await attachCursorOverlay(runtime.page, cdpSession, options.cursorAppearance);
|
|
16061
|
+
if (!options.hideCursor) session.removeCursorOverlay = await attachCursorOverlay(runtime.page, cdpSession, options.cursorAppearance);
|
|
16062
|
+
await options.beforeCapture?.(runtime.page);
|
|
16063
|
+
session.startedAt = /* @__PURE__ */ new Date();
|
|
15621
16064
|
await screencast.start();
|
|
15622
16065
|
if (options.url) await session.page.goto(options.url, { waitUntil: "domcontentloaded" });
|
|
15623
16066
|
} catch (error51) {
|
|
@@ -15626,7 +16069,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
15626
16069
|
}
|
|
15627
16070
|
session.maxTimer = setTimeout(() => {
|
|
15628
16071
|
void session.stop("max_duration").catch(() => void 0);
|
|
15629
|
-
}, (
|
|
16072
|
+
}, Math.max(0, session.startedAt.getTime() + session.maxDurationSeconds * 1e3 - Date.now()));
|
|
15630
16073
|
return session;
|
|
15631
16074
|
}
|
|
15632
16075
|
// In-process agents share this connection so exposed diagnostic bindings are
|
|
@@ -15635,7 +16078,17 @@ var RecordingSession = class _RecordingSession {
|
|
|
15635
16078
|
return this.page;
|
|
15636
16079
|
}
|
|
15637
16080
|
status() {
|
|
15638
|
-
|
|
16081
|
+
const elapsedSeconds = Math.max(0, (Date.now() - this.startedAt.getTime()) / 1e3);
|
|
16082
|
+
return {
|
|
16083
|
+
recordingId: this.recordingId,
|
|
16084
|
+
cdpUrl: this.cdpUrl,
|
|
16085
|
+
startedAt: this.startedAt.toISOString(),
|
|
16086
|
+
maxDurationSeconds: this.maxDurationSeconds,
|
|
16087
|
+
elapsedSeconds,
|
|
16088
|
+
remainingSeconds: this.stopPromise ? 0 : Math.max(0, this.maxDurationSeconds - elapsedSeconds),
|
|
16089
|
+
stopsAt: new Date(this.startedAt.getTime() + this.maxDurationSeconds * 1e3).toISOString(),
|
|
16090
|
+
status: this.stopPromise ? "finalizing" : "recording"
|
|
16091
|
+
};
|
|
15639
16092
|
}
|
|
15640
16093
|
async stop(reason = "agent_request") {
|
|
15641
16094
|
this.stopPromise ??= this.finalize(reason);
|
|
@@ -15645,8 +16098,8 @@ var RecordingSession = class _RecordingSession {
|
|
|
15645
16098
|
if (this.maxTimer) clearTimeout(this.maxTimer);
|
|
15646
16099
|
const durationSeconds = Math.max(0.1, (Date.now() - this.startedAt.getTime()) / 1e3);
|
|
15647
16100
|
const outputDirectory = this.outputDirectory;
|
|
15648
|
-
const videoPath =
|
|
15649
|
-
const diagnosticsPath =
|
|
16101
|
+
const videoPath = join9(outputDirectory, `recording.${this.videoFormat}`);
|
|
16102
|
+
const diagnosticsPath = join9(outputDirectory, "diagnostics-v1.ndjson.gz");
|
|
15650
16103
|
const bundle = {
|
|
15651
16104
|
schemaVersion: 1,
|
|
15652
16105
|
capturePolicyVersion: "safe-default-v1",
|
|
@@ -15663,21 +16116,23 @@ var RecordingSession = class _RecordingSession {
|
|
|
15663
16116
|
try {
|
|
15664
16117
|
const safeBundle = JSON.parse(this.secretValues.reduce((text, secret) => text.split(JSON.stringify(secret).slice(1, -1)).join("[redacted]"), JSON.stringify(bundle)));
|
|
15665
16118
|
await writeBundle(safeBundle, diagnosticsPath);
|
|
15666
|
-
await this.screencast.stop(videoPath, durationSeconds, this.encoding === "remote");
|
|
16119
|
+
await this.screencast.stop(videoPath, durationSeconds, this.encoding === "remote", this.captureSignal);
|
|
15667
16120
|
} finally {
|
|
15668
16121
|
await this.removeCursorOverlay?.();
|
|
15669
16122
|
if (this.browserProcess) {
|
|
15670
16123
|
await this.browser.close().catch(() => void 0);
|
|
15671
16124
|
await terminateBrowser(this.browserProcess);
|
|
15672
|
-
if (this.profileDirectory) await
|
|
16125
|
+
if (this.profileDirectory) await rm5(this.profileDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
15673
16126
|
}
|
|
15674
16127
|
}
|
|
15675
16128
|
const consoleErrors = this.consoleEvents.filter((event) => event.severity === "error").length;
|
|
15676
16129
|
const failedRequests = this.networkEvents.filter((event) => event.failureReason).length;
|
|
15677
16130
|
const httpErrors = this.networkEvents.filter((event) => (event.status ?? 0) >= 400).length;
|
|
15678
|
-
return { recordingId: this.recordingId, videoPath, ...this.encoding === "remote" ? { frameDirectory:
|
|
16131
|
+
return { recordingId: this.recordingId, videoPath, ...this.encoding === "remote" ? { frameDirectory: join9(outputDirectory, "frames") } : {}, diagnosticsPath, durationSeconds, stoppedReason: reason, diagnostics: { consoleErrors, failedRequests, httpErrors, truncated: this.truncated, bodyCaptureEnabled: this.captureNetworkBodies } };
|
|
15679
16132
|
}
|
|
15680
16133
|
captureNetworkBodies = false;
|
|
16134
|
+
captureSignal;
|
|
16135
|
+
videoFormat = "webm";
|
|
15681
16136
|
encoding = "local";
|
|
15682
16137
|
outputDirectory = "";
|
|
15683
16138
|
async attachDiagnostics(captureNetworkBodies) {
|
|
@@ -15787,7 +16242,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
15787
16242
|
};
|
|
15788
16243
|
async function launchManaged(options) {
|
|
15789
16244
|
const port = await getAvailablePort();
|
|
15790
|
-
const profileDirectory = await mkdtemp(
|
|
16245
|
+
const profileDirectory = await mkdtemp(join9(tmpdir2(), "rill-chrome-"));
|
|
15791
16246
|
const executable = findChromeExecutable();
|
|
15792
16247
|
const args = [
|
|
15793
16248
|
`--remote-debugging-port=${port}`,
|
|
@@ -15804,19 +16259,19 @@ async function launchManaged(options) {
|
|
|
15804
16259
|
...options.headed ? [] : ["--headless=new", "--hide-scrollbars"],
|
|
15805
16260
|
"about:blank"
|
|
15806
16261
|
];
|
|
15807
|
-
const env = options.proxyServer ? Object.fromEntries(Object.entries(process.env).filter(([
|
|
15808
|
-
const browserProcess =
|
|
16262
|
+
const env = options.proxyServer ? Object.fromEntries(Object.entries(process.env).filter(([key2]) => ["PATH", "HOME", "TMPDIR", "DISPLAY", "LANG", "LD_LIBRARY_PATH", "XDG_RUNTIME_DIR"].includes(key2))) : process.env;
|
|
16263
|
+
const browserProcess = spawn6(executable, args, { stdio: ["ignore", "ignore", "pipe"], detached: false, windowsHide: true, env });
|
|
15809
16264
|
const cdpUrl = `http://127.0.0.1:${port}`;
|
|
15810
16265
|
try {
|
|
15811
16266
|
await waitForCdp(cdpUrl, browserProcess);
|
|
15812
16267
|
const browser = await chromium.connectOverCDP(cdpUrl, { artifactsDir: options.outputDirectory, isLocal: true });
|
|
15813
|
-
const context = browser.contexts()[0];
|
|
16268
|
+
const context = options.blockServiceWorkers ? await browser.newContext({ serviceWorkers: "block" }) : browser.contexts()[0];
|
|
15814
16269
|
const page = context.pages()[0] ?? await context.newPage();
|
|
15815
16270
|
await page.setViewportSize({ width: options.width, height: options.height });
|
|
15816
16271
|
return { cdpUrl, browser, context, page, browserProcess, profileDirectory };
|
|
15817
16272
|
} catch (error51) {
|
|
15818
16273
|
await terminateBrowser(browserProcess);
|
|
15819
|
-
await
|
|
16274
|
+
await rm5(profileDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
15820
16275
|
throw error51;
|
|
15821
16276
|
}
|
|
15822
16277
|
}
|
|
@@ -15866,16 +16321,31 @@ async function waitForCdp(cdpUrl, browserProcess) {
|
|
|
15866
16321
|
var sessions = /* @__PURE__ */ new Map();
|
|
15867
16322
|
var lastActivity = Date.now();
|
|
15868
16323
|
async function runDaemon() {
|
|
15869
|
-
|
|
15870
|
-
await
|
|
15871
|
-
|
|
16324
|
+
if (process.platform === "win32" && process.env.RILL_SOCKET) throw new Error("RILL_SOCKET overrides are not supported by authenticated Windows IPC.");
|
|
16325
|
+
const identity = process.platform === "win32" ? await windowsIpcIdentity() : void 0;
|
|
16326
|
+
const socketPath = identity?.pipe ?? daemonSocketPath();
|
|
16327
|
+
await mkdir7(identity ? dirname(daemonStatePath()) : dirname(socketPath), { recursive: true, mode: 448 });
|
|
16328
|
+
await mkdir7(recordingTempRoot(), { recursive: true, mode: 448 });
|
|
15872
16329
|
await restoreRecoverableSessions();
|
|
15873
16330
|
await purgeExpiredCaptures();
|
|
15874
|
-
await unlink(socketPath).catch((error51) => {
|
|
16331
|
+
if (!identity) await unlink(socketPath).catch((error51) => {
|
|
15875
16332
|
if (error51.code !== "ENOENT") throw error51;
|
|
15876
16333
|
});
|
|
15877
|
-
const server = createServer2((request3, response) =>
|
|
15878
|
-
|
|
16334
|
+
const server = createServer2((request3, response) => {
|
|
16335
|
+
if (identity && !acceptsWindowsIpcToken(identity.token, request3.headers.authorization)) {
|
|
16336
|
+
request3.resume();
|
|
16337
|
+
json2(response, 401, { error: { code: "authentication_required", message: "Recorder authentication required." } });
|
|
16338
|
+
return;
|
|
16339
|
+
}
|
|
16340
|
+
void route(request3, response);
|
|
16341
|
+
});
|
|
16342
|
+
await new Promise((resolve, reject) => {
|
|
16343
|
+
server.once("error", reject);
|
|
16344
|
+
server.listen(socketPath, () => {
|
|
16345
|
+
if (identity) resolve();
|
|
16346
|
+
else void chmod(socketPath, 384).then(resolve, reject);
|
|
16347
|
+
});
|
|
16348
|
+
});
|
|
15879
16349
|
const idleTimer = setInterval(() => {
|
|
15880
16350
|
void (async () => {
|
|
15881
16351
|
await purgeExpiredCaptures();
|
|
@@ -15888,7 +16358,7 @@ async function runDaemon() {
|
|
|
15888
16358
|
const close = () => {
|
|
15889
16359
|
clearInterval(idleTimer);
|
|
15890
16360
|
server.close();
|
|
15891
|
-
void unlink(socketPath).catch(() => void 0);
|
|
16361
|
+
if (!identity) void unlink(socketPath).catch(() => void 0);
|
|
15892
16362
|
};
|
|
15893
16363
|
process.once("SIGTERM", close);
|
|
15894
16364
|
process.once("SIGINT", close);
|
|
@@ -15902,12 +16372,12 @@ async function route(request3, response) {
|
|
|
15902
16372
|
try {
|
|
15903
16373
|
const input = await readJson(request3);
|
|
15904
16374
|
if (!/^rec_[a-zA-Z0-9_-]+$/.test(input.recordingId)) return json2(response, 400, { error: { code: "validation_failed", message: "recordingId is required." } });
|
|
15905
|
-
const outputDirectory =
|
|
16375
|
+
const outputDirectory = join10(recordingTempRoot(), input.recordingId);
|
|
15906
16376
|
const session = await RecordingSession.start({ ...input, outputDirectory });
|
|
15907
16377
|
const status = session.status();
|
|
15908
16378
|
const entry = { session, base: { recordingId: status.recordingId, cdpUrl: status.cdpUrl, startedAt: status.startedAt }, state: "recording" };
|
|
15909
16379
|
sessions.set(input.recordingId, entry);
|
|
15910
|
-
const maxTimer = setTimeout(() => void finalizeEntry(entry, "max_duration"), (
|
|
16380
|
+
const maxTimer = setTimeout(() => void finalizeEntry(entry, "max_duration"), Math.max(0, Date.parse(status.stopsAt) - Date.now()) + 250);
|
|
15911
16381
|
maxTimer.unref();
|
|
15912
16382
|
await persistState();
|
|
15913
16383
|
return json2(response, 201, session.status());
|
|
@@ -15942,11 +16412,18 @@ async function finalizeEntry(entry, reason) {
|
|
|
15942
16412
|
await persistState();
|
|
15943
16413
|
}
|
|
15944
16414
|
function serializeEntry(entry) {
|
|
15945
|
-
return {
|
|
16415
|
+
return {
|
|
16416
|
+
...entry.base,
|
|
16417
|
+
...entry.session?.status(),
|
|
16418
|
+
status: entry.state,
|
|
16419
|
+
...entry.state !== "recording" ? { remainingSeconds: 0, ...entry.result ? { elapsedSeconds: entry.result.durationSeconds } : {} } : {},
|
|
16420
|
+
result: entry.result,
|
|
16421
|
+
error: entry.error
|
|
16422
|
+
};
|
|
15946
16423
|
}
|
|
15947
16424
|
async function restoreRecoverableSessions() {
|
|
15948
16425
|
try {
|
|
15949
|
-
const value = JSON.parse(await
|
|
16426
|
+
const value = JSON.parse(await readFile7(daemonStatePath(), "utf8"));
|
|
15950
16427
|
for (const [id, entry] of Object.entries(value)) {
|
|
15951
16428
|
if (entry.status !== "ready" && entry.status !== "failed") continue;
|
|
15952
16429
|
sessions.set(id, { base: { recordingId: entry.recordingId, cdpUrl: entry.cdpUrl, startedAt: entry.startedAt }, state: entry.status, result: entry.result, error: entry.error });
|
|
@@ -15957,9 +16434,9 @@ async function restoreRecoverableSessions() {
|
|
|
15957
16434
|
async function persistState() {
|
|
15958
16435
|
const state = Object.fromEntries([...sessions.entries()].map(([id, entry]) => [id, serializeEntry(entry)]));
|
|
15959
16436
|
const temporary = daemonStatePath() + "." + crypto.randomUUID() + ".tmp";
|
|
15960
|
-
await
|
|
16437
|
+
await writeFile6(temporary, `${JSON.stringify(state, null, 2)}
|
|
15961
16438
|
`, { mode: 384 });
|
|
15962
|
-
await
|
|
16439
|
+
await rename2(temporary, daemonStatePath());
|
|
15963
16440
|
}
|
|
15964
16441
|
async function readJson(request3) {
|
|
15965
16442
|
const chunks = [];
|
|
@@ -15980,7 +16457,7 @@ async function purgeExpiredCaptures() {
|
|
|
15980
16457
|
const removed = await purgeExpiredRemoteCaptures(recordingTempRoot());
|
|
15981
16458
|
for (const id of removed) sessions.delete(id);
|
|
15982
16459
|
let changed = removed.length > 0;
|
|
15983
|
-
for (const [id, entry] of sessions) if (entry.result?.frameDirectory && !await
|
|
16460
|
+
for (const [id, entry] of sessions) if (entry.result?.frameDirectory && !await stat4(entry.result.frameDirectory).catch(() => null)) {
|
|
15984
16461
|
sessions.delete(id);
|
|
15985
16462
|
changed = true;
|
|
15986
16463
|
}
|
|
@@ -16092,7 +16569,7 @@ async function withRunFeedback(session, start) {
|
|
|
16092
16569
|
}
|
|
16093
16570
|
|
|
16094
16571
|
// src/doctor.ts
|
|
16095
|
-
import { spawn as
|
|
16572
|
+
import { spawn as spawn7 } from "child_process";
|
|
16096
16573
|
|
|
16097
16574
|
// src/version.ts
|
|
16098
16575
|
import { createRequire } from "module";
|
|
@@ -16153,7 +16630,7 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
16153
16630
|
credentialError = error51;
|
|
16154
16631
|
}
|
|
16155
16632
|
if (credentialError) {
|
|
16156
|
-
checks.push({ name: "authentication", status: "fail", message: errorMessage(credentialError), recovery: "Unlock the OS credential store, or use RILL_TOKEN. Windows
|
|
16633
|
+
checks.push({ name: "authentication", status: "fail", message: errorMessage(credentialError), recovery: "Unlock the OS credential store, or use RILL_TOKEN. On Windows, use the same Windows account that saved the credential." });
|
|
16157
16634
|
} else if (!token) {
|
|
16158
16635
|
checks.push({ name: "authentication", status: health.anonymousAccess ? "warn" : "fail", message: health.anonymousAccess ? "The first recording will create an anonymous identity in your OS credential store. Anonymous recordings expire after seven days." : "No Rill agent credential is available.", recovery: `Run \`rill --api-url ${apiUrl} login\` or set RILL_TOKEN.` });
|
|
16159
16636
|
} else {
|
|
@@ -16168,7 +16645,7 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
16168
16645
|
}
|
|
16169
16646
|
checks.push(await executableCheck("chrome", dependencies.chromePath, ["--version"], dependencies.commandVersion, "Install Google Chrome or Chromium, or set RILL_CHROME_PATH to its executable."));
|
|
16170
16647
|
if (dependencies.encoding === "local") {
|
|
16171
|
-
checks.push(await executableCheck("ffmpeg", dependencies.ffmpegPath, ["-version"], dependencies.commandVersion,
|
|
16648
|
+
checks.push(await executableCheck("ffmpeg", dependencies.ffmpegPath, ["-version"], dependencies.commandVersion, dependencies.platform === "darwin" ? "Install ffmpeg with `brew install ffmpeg`, or set RILL_FFMPEG." : "Install ffmpeg using the system package manager, or set RILL_FFMPEG."));
|
|
16172
16649
|
} else {
|
|
16173
16650
|
checks.push({ name: "ffmpeg", status: "skip", message: "Remote encoding does not require local FFmpeg." });
|
|
16174
16651
|
try {
|
|
@@ -16176,7 +16653,7 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
16176
16653
|
if (!(token ? (await dependencies.encodingCapabilities(apiUrl, token)).enabled : health?.anonymousAccess && health.anonymousRemoteEncoding)) throw new Error("Remote encoding is not enabled for this workspace.");
|
|
16177
16654
|
checks.push({ name: "encoding", status: "pass", message: token ? "Remote encoding is enabled." : "Remote encoding is available for the first anonymous recording." });
|
|
16178
16655
|
} catch (error51) {
|
|
16179
|
-
checks.push({ name: "encoding", status: "fail", message: errorMessage(error51), recovery: "
|
|
16656
|
+
checks.push({ name: "encoding", status: "fail", message: errorMessage(error51), recovery: "Run doctor --encoding local to check local FFmpeg, then record start --encoding local, or ask the workspace owner to enable remote encoding." });
|
|
16180
16657
|
}
|
|
16181
16658
|
}
|
|
16182
16659
|
return { schemaVersion: 1, status: checks.some((check2) => check2.status === "fail") ? "error" : "ok", apiUrl, checks, ...runFeedbackFields(feedbackEnabled) };
|
|
@@ -16191,7 +16668,7 @@ async function executableCheck(name, path, args, run2, recovery) {
|
|
|
16191
16668
|
}
|
|
16192
16669
|
function commandVersion(command, args) {
|
|
16193
16670
|
return new Promise((resolve, reject) => {
|
|
16194
|
-
const child =
|
|
16671
|
+
const child = spawn7(command, args, { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
16195
16672
|
const stdout = [];
|
|
16196
16673
|
const stderr = [];
|
|
16197
16674
|
const timeout = setTimeout(() => {
|
|
@@ -16252,16 +16729,24 @@ function formatCloudRun(run2) {
|
|
|
16252
16729
|
}
|
|
16253
16730
|
|
|
16254
16731
|
// src/recording-lifecycle.ts
|
|
16255
|
-
async function startRecordingWithCompensation(remote, dependencies) {
|
|
16732
|
+
async function startRecordingWithCompensation(remote, dependencies, apiUrl = "https://userill.dev") {
|
|
16256
16733
|
try {
|
|
16257
16734
|
return await dependencies.startLocal();
|
|
16258
16735
|
} catch (startupError) {
|
|
16259
16736
|
try {
|
|
16260
16737
|
await dependencies.cancelRemote(remote.recordingId, "local_start_failed");
|
|
16261
16738
|
} catch (cleanupError) {
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16739
|
+
const nextCommand = cliCommand(apiUrl, "record", "cancel", remote.recordingId);
|
|
16740
|
+
throw Object.assign(
|
|
16741
|
+
new AggregateError(
|
|
16742
|
+
[startupError, cleanupError],
|
|
16743
|
+
"browser_unavailable: Local startup failed and its remote reservation could not be cancelled."
|
|
16744
|
+
),
|
|
16745
|
+
{
|
|
16746
|
+
code: "browser_unavailable",
|
|
16747
|
+
recovery: `Run ${nextCommand} to release this failed startup reservation.`,
|
|
16748
|
+
details: { recordingId: remote.recordingId, nextCommand }
|
|
16749
|
+
}
|
|
16265
16750
|
);
|
|
16266
16751
|
}
|
|
16267
16752
|
throw startupError;
|
|
@@ -16279,7 +16764,16 @@ async function remoteAwareRecordingStatus(recordingId, dependencies, apiUrl = "h
|
|
|
16279
16764
|
try {
|
|
16280
16765
|
const local = await dependencies.localStatus(recordingId);
|
|
16281
16766
|
const status = local.status === "ready" ? "captured" : local.status;
|
|
16282
|
-
return {
|
|
16767
|
+
return {
|
|
16768
|
+
...local,
|
|
16769
|
+
status,
|
|
16770
|
+
source: "local",
|
|
16771
|
+
...recordingNextStep(apiUrl, recordingId, String(status)),
|
|
16772
|
+
...status === "failed" ? {
|
|
16773
|
+
nextCommand: cliCommand(apiUrl, "record", "cancel", recordingId),
|
|
16774
|
+
message: "Local capture failed; the remote reservation may still hold a slot. Preserve any local draft you need, then cancel this failed attempt to release its slot. Cancellation does not delete local drafts."
|
|
16775
|
+
} : {}
|
|
16776
|
+
};
|
|
16283
16777
|
} catch {
|
|
16284
16778
|
return remoteView;
|
|
16285
16779
|
}
|
|
@@ -16295,10 +16789,10 @@ function controlPlaneUrl(environmentUrl) {
|
|
|
16295
16789
|
async function removeLocalCapture(id) {
|
|
16296
16790
|
await ensureDaemon(process.argv[1]);
|
|
16297
16791
|
const local = await recordingStatus(id).catch(() => null);
|
|
16298
|
-
if (local?.result?.frameDirectory) await
|
|
16792
|
+
if (local?.result?.frameDirectory) await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
|
|
16299
16793
|
}
|
|
16300
16794
|
function watchEncoding(apiUrl, id) {
|
|
16301
|
-
const child =
|
|
16795
|
+
const child = spawn8(process.execPath, [process.argv[1], "--api-url", apiUrl, "encoding-watch", id], { detached: true, stdio: "ignore", env: { ...process.env }, windowsHide: true });
|
|
16302
16796
|
child.on("error", () => void 0);
|
|
16303
16797
|
child.unref();
|
|
16304
16798
|
}
|
|
@@ -16320,7 +16814,7 @@ async function authorize(quiet = false) {
|
|
|
16320
16814
|
process.stderr.write(`Open ${authorization.verificationUri} and enter ${authorization.userCode}
|
|
16321
16815
|
`);
|
|
16322
16816
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
|
|
16323
|
-
const child =
|
|
16817
|
+
const child = spawn8(opener, [authorization.verificationUri], { stdio: "ignore", detached: true });
|
|
16324
16818
|
child.on("error", () => void 0);
|
|
16325
16819
|
child.unref();
|
|
16326
16820
|
const deadline = Date.now() + authorization.expiresIn * 1e3;
|
|
@@ -16406,7 +16900,7 @@ record2.command("start").option("--encoding <mode>", "remote or local", "remote"
|
|
|
16406
16900
|
return startRecording({ encoding: commandOptions.encoding, recordingId: remote.recordingId, url: commandOptions.url, cdpUrl: commandOptions.cdpUrl, headed: Boolean(commandOptions.headed), width, height, maxDurationSeconds: Math.min(Number(commandOptions.maxDuration), remote.maxDurationSeconds ?? 600), captureNetworkBodies: remote.bodyCaptureEnabled, cursorAppearance: remote.cursorAppearance });
|
|
16407
16901
|
},
|
|
16408
16902
|
cancelRemote: (recordingId, reason) => client.cancelRecording(recordingId, reason)
|
|
16409
|
-
});
|
|
16903
|
+
}, options.apiUrl);
|
|
16410
16904
|
await client.markRecordingStarted(remote.recordingId, {
|
|
16411
16905
|
cliVersion: CLI_VERSION,
|
|
16412
16906
|
os: process.platform,
|
|
@@ -16414,7 +16908,7 @@ record2.command("start").option("--encoding <mode>", "remote or local", "remote"
|
|
|
16414
16908
|
headed: Boolean(commandOptions.headed),
|
|
16415
16909
|
attached: Boolean(commandOptions.cdpUrl)
|
|
16416
16910
|
}).catch(() => void 0);
|
|
16417
|
-
return { schemaVersion: 1, recordingId: remote.recordingId, status: local.status, cdpUrl: local.cdpUrl, browserAttachment: browserAttachment(local.cdpUrl), bodyCaptureEnabled: remote.bodyCaptureEnabled, anonymous: remote.anonymous ?? false, expiresAt: remote.expiresAt ?? null, maxDurationSeconds: Math.min(Number(commandOptions.maxDuration), remote.maxDurationSeconds ?? 600) };
|
|
16911
|
+
return { schemaVersion: 1, recordingId: remote.recordingId, status: local.status, cdpUrl: local.cdpUrl, browserAttachment: browserAttachment(local.cdpUrl), startedAt: local.startedAt, stopsAt: local.stopsAt, elapsedSeconds: local.elapsedSeconds, remainingSeconds: local.remainingSeconds, bodyCaptureEnabled: remote.bodyCaptureEnabled, anonymous: remote.anonymous ?? false, expiresAt: remote.expiresAt ?? null, maxDurationSeconds: Math.min(Number(commandOptions.maxDuration), remote.maxDurationSeconds ?? 600) };
|
|
16418
16912
|
});
|
|
16419
16913
|
emit(result, options.pretty);
|
|
16420
16914
|
});
|
|
@@ -16435,11 +16929,11 @@ record2.command("stop").argument("<recording-id>").option("--no-wait").option("-
|
|
|
16435
16929
|
const accepted = await submitFrameCapture(client, recordingId, local.result.frameDirectory);
|
|
16436
16930
|
const result2 = commandOptions.wait && record2.opts().wait !== false ? await waitForEncoding(client, recordingId) : accepted;
|
|
16437
16931
|
emit({ schemaVersion: 1, ...result2, ...recordingNextStep(options.apiUrl, recordingId, result2.status, { resumableFrames: true }), ...recordingFeedbackFields(recordingId, stopped?.feedbackRequested ?? false) }, options.pretty);
|
|
16438
|
-
if (result2.status === "ready") await
|
|
16932
|
+
if (result2.status === "ready") await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
|
|
16439
16933
|
else watchEncoding(options.apiUrl, recordingId);
|
|
16440
16934
|
return;
|
|
16441
16935
|
}
|
|
16442
|
-
const video = await
|
|
16936
|
+
const video = await stat5(local.result.videoPath);
|
|
16443
16937
|
const provisioned = await client.provisionVideo(recordingId, { sizeBytes: video.size, filename: basename(local.result.videoPath), uploadKind: "recorded_browser" });
|
|
16444
16938
|
await Promise.all([client.uploadVideo(provisioned.uploadUrl, local.result.videoPath), client.uploadDiagnostics(recordingId, local.result.diagnosticsPath, local.result.diagnostics)]);
|
|
16445
16939
|
const remote = commandOptions.wait === false || record2.opts().wait === false ? null : await client.waitUntilPlayable(recordingId);
|
|
@@ -16457,7 +16951,7 @@ record2.command("stop").argument("<recording-id>").option("--no-wait").option("-
|
|
|
16457
16951
|
...recordingFeedbackFields(recordingId, stopped?.feedbackRequested ?? false)
|
|
16458
16952
|
};
|
|
16459
16953
|
emit(result, options.pretty);
|
|
16460
|
-
if (remote) await
|
|
16954
|
+
if (remote) await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
|
|
16461
16955
|
});
|
|
16462
16956
|
record2.command("retry").argument("<recording-id>").option("--no-wait").action(async (recordingId, commandOptions) => {
|
|
16463
16957
|
const options = program.opts();
|
|
@@ -16489,7 +16983,7 @@ program.command("encoding-watch", { hidden: true }).argument("<recording-id>").a
|
|
|
16489
16983
|
if (result.status === "ready") {
|
|
16490
16984
|
await ensureDaemon(process.argv[1]);
|
|
16491
16985
|
const local = await recordingStatus(id).catch(() => null);
|
|
16492
|
-
if (local?.result?.frameDirectory) await
|
|
16986
|
+
if (local?.result?.frameDirectory) await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
|
|
16493
16987
|
}
|
|
16494
16988
|
});
|
|
16495
16989
|
var feedback = program.command("feedback").description("Submit a short Rill product review for a recorded run.");
|
|
@@ -16544,7 +17038,7 @@ program.command("upload").argument("<video-file>").option("--title <title>").opt
|
|
|
16544
17038
|
const options = program.opts();
|
|
16545
17039
|
await ensureAnonymousCredential(options.apiUrl);
|
|
16546
17040
|
const client = new ControlPlaneClient(options.apiUrl, () => loadCredential(options.apiUrl));
|
|
16547
|
-
const file2 = await
|
|
17041
|
+
const file2 = await stat5(videoFile);
|
|
16548
17042
|
const recording = await client.createRecording({ title: commandOptions.title ?? basename(videoFile), sourceUrl: null });
|
|
16549
17043
|
const upload = await client.provisionVideo(recording.recordingId, { sizeBytes: file2.size, filename: basename(videoFile), uploadKind: "existing_video" });
|
|
16550
17044
|
await client.uploadVideo(upload.uploadUrl, videoFile);
|
|
@@ -16573,20 +17067,20 @@ comments.command("edit").argument("<share-url>").requiredOption("--comment <id>"
|
|
|
16573
17067
|
var drafts = program.command("drafts");
|
|
16574
17068
|
drafts.command("list").action(async () => {
|
|
16575
17069
|
const options = program.opts();
|
|
16576
|
-
const root = process.env.RILL_TEMP ??
|
|
17070
|
+
const root = process.env.RILL_TEMP ?? join11(tmpdir3(), "rill-recordings");
|
|
16577
17071
|
const names = await readdir2(root).catch(() => []);
|
|
16578
17072
|
emit({ schemaVersion: 1, drafts: names }, options.pretty);
|
|
16579
17073
|
});
|
|
16580
17074
|
drafts.command("purge").action(async () => {
|
|
16581
17075
|
const options = program.opts();
|
|
16582
|
-
const root = process.env.RILL_TEMP ??
|
|
17076
|
+
const root = process.env.RILL_TEMP ?? join11(tmpdir3(), "rill-recordings");
|
|
16583
17077
|
const names = await readdir2(root).catch(() => []);
|
|
16584
17078
|
let purged = 0;
|
|
16585
17079
|
for (const name of names) {
|
|
16586
|
-
const path =
|
|
16587
|
-
const metadata = await
|
|
17080
|
+
const path = join11(root, name);
|
|
17081
|
+
const metadata = await stat5(path);
|
|
16588
17082
|
if (Date.now() - metadata.mtimeMs > 24 * 60 * 60 * 1e3) {
|
|
16589
|
-
await
|
|
17083
|
+
await rm7(path, { recursive: true, force: true });
|
|
16590
17084
|
purged += 1;
|
|
16591
17085
|
}
|
|
16592
17086
|
}
|