pro-visu 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +176 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { cac } from "cac";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
|
-
var TOOL_VERSION = true ? "0.
|
|
7
|
+
var TOOL_VERSION = true ? "0.4.0" : "0.0.0-dev";
|
|
8
8
|
|
|
9
9
|
// src/cli/update-check.ts
|
|
10
10
|
import updateNotifier from "update-notifier";
|
|
@@ -163,17 +163,88 @@ async function ensureChromium(opts) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// src/media/ensure-ffmpeg.ts
|
|
166
|
-
import path5 from "path";
|
|
167
166
|
import { existsSync as existsSync2 } from "fs";
|
|
168
|
-
import { rm as rm3 } from "fs/promises";
|
|
169
167
|
import { spawn as spawn3 } from "child_process";
|
|
170
|
-
import { createRequire as createRequire2 } from "module";
|
|
171
168
|
|
|
172
169
|
// src/media/ffmpeg.ts
|
|
173
|
-
import
|
|
170
|
+
import path5 from "path";
|
|
174
171
|
import { spawn as spawn2 } from "child_process";
|
|
175
|
-
import { rm as
|
|
176
|
-
|
|
172
|
+
import { rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
173
|
+
|
|
174
|
+
// src/media/ffmpeg-binary.ts
|
|
175
|
+
import os from "os";
|
|
176
|
+
import path4 from "path";
|
|
177
|
+
import https from "https";
|
|
178
|
+
import { createWriteStream } from "fs";
|
|
179
|
+
import { mkdir as mkdir2, rename, rm as rm2, chmod } from "fs/promises";
|
|
180
|
+
import { createGunzip } from "zlib";
|
|
181
|
+
import { pipeline } from "stream/promises";
|
|
182
|
+
var FFMPEG_RELEASE = process.env.FFMPEG_BINARY_RELEASE || "b6.1.1";
|
|
183
|
+
var BINARIES_URL = process.env.FFMPEG_BINARIES_URL || "https://github.com/eugeneware/ffmpeg-static/releases/download";
|
|
184
|
+
var SUPPORTED = {
|
|
185
|
+
darwin: ["x64", "arm64"],
|
|
186
|
+
linux: ["x64", "ia32", "arm64", "arm"],
|
|
187
|
+
win32: ["x64", "ia32"],
|
|
188
|
+
freebsd: ["x64"]
|
|
189
|
+
};
|
|
190
|
+
function ffmpegIsSupported() {
|
|
191
|
+
return (SUPPORTED[process.platform] ?? []).includes(process.arch);
|
|
192
|
+
}
|
|
193
|
+
function ffmpegCacheDir() {
|
|
194
|
+
const base = process.env.PROVISU_FFMPEG_DIR || path4.join(os.homedir(), ".cache", "pro-visu", "ffmpeg");
|
|
195
|
+
return path4.join(base, FFMPEG_RELEASE);
|
|
196
|
+
}
|
|
197
|
+
function ffmpegCachedBinary() {
|
|
198
|
+
return path4.join(ffmpegCacheDir(), process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg");
|
|
199
|
+
}
|
|
200
|
+
function ffmpegBinaryPath() {
|
|
201
|
+
return process.env.FFMPEG_BIN || ffmpegCachedBinary();
|
|
202
|
+
}
|
|
203
|
+
function ffmpegDownloadUrl() {
|
|
204
|
+
if (!ffmpegIsSupported()) return null;
|
|
205
|
+
return `${BINARIES_URL}/${FFMPEG_RELEASE}/ffmpeg-${process.platform}-${process.arch}.gz`;
|
|
206
|
+
}
|
|
207
|
+
function getFollowing(url, redirects = 5) {
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const req = https.get(url, { headers: { "User-Agent": "pro-visu" } }, (res) => {
|
|
210
|
+
const status = res.statusCode ?? 0;
|
|
211
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
212
|
+
res.resume();
|
|
213
|
+
if (redirects <= 0) return reject(new Error("Too many redirects fetching ffmpeg."));
|
|
214
|
+
resolve(getFollowing(new URL(res.headers.location, url).toString(), redirects - 1));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (status !== 200) {
|
|
218
|
+
res.resume();
|
|
219
|
+
reject(new Error(`ffmpeg download failed: HTTP ${status} for ${url}`));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
resolve(res);
|
|
223
|
+
});
|
|
224
|
+
req.on("error", reject);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
async function downloadFfmpeg() {
|
|
228
|
+
const url = ffmpegDownloadUrl();
|
|
229
|
+
if (!url) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`No prebuilt ffmpeg for ${process.platform}/${process.arch}. Set FFMPEG_BIN to a local ffmpeg binary.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const dest = ffmpegCachedBinary();
|
|
235
|
+
const tmp = `${dest}.download`;
|
|
236
|
+
await mkdir2(path4.dirname(dest), { recursive: true });
|
|
237
|
+
await rm2(tmp, { force: true });
|
|
238
|
+
const res = await getFollowing(url);
|
|
239
|
+
await pipeline(res, createGunzip(), createWriteStream(tmp));
|
|
240
|
+
await chmod(tmp, 493).catch(() => {
|
|
241
|
+
});
|
|
242
|
+
await rm2(dest, { force: true });
|
|
243
|
+
await rename(tmp, dest);
|
|
244
|
+
return dest;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/media/ffmpeg.ts
|
|
177
248
|
var SCALE_COLOR = "out_color_matrix=bt709:out_range=tv";
|
|
178
249
|
var COLOR_TAGS = [
|
|
179
250
|
"-colorspace",
|
|
@@ -186,11 +257,7 @@ var COLOR_TAGS = [
|
|
|
186
257
|
"tv"
|
|
187
258
|
];
|
|
188
259
|
function ffmpegPath() {
|
|
189
|
-
|
|
190
|
-
if (!p) {
|
|
191
|
-
throw new Error("ffmpeg-static did not provide a binary for this platform.");
|
|
192
|
-
}
|
|
193
|
-
return p;
|
|
260
|
+
return ffmpegBinaryPath();
|
|
194
261
|
}
|
|
195
262
|
function buildTranscodeArgs(args) {
|
|
196
263
|
const seek = args.startOffsetSeconds && args.startOffsetSeconds > 0 ? ["-ss", args.startOffsetSeconds.toFixed(3)] : [];
|
|
@@ -332,7 +399,7 @@ function buildConcatArgs(listFile, outPath) {
|
|
|
332
399
|
];
|
|
333
400
|
}
|
|
334
401
|
async function concatMp4(segments, outPath, logger, signal) {
|
|
335
|
-
await ensureDir(
|
|
402
|
+
await ensureDir(path5.dirname(outPath));
|
|
336
403
|
const listFile = `${outPath}.concat.txt`;
|
|
337
404
|
const list = segments.map((s) => `file '${s.replace(/\\/g, "/")}'`).join("\n");
|
|
338
405
|
await writeFile2(listFile, `${list}
|
|
@@ -340,7 +407,7 @@ async function concatMp4(segments, outPath, logger, signal) {
|
|
|
340
407
|
try {
|
|
341
408
|
await runFfmpeg(buildConcatArgs(listFile, outPath), logger, signal);
|
|
342
409
|
} finally {
|
|
343
|
-
await
|
|
410
|
+
await rm3(listFile, { force: true });
|
|
344
411
|
}
|
|
345
412
|
}
|
|
346
413
|
async function runFfmpeg(argv, logger, signal) {
|
|
@@ -362,7 +429,7 @@ ${stderr.slice(-2e3)}`));
|
|
|
362
429
|
});
|
|
363
430
|
}
|
|
364
431
|
async function transcodeToMp4(args) {
|
|
365
|
-
await ensureDir(
|
|
432
|
+
await ensureDir(path5.dirname(args.outputPath));
|
|
366
433
|
await runFfmpeg(buildTranscodeArgs(args), args.logger, args.signal);
|
|
367
434
|
}
|
|
368
435
|
function aspectTarget(aspect) {
|
|
@@ -467,7 +534,6 @@ function buildStillSegmentArgs(a) {
|
|
|
467
534
|
}
|
|
468
535
|
|
|
469
536
|
// src/media/ensure-ffmpeg.ts
|
|
470
|
-
var require3 = createRequire2(import.meta.url);
|
|
471
537
|
async function ffmpegWorks() {
|
|
472
538
|
let bin;
|
|
473
539
|
try {
|
|
@@ -488,42 +554,26 @@ async function ffmpegWorks() {
|
|
|
488
554
|
child.on("close", (code) => resolve(code === 0));
|
|
489
555
|
});
|
|
490
556
|
}
|
|
491
|
-
function resolveInstallScript() {
|
|
492
|
-
try {
|
|
493
|
-
return require3.resolve("ffmpeg-static/install.js");
|
|
494
|
-
} catch {
|
|
495
|
-
try {
|
|
496
|
-
const pkg = require3.resolve("ffmpeg-static/package.json");
|
|
497
|
-
return path5.join(path5.dirname(pkg), "install.js");
|
|
498
|
-
} catch {
|
|
499
|
-
return null;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
557
|
async function ensureFfmpeg(opts) {
|
|
504
558
|
if (await ffmpegWorks()) return true;
|
|
505
559
|
if (opts.checkOnly) return false;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
560
|
+
if (process.env.FFMPEG_BIN) {
|
|
561
|
+
opts.logger.error(`FFMPEG_BIN is set to "${process.env.FFMPEG_BIN}" but that binary won't run.`);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
if (!ffmpegIsSupported()) {
|
|
565
|
+
opts.logger.error(
|
|
566
|
+
`No prebuilt ffmpeg for ${process.platform}/${process.arch}. Set FFMPEG_BIN to a local ffmpeg binary.`
|
|
567
|
+
);
|
|
509
568
|
return false;
|
|
510
569
|
}
|
|
511
570
|
opts.logger.info("Fetching ffmpeg (one-time, ~80 MB)\u2026");
|
|
512
571
|
try {
|
|
513
|
-
await
|
|
514
|
-
} catch {
|
|
572
|
+
await downloadFfmpeg();
|
|
573
|
+
} catch (err) {
|
|
574
|
+
opts.logger.error(`ffmpeg download failed: ${err.message}`);
|
|
575
|
+
return false;
|
|
515
576
|
}
|
|
516
|
-
await new Promise((resolve, reject) => {
|
|
517
|
-
const child = spawn3(process.execPath, [installScript], {
|
|
518
|
-
cwd: path5.dirname(installScript),
|
|
519
|
-
stdio: "inherit"
|
|
520
|
-
});
|
|
521
|
-
child.on("error", reject);
|
|
522
|
-
child.on(
|
|
523
|
-
"close",
|
|
524
|
-
(code) => code === 0 ? resolve() : reject(new Error(`ffmpeg download failed (exit code ${code}).`))
|
|
525
|
-
);
|
|
526
|
-
});
|
|
527
577
|
if (!await ffmpegWorks()) {
|
|
528
578
|
opts.logger.error("ffmpeg was downloaded but still won't run on this platform.");
|
|
529
579
|
return false;
|
|
@@ -578,6 +628,16 @@ var serverSettingsSchema = z.object({
|
|
|
578
628
|
/** If a server is already reachable at the URL, use it as-is (don't start or stop one). */
|
|
579
629
|
reuseExisting: z.boolean().default(true).describe("If a server is already reachable at the URL, use it as-is (don't start or stop one). Default true.")
|
|
580
630
|
}).strict();
|
|
631
|
+
var captureSettingsSchema = z.object({
|
|
632
|
+
/** Query params appended to every URL-based asset (e.g. `{ capture: "1" }` → `?capture=1`). */
|
|
633
|
+
query: z.record(z.string(), z.string()).optional().describe('Query params appended to every URL-based asset, e.g. { capture: "1" }.'),
|
|
634
|
+
/** Cookies set on every capture context before navigation, scoped to the asset's origin. */
|
|
635
|
+
cookies: z.array(z.object({ name: z.string().min(1), value: z.string() }).strict()).optional().describe("Cookies set on every capture context before navigation (scoped to the asset's origin)."),
|
|
636
|
+
/** localStorage entries seeded before the page's own scripts run. */
|
|
637
|
+
localStorage: z.record(z.string(), z.string()).optional().describe("localStorage entries seeded (per origin) before the page's own scripts run."),
|
|
638
|
+
/** JS source run in every page before its own scripts — e.g. set a global capture flag. */
|
|
639
|
+
initScript: z.string().optional().describe("JS run in every page before its own scripts (e.g. `window.__PV_CAPTURE__ = true`).")
|
|
640
|
+
}).strict();
|
|
581
641
|
var settingsSchema = z.object({
|
|
582
642
|
/** Output directory, relative to the repo root. */
|
|
583
643
|
outDir: z.string().min(1).default("pro-visu").describe('Output directory for generated assets, relative to the repo root (default "pro-visu").'),
|
|
@@ -599,6 +659,8 @@ var settingsSchema = z.object({
|
|
|
599
659
|
defaults: z.record(z.string(), z.record(z.string(), z.unknown())).default({}).describe("Per-generator option defaults, keyed by generator id, merged underneath each asset's own options."),
|
|
600
660
|
/** Optional managed dev/prod server lifecycle (build → start → wait → … → stop). */
|
|
601
661
|
server: serverSettingsSchema.optional().describe("Build \u2192 start \u2192 wait \u2192 capture \u2192 stop a server automatically."),
|
|
662
|
+
/** "Capture mode" toggles (query/cookies/localStorage/init script) applied to every URL capture. */
|
|
663
|
+
capture: captureSettingsSchema.optional().describe("Capture-mode toggles applied to every URL-based asset (e.g. disable animations / hide the cookie banner)."),
|
|
602
664
|
/** Render quality. "draft" lowers fps/scale and speeds the encoder for fast iteration. */
|
|
603
665
|
quality: z.enum(["draft", "final"]).default("final").describe('Render quality; "draft" lowers fps/scale and speeds the encoder for fast iteration (default "final").'),
|
|
604
666
|
/** Skip assets whose inputs+options+tool fingerprint is unchanged (opt-in; can be stale). */
|
|
@@ -943,6 +1005,38 @@ var scrollReelOptionsSchema = z2.object({
|
|
|
943
1005
|
import path6 from "path";
|
|
944
1006
|
import { mkdtemp } from "fs/promises";
|
|
945
1007
|
|
|
1008
|
+
// src/pipeline/capture.ts
|
|
1009
|
+
function withCaptureQuery(url, capture) {
|
|
1010
|
+
if (!url || !capture?.query) return url;
|
|
1011
|
+
try {
|
|
1012
|
+
const u = new URL(url);
|
|
1013
|
+
for (const [k, v] of Object.entries(capture.query)) u.searchParams.set(k, v);
|
|
1014
|
+
return u.toString();
|
|
1015
|
+
} catch {
|
|
1016
|
+
return url;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async function applyCapture(context, capture, url) {
|
|
1020
|
+
if (!capture) return;
|
|
1021
|
+
if (capture.cookies?.length && url) {
|
|
1022
|
+
try {
|
|
1023
|
+
const origin = new URL(url).origin;
|
|
1024
|
+
await context.addCookies(
|
|
1025
|
+
capture.cookies.map((c) => ({ name: c.name, value: c.value, url: origin }))
|
|
1026
|
+
);
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const scripts = [];
|
|
1031
|
+
if (capture.localStorage) {
|
|
1032
|
+
scripts.push(
|
|
1033
|
+
`try{var e=${JSON.stringify(capture.localStorage)};for(var k in e)localStorage.setItem(k,e[k]);}catch(_){}`
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
if (capture.initScript) scripts.push(capture.initScript);
|
|
1037
|
+
if (scripts.length) await context.addInitScript(scripts.join("\n"));
|
|
1038
|
+
}
|
|
1039
|
+
|
|
946
1040
|
// src/generators/scroll-reel/scroll.ts
|
|
947
1041
|
var EASINGS = {
|
|
948
1042
|
linear: (t) => t,
|
|
@@ -957,16 +1051,21 @@ async function prepareScroll(args) {
|
|
|
957
1051
|
const target = findScrollTarget(document, getComputedStyle);
|
|
958
1052
|
forceInstant(target);
|
|
959
1053
|
const sleep2 = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms));
|
|
1054
|
+
const withCap = (p, ms) => Promise.race([p ?? Promise.resolve(), sleep2(ms)]);
|
|
960
1055
|
scrollTargetTo(target, maxScrollOf(target));
|
|
961
1056
|
await sleep2(Math.max(0, args.settleMs));
|
|
962
1057
|
try {
|
|
963
|
-
await (document.fonts?.ready ??
|
|
1058
|
+
await withCap(document.fonts?.ready ?? null, 5e3);
|
|
964
1059
|
} catch {
|
|
965
1060
|
}
|
|
966
1061
|
try {
|
|
967
1062
|
const imgs = Array.from(document.images ?? []);
|
|
968
|
-
await
|
|
969
|
-
|
|
1063
|
+
await withCap(
|
|
1064
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1065
|
+
Promise.all(imgs.map((im) => im.decode ? im.decode().catch(() => {
|
|
1066
|
+
}) : null)),
|
|
1067
|
+
4e3
|
|
1068
|
+
);
|
|
970
1069
|
} catch {
|
|
971
1070
|
}
|
|
972
1071
|
scrollTargetTo(target, 0);
|
|
@@ -1410,6 +1509,7 @@ async function captureScrollWebm(args) {
|
|
|
1410
1509
|
size: { width: options.width, height: options.height }
|
|
1411
1510
|
}
|
|
1412
1511
|
});
|
|
1512
|
+
await applyCapture(context, args.capture, url);
|
|
1413
1513
|
const page = await context.newPage();
|
|
1414
1514
|
const video = page.video();
|
|
1415
1515
|
const recStart = Date.now();
|
|
@@ -1444,10 +1544,10 @@ async function captureScrollWebm(args) {
|
|
|
1444
1544
|
}
|
|
1445
1545
|
|
|
1446
1546
|
// src/media/frame-capture.ts
|
|
1447
|
-
import
|
|
1547
|
+
import os2 from "os";
|
|
1448
1548
|
import path7 from "path";
|
|
1449
1549
|
function autoWorkers() {
|
|
1450
|
-
const cores =
|
|
1550
|
+
const cores = os2.cpus()?.length ?? 2;
|
|
1451
1551
|
return Math.max(1, Math.min(6, Math.floor(cores / 2)));
|
|
1452
1552
|
}
|
|
1453
1553
|
function planFrames(totalFrames, workers) {
|
|
@@ -2019,6 +2119,7 @@ async function captureScrollFrames(a) {
|
|
|
2019
2119
|
signal: a.signal,
|
|
2020
2120
|
prepare: async (page, { logger }) => {
|
|
2021
2121
|
if (a.colorScheme) await page.emulateMedia({ colorScheme: a.colorScheme });
|
|
2122
|
+
await applyCapture(page.context(), a.capture, a.url);
|
|
2022
2123
|
await installNetworkHygiene(page, options);
|
|
2023
2124
|
await installPreNav(page, options);
|
|
2024
2125
|
logger.debug(`navigating to ${a.url} (waitUntil=${options.waitUntil})`);
|
|
@@ -2483,6 +2584,7 @@ async function captureInteractionWebm(args) {
|
|
|
2483
2584
|
deviceScaleFactor: options.deviceScaleFactor,
|
|
2484
2585
|
recordVideo: { dir: recordDir, size: { width: options.width, height: options.height } }
|
|
2485
2586
|
});
|
|
2587
|
+
await applyCapture(context, args.capture, url);
|
|
2486
2588
|
const page = await context.newPage();
|
|
2487
2589
|
const video = page.video();
|
|
2488
2590
|
const actions = options.actions ?? [];
|
|
@@ -2545,6 +2647,7 @@ async function captureFocusWebm(args) {
|
|
|
2545
2647
|
deviceScaleFactor: options.deviceScaleFactor,
|
|
2546
2648
|
recordVideo: { dir: recordDir, size: { width: options.width, height: options.height } }
|
|
2547
2649
|
});
|
|
2650
|
+
await applyCapture(context, args.capture, url);
|
|
2548
2651
|
const page = await context.newPage();
|
|
2549
2652
|
const video = page.video();
|
|
2550
2653
|
const actions = focus.actions ?? [];
|
|
@@ -2639,6 +2742,7 @@ async function run(ctx, options) {
|
|
|
2639
2742
|
ctx.logger.info(`recording ${url} (focus: ${options.focus.selector})`);
|
|
2640
2743
|
const { webmPath, leadSeconds, durationSeconds, cropBox } = await captureFocusWebm({
|
|
2641
2744
|
browser: ctx.browser,
|
|
2745
|
+
capture: ctx.capture,
|
|
2642
2746
|
url,
|
|
2643
2747
|
options,
|
|
2644
2748
|
colorScheme: scheme,
|
|
@@ -2692,6 +2796,7 @@ async function run(ctx, options) {
|
|
|
2692
2796
|
ctx.logger.info(`recording ${url} (interaction, ${options.actions.length} action(s))`);
|
|
2693
2797
|
const { webmPath, leadSeconds, durationSeconds } = await captureInteractionWebm({
|
|
2694
2798
|
browser: ctx.browser,
|
|
2799
|
+
capture: ctx.capture,
|
|
2695
2800
|
url,
|
|
2696
2801
|
options,
|
|
2697
2802
|
colorScheme: scheme,
|
|
@@ -2755,6 +2860,7 @@ async function run(ctx, options) {
|
|
|
2755
2860
|
);
|
|
2756
2861
|
await captureScrollFrames({
|
|
2757
2862
|
browser: ctx.browser,
|
|
2863
|
+
capture: ctx.capture,
|
|
2758
2864
|
url: routeUrl,
|
|
2759
2865
|
options: routeOpts,
|
|
2760
2866
|
outPath: segPath,
|
|
@@ -2810,6 +2916,7 @@ async function run(ctx, options) {
|
|
|
2810
2916
|
ctx.logger.info(`recording ${url} (realtime)`);
|
|
2811
2917
|
const { webmPath, leadSeconds } = await captureScrollWebm({
|
|
2812
2918
|
browser: ctx.browser,
|
|
2919
|
+
capture: ctx.capture,
|
|
2813
2920
|
url,
|
|
2814
2921
|
options,
|
|
2815
2922
|
tmpDir: ctx.tmpDir,
|
|
@@ -2873,6 +2980,7 @@ async function run(ctx, options) {
|
|
|
2873
2980
|
ctx.logger.info(`recording ${url}${label} (frame-stepped, ${workers} worker(s))`);
|
|
2874
2981
|
await captureScrollFrames({
|
|
2875
2982
|
browser: ctx.browser,
|
|
2983
|
+
capture: ctx.capture,
|
|
2876
2984
|
url,
|
|
2877
2985
|
options: vopts,
|
|
2878
2986
|
outPath: captureMp4,
|
|
@@ -2999,6 +3107,7 @@ async function captureBreakpoint(args, bp) {
|
|
|
2999
3107
|
viewport: { width: bp.width, height: bp.height },
|
|
3000
3108
|
deviceScaleFactor: bp.deviceScaleFactor ?? options.deviceScaleFactor
|
|
3001
3109
|
});
|
|
3110
|
+
await applyCapture(context, args.capture, url);
|
|
3002
3111
|
const page = await context.newPage();
|
|
3003
3112
|
try {
|
|
3004
3113
|
logger.debug(`[${bp.name}] navigating to ${url}`);
|
|
@@ -3069,7 +3178,8 @@ async function run2(ctx, options) {
|
|
|
3069
3178
|
browser: ctx.browser,
|
|
3070
3179
|
url,
|
|
3071
3180
|
options,
|
|
3072
|
-
logger: ctx.logger
|
|
3181
|
+
logger: ctx.logger,
|
|
3182
|
+
capture: ctx.capture
|
|
3073
3183
|
});
|
|
3074
3184
|
const records = [];
|
|
3075
3185
|
for (const shot of shots) {
|
|
@@ -3449,7 +3559,7 @@ var wallOptionsSchema = z6.object({
|
|
|
3449
3559
|
// src/generators/scene/index.ts
|
|
3450
3560
|
import path13 from "path";
|
|
3451
3561
|
import { fileURLToPath } from "url";
|
|
3452
|
-
import { copyFile as copyFile2, rename, stat as stat4 } from "fs/promises";
|
|
3562
|
+
import { copyFile as copyFile2, rename as rename2, stat as stat4 } from "fs/promises";
|
|
3453
3563
|
|
|
3454
3564
|
// src/scene/serve.ts
|
|
3455
3565
|
import http from "http";
|
|
@@ -3774,7 +3884,7 @@ async function renderScene(ctx, options, generatorId = SCENE_ID) {
|
|
|
3774
3884
|
});
|
|
3775
3885
|
}
|
|
3776
3886
|
try {
|
|
3777
|
-
await
|
|
3887
|
+
await rename2(composedTmp, outPath);
|
|
3778
3888
|
} catch {
|
|
3779
3889
|
await copyFile2(composedTmp, outPath);
|
|
3780
3890
|
}
|
|
@@ -4916,7 +5026,7 @@ function killTreeByPid(pid) {
|
|
|
4916
5026
|
// src/server/manage-server.ts
|
|
4917
5027
|
import path20 from "path";
|
|
4918
5028
|
import http2 from "http";
|
|
4919
|
-
import
|
|
5029
|
+
import https2 from "https";
|
|
4920
5030
|
import { spawn as spawn6 } from "child_process";
|
|
4921
5031
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4922
5032
|
function resolveServerUrl(server) {
|
|
@@ -4924,7 +5034,7 @@ function resolveServerUrl(server) {
|
|
|
4924
5034
|
}
|
|
4925
5035
|
function probe(url) {
|
|
4926
5036
|
return new Promise((resolve) => {
|
|
4927
|
-
const lib = url.startsWith("https:") ?
|
|
5037
|
+
const lib = url.startsWith("https:") ? https2 : http2;
|
|
4928
5038
|
const req = lib.get(url, (res) => {
|
|
4929
5039
|
res.resume();
|
|
4930
5040
|
resolve((res.statusCode ?? 0) > 0);
|
|
@@ -5085,7 +5195,7 @@ async function startManagedServer(server, baseCwd, logger, tasks = {}, signal) {
|
|
|
5085
5195
|
}
|
|
5086
5196
|
|
|
5087
5197
|
// src/pipeline/runner.ts
|
|
5088
|
-
import
|
|
5198
|
+
import os3 from "os";
|
|
5089
5199
|
import path23 from "path";
|
|
5090
5200
|
import { existsSync as existsSync7 } from "fs";
|
|
5091
5201
|
import { mkdtemp as mkdtemp4 } from "fs/promises";
|
|
@@ -5109,7 +5219,10 @@ async function createContext(args) {
|
|
|
5109
5219
|
await ensureDir(genDir);
|
|
5110
5220
|
return {
|
|
5111
5221
|
browser: args.browser,
|
|
5112
|
-
target
|
|
5222
|
+
// Fold the capture query params into the target URL so every generator's `requireUrl(ctx)`
|
|
5223
|
+
// captures in capture mode without each one re-applying it.
|
|
5224
|
+
target: { ...args.target, url: withCaptureQuery(args.target.url, args.capture) },
|
|
5225
|
+
capture: args.capture,
|
|
5113
5226
|
resolvedInputs: args.resolvedInputs,
|
|
5114
5227
|
outDir: args.outDir,
|
|
5115
5228
|
resolveOutPath: (filename) => path21.join(genDir, filename),
|
|
@@ -5196,7 +5309,7 @@ function computeCacheKey(parts) {
|
|
|
5196
5309
|
// src/manifest/manifest.ts
|
|
5197
5310
|
import path22 from "path";
|
|
5198
5311
|
import { existsSync as existsSync6 } from "fs";
|
|
5199
|
-
import { readFile as readFile7, rename as
|
|
5312
|
+
import { readFile as readFile7, rename as rename3, rm as rm5, writeFile as writeFile8 } from "fs/promises";
|
|
5200
5313
|
|
|
5201
5314
|
// src/manifest/schema.ts
|
|
5202
5315
|
import { z as z10 } from "zod";
|
|
@@ -5263,7 +5376,7 @@ async function writeManifest(outDir, manifest) {
|
|
|
5263
5376
|
const maxAttempts = 5;
|
|
5264
5377
|
for (let attempt = 1; ; attempt++) {
|
|
5265
5378
|
try {
|
|
5266
|
-
await
|
|
5379
|
+
await rename3(tmp, file);
|
|
5267
5380
|
return;
|
|
5268
5381
|
} catch (err) {
|
|
5269
5382
|
const code = err.code;
|
|
@@ -5324,7 +5437,7 @@ async function runPipeline(opts) {
|
|
|
5324
5437
|
if (specs.length === 0) return [];
|
|
5325
5438
|
await ensureDir(opts.outDir);
|
|
5326
5439
|
const manifest = await ManifestStore.load(opts.outDir);
|
|
5327
|
-
const tmpRoot = await mkdtemp4(path23.join(
|
|
5440
|
+
const tmpRoot = await mkdtemp4(path23.join(os3.tmpdir(), "pro-visu-"));
|
|
5328
5441
|
const browser = await launchBrowser(opts.config.settings.browser);
|
|
5329
5442
|
opts.onResources?.({ tmpDir: tmpRoot });
|
|
5330
5443
|
const concurrency = Math.max(1, opts.concurrency ?? opts.config.settings.concurrency);
|
|
@@ -5376,7 +5489,9 @@ async function runPipeline(opts) {
|
|
|
5376
5489
|
inputs: inputHashes,
|
|
5377
5490
|
files: fileHashes,
|
|
5378
5491
|
quality,
|
|
5379
|
-
toolVersion: opts.toolVersion
|
|
5492
|
+
toolVersion: opts.toolVersion,
|
|
5493
|
+
// Capture-mode toggles change the rendered output, so a change must bust the cache.
|
|
5494
|
+
capture: opts.config.settings.capture
|
|
5380
5495
|
});
|
|
5381
5496
|
if (cacheEnabled) {
|
|
5382
5497
|
const existing = manifest.find(spec.name);
|
|
@@ -5405,6 +5520,7 @@ async function runPipeline(opts) {
|
|
|
5405
5520
|
toolVersion: opts.toolVersion,
|
|
5406
5521
|
quality,
|
|
5407
5522
|
manifest,
|
|
5523
|
+
capture: opts.config.settings.capture,
|
|
5408
5524
|
onProgress: reporter ? (v) => reporter.progress(spec.name, v) : void 0,
|
|
5409
5525
|
signal: opts.signal
|
|
5410
5526
|
});
|