@scenar/cli 0.8.0 → 0.9.1

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.
Files changed (78) hide show
  1. package/example-bundle/assets/index-DlmG1Uh7.js +388 -0
  2. package/example-bundle/assets/style-BjquDnTE.css +1 -0
  3. package/example-bundle/index.html +2 -2
  4. package/example-bundle/pack-manifest.json +9 -9
  5. package/example-bundle/scenario.json +6 -2
  6. package/package.json +9 -5
  7. package/src/__tests__/collect-pack-shots.test.ts +97 -0
  8. package/src/__tests__/pack-generate-embed-entry.test.ts +68 -3
  9. package/src/__tests__/pack-manifest.test.ts +17 -0
  10. package/src/__tests__/read-shots.test.ts +57 -0
  11. package/src/__tests__/run-shoot.test.ts +319 -0
  12. package/src/api.d.ts +3 -0
  13. package/src/api.d.ts.map +1 -1
  14. package/src/api.js +2 -0
  15. package/src/api.js.map +1 -1
  16. package/src/api.ts +5 -0
  17. package/src/bundle/read-shots.d.ts +28 -0
  18. package/src/bundle/read-shots.d.ts.map +1 -0
  19. package/src/bundle/read-shots.js +28 -0
  20. package/src/bundle/read-shots.js.map +1 -0
  21. package/src/bundle/read-shots.ts +38 -0
  22. package/src/commands/shoot.d.ts +3 -0
  23. package/src/commands/shoot.d.ts.map +1 -0
  24. package/src/commands/shoot.js +52 -0
  25. package/src/commands/shoot.js.map +1 -0
  26. package/src/commands/shoot.ts +72 -0
  27. package/src/index.d.ts.map +1 -1
  28. package/src/index.js +2 -0
  29. package/src/index.js.map +1 -1
  30. package/src/index.ts +2 -0
  31. package/src/pack/build.d.ts +33 -1
  32. package/src/pack/build.d.ts.map +1 -1
  33. package/src/pack/build.js +35 -10
  34. package/src/pack/build.js.map +1 -1
  35. package/src/pack/build.ts +63 -17
  36. package/src/pack/collect-pack-shots.d.ts +40 -0
  37. package/src/pack/collect-pack-shots.d.ts.map +1 -0
  38. package/src/pack/collect-pack-shots.js +59 -0
  39. package/src/pack/collect-pack-shots.js.map +1 -0
  40. package/src/pack/collect-pack-shots.ts +76 -0
  41. package/src/pack/generate-embed-entry.d.ts +18 -0
  42. package/src/pack/generate-embed-entry.d.ts.map +1 -1
  43. package/src/pack/generate-embed-entry.js +92 -3
  44. package/src/pack/generate-embed-entry.js.map +1 -1
  45. package/src/pack/generate-embed-entry.ts +94 -3
  46. package/src/pack/pack-manifest.d.ts +12 -3
  47. package/src/pack/pack-manifest.d.ts.map +1 -1
  48. package/src/pack/pack-manifest.js +13 -3
  49. package/src/pack/pack-manifest.js.map +1 -1
  50. package/src/pack/pack-manifest.ts +13 -2
  51. package/src/pack/run-pack.d.ts +6 -0
  52. package/src/pack/run-pack.d.ts.map +1 -1
  53. package/src/pack/run-pack.js +41 -14
  54. package/src/pack/run-pack.js.map +1 -1
  55. package/src/pack/run-pack.ts +55 -14
  56. package/src/shoot/playwright-browser.d.ts +9 -0
  57. package/src/shoot/playwright-browser.d.ts.map +1 -0
  58. package/src/shoot/playwright-browser.js +92 -0
  59. package/src/shoot/playwright-browser.js.map +1 -0
  60. package/src/shoot/playwright-browser.ts +121 -0
  61. package/src/shoot/run-shoot.d.ts +58 -0
  62. package/src/shoot/run-shoot.d.ts.map +1 -0
  63. package/src/shoot/run-shoot.js +190 -0
  64. package/src/shoot/run-shoot.js.map +1 -0
  65. package/src/shoot/run-shoot.ts +270 -0
  66. package/src/shoot/types.d.ts +40 -0
  67. package/src/shoot/types.d.ts.map +1 -0
  68. package/src/shoot/types.js +2 -0
  69. package/src/shoot/types.js.map +1 -0
  70. package/src/shoot/types.ts +40 -0
  71. package/src/util/load-ts.d.ts +12 -2
  72. package/src/util/load-ts.d.ts.map +1 -1
  73. package/src/util/load-ts.js +25 -10
  74. package/src/util/load-ts.js.map +1 -1
  75. package/src/util/load-ts.ts +25 -11
  76. package/tsconfig.tsbuildinfo +1 -1
  77. package/example-bundle/assets/index-CBaiZ4ek.js +0 -147
  78. package/example-bundle/assets/style-Ci-h7jik.css +0 -1
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Chromium launch flags for byte-deterministic rasterization — every one
3
+ * proven necessary by the 2026-07-28 determinism spike (without them,
4
+ * ±1-LSB full-frame compositing jitter across sessions):
5
+ *
6
+ * - `--disable-gpu`: software rasterization, no GPU-dependent output.
7
+ * - `--force-color-profile=srgb`: pins color conversion.
8
+ * - `--disable-lcd-text`: subpixel AA depends on the (virtual) display.
9
+ * - `--hide-scrollbars`: platform scrollbar chrome is not content.
10
+ */
11
+ const DETERMINISM_LAUNCH_FLAGS = [
12
+ "--disable-gpu",
13
+ "--force-color-profile=srgb",
14
+ "--disable-lcd-text",
15
+ "--hide-scrollbars",
16
+ ];
17
+ const PLAYWRIGHT_INSTALL_HINT = "Install it in your demos project: npm install -D playwright\n" +
18
+ "Then install the browser binary: npx playwright install chromium";
19
+ /**
20
+ * The real {@link ShotBrowser}: Playwright Chromium with the full spike
21
+ * determinism recipe. Playwright is an optional peer dependency (the
22
+ * remotion/echogarden pattern) — loaded lazily so every other command works
23
+ * without it, and failing with install instructions when absent.
24
+ */
25
+ export async function createPlaywrightShotBrowser() {
26
+ const playwright = await import("playwright").catch(() => {
27
+ throw new Error(`Could not load playwright.\n${PLAYWRIGHT_INSTALL_HINT}`);
28
+ });
29
+ const browser = await playwright.chromium
30
+ .launch({ args: DETERMINISM_LAUNCH_FLAGS })
31
+ .catch((error) => {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ throw new Error(`Could not launch Chromium.\n${message}\n\n${PLAYWRIGHT_INSTALL_HINT}`);
34
+ });
35
+ return {
36
+ async newSession({ theme, viewport }) {
37
+ // `reducedMotion: "reduce"` is safe under a TimeSource: step index
38
+ // derives from time, not from the reduced-motion shortcut — and it
39
+ // keeps framer-motion from animating on wall-clock time.
40
+ // `colorScheme` emulation resolves the stage backdrop's light-dark()
41
+ // colors; the `?theme=` param handles the scenar token class — both
42
+ // halves are required for a correct dark variant (DD-02 D2).
43
+ const context = await browser.newContext({
44
+ viewport,
45
+ deviceScaleFactor: 2,
46
+ reducedMotion: "reduce",
47
+ colorScheme: theme,
48
+ });
49
+ const page = await context.newPage();
50
+ let frameSelector = "";
51
+ return {
52
+ async open(url, timeoutMs) {
53
+ await page.goto(url, { waitUntil: "networkidle" });
54
+ await page.waitForFunction(() => window.__scenarShot !== undefined ||
55
+ window.__scenarShotError !== undefined, undefined, { timeout: timeoutMs });
56
+ const result = await page.evaluate(() => {
57
+ const w = window;
58
+ if (w.__scenarShotError !== undefined) {
59
+ return { error: String(w.__scenarShotError), shots: [], frameSelector: "" };
60
+ }
61
+ const driver = w.__scenarShot;
62
+ return { error: null, shots: driver.shots, frameSelector: driver.frameSelector };
63
+ });
64
+ if (result.error !== null) {
65
+ throw new Error(`the capture page reported: ${result.error}`);
66
+ }
67
+ // Webfont rasterization differs from the fallback font — never
68
+ // shoot before every declared font face has loaded.
69
+ await page.evaluate(() => document.fonts.ready.then(() => undefined));
70
+ frameSelector = result.frameSelector;
71
+ return { shots: result.shots, frameSelector: result.frameSelector };
72
+ },
73
+ async walkTo(timeMs) {
74
+ await page.evaluate((ms) => window.__scenarShot.walkTo(ms), timeMs);
75
+ },
76
+ async screenshotFrame() {
77
+ // `animations: "disabled"` is load-bearing: the shells crossfade
78
+ // content on wall-clock CSS time, and this alone caused whole-pane
79
+ // noise in the spike until disabled at screenshot time.
80
+ return page.locator(frameSelector).screenshot({ animations: "disabled" });
81
+ },
82
+ async close() {
83
+ await context.close();
84
+ },
85
+ };
86
+ },
87
+ async close() {
88
+ await browser.close();
89
+ },
90
+ };
91
+ }
92
+ //# sourceMappingURL=playwright-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-browser.js","sourceRoot":"","sources":["../../../src/shoot/playwright-browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import type { ShotBrowser, ShotCaptureInfo } from "./types.js";
2
+
3
+ /**
4
+ * Chromium launch flags for byte-deterministic rasterization — every one
5
+ * proven necessary by the 2026-07-28 determinism spike (without them,
6
+ * ±1-LSB full-frame compositing jitter across sessions):
7
+ *
8
+ * - `--disable-gpu`: software rasterization, no GPU-dependent output.
9
+ * - `--force-color-profile=srgb`: pins color conversion.
10
+ * - `--disable-lcd-text`: subpixel AA depends on the (virtual) display.
11
+ * - `--hide-scrollbars`: platform scrollbar chrome is not content.
12
+ */
13
+ const DETERMINISM_LAUNCH_FLAGS = [
14
+ "--disable-gpu",
15
+ "--force-color-profile=srgb",
16
+ "--disable-lcd-text",
17
+ "--hide-scrollbars",
18
+ ];
19
+
20
+ const PLAYWRIGHT_INSTALL_HINT =
21
+ "Install it in your demos project: npm install -D playwright\n" +
22
+ "Then install the browser binary: npx playwright install chromium";
23
+
24
+ /**
25
+ * The real {@link ShotBrowser}: Playwright Chromium with the full spike
26
+ * determinism recipe. Playwright is an optional peer dependency (the
27
+ * remotion/echogarden pattern) — loaded lazily so every other command works
28
+ * without it, and failing with install instructions when absent.
29
+ */
30
+ export async function createPlaywrightShotBrowser(): Promise<ShotBrowser> {
31
+ const playwright = await import("playwright").catch(() => {
32
+ throw new Error(`Could not load playwright.\n${PLAYWRIGHT_INSTALL_HINT}`);
33
+ });
34
+
35
+ const browser = await playwright.chromium
36
+ .launch({ args: DETERMINISM_LAUNCH_FLAGS })
37
+ .catch((error: unknown) => {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ throw new Error(
40
+ `Could not launch Chromium.\n${message}\n\n${PLAYWRIGHT_INSTALL_HINT}`,
41
+ );
42
+ });
43
+
44
+ return {
45
+ async newSession({ theme, viewport }) {
46
+ // `reducedMotion: "reduce"` is safe under a TimeSource: step index
47
+ // derives from time, not from the reduced-motion shortcut — and it
48
+ // keeps framer-motion from animating on wall-clock time.
49
+ // `colorScheme` emulation resolves the stage backdrop's light-dark()
50
+ // colors; the `?theme=` param handles the scenar token class — both
51
+ // halves are required for a correct dark variant (DD-02 D2).
52
+ const context = await browser.newContext({
53
+ viewport,
54
+ deviceScaleFactor: 2,
55
+ reducedMotion: "reduce",
56
+ colorScheme: theme,
57
+ });
58
+ const page = await context.newPage();
59
+ let frameSelector = "";
60
+
61
+ return {
62
+ async open(url, timeoutMs) {
63
+ await page.goto(url, { waitUntil: "networkidle" });
64
+ await page.waitForFunction(
65
+ () =>
66
+ (window as { __scenarShot?: unknown }).__scenarShot !== undefined ||
67
+ (window as { __scenarShotError?: unknown }).__scenarShotError !== undefined,
68
+ undefined,
69
+ { timeout: timeoutMs },
70
+ );
71
+ const result: { error: string | null } & ShotCaptureInfo = await page.evaluate(() => {
72
+ const w = window as unknown as {
73
+ __scenarShot?: { shots: ShotCaptureInfo["shots"]; frameSelector: string };
74
+ __scenarShotError?: string;
75
+ };
76
+ if (w.__scenarShotError !== undefined) {
77
+ return { error: String(w.__scenarShotError), shots: [], frameSelector: "" };
78
+ }
79
+ const driver = w.__scenarShot!;
80
+ return { error: null, shots: driver.shots, frameSelector: driver.frameSelector };
81
+ });
82
+ if (result.error !== null) {
83
+ throw new Error(`the capture page reported: ${result.error}`);
84
+ }
85
+ // Webfont rasterization differs from the fallback font — never
86
+ // shoot before every declared font face has loaded.
87
+ await page.evaluate(() => document.fonts.ready.then(() => undefined));
88
+ frameSelector = result.frameSelector;
89
+ return { shots: result.shots, frameSelector: result.frameSelector };
90
+ },
91
+
92
+ async walkTo(timeMs) {
93
+ await page.evaluate(
94
+ (ms) =>
95
+ (
96
+ window as unknown as {
97
+ __scenarShot: { walkTo: (t: number) => Promise<void> };
98
+ }
99
+ ).__scenarShot.walkTo(ms),
100
+ timeMs,
101
+ );
102
+ },
103
+
104
+ async screenshotFrame() {
105
+ // `animations: "disabled"` is load-bearing: the shells crossfade
106
+ // content on wall-clock CSS time, and this alone caused whole-pane
107
+ // noise in the spike until disabled at screenshot time.
108
+ return page.locator(frameSelector).screenshot({ animations: "disabled" });
109
+ },
110
+
111
+ async close() {
112
+ await context.close();
113
+ },
114
+ };
115
+ },
116
+
117
+ async close() {
118
+ await browser.close();
119
+ },
120
+ };
121
+ }
@@ -0,0 +1,58 @@
1
+ import type { ShotBrowser, ShotCaptureInfo, ShotTheme } from "./types.js";
2
+ /** Directory inside the bundle where stills land (and deploy from). */
3
+ export declare const STILLS_DIR = "stills";
4
+ /** Options for {@link runShoot}. Paths may be relative; they are resolved here. */
5
+ export interface RunShootOptions {
6
+ /** A packed bundle directory (a `scenar pack` output with a ?shot entry). */
7
+ readonly bundleDir: string;
8
+ /** Themes to capture (default: both — DD-02 D2). */
9
+ readonly themes?: readonly ShotTheme[];
10
+ /**
11
+ * Capture everything twice in fresh browser sessions and byte-compare —
12
+ * the determinism gate. A mismatch means some component renders
13
+ * nondeterministically and the stills cannot be trusted.
14
+ */
15
+ readonly verify?: boolean;
16
+ /** Per-page readiness timeout in ms (default {@link DEFAULT_TIMEOUT_MS}). */
17
+ readonly timeoutMs?: number;
18
+ /** Progress sink for mid-operation messages. */
19
+ readonly onLog?: (message: string) => void;
20
+ /** Test seam: swap the real Playwright browser for a fake. */
21
+ readonly browserFactory?: () => Promise<ShotBrowser>;
22
+ }
23
+ /** The outcome of a successful shoot. */
24
+ export interface ShootResult {
25
+ readonly scenarioId: string;
26
+ readonly bundleDir: string;
27
+ /** The bundle's declared shots, in timeline order (empty = nothing to do). */
28
+ readonly shots: ShotCaptureInfo["shots"];
29
+ /** Bundle-relative paths of the stills written, in write order. */
30
+ readonly files: readonly string[];
31
+ readonly themes: readonly ShotTheme[];
32
+ /** True when `verify` ran and every still was byte-identical. */
33
+ readonly verified: boolean;
34
+ }
35
+ /**
36
+ * Render a packed bundle's declared shots to still images — the pure
37
+ * orchestration behind `scenar shoot`, with no process/exit coupling so both
38
+ * the CLI command and the MCP server can call it.
39
+ *
40
+ * Bundle-centric by design: stills are captured from (and written into) the
41
+ * exact bundle that deploys, so they can never depict anything but what
42
+ * ships. The pack manifest is rebuilt afterwards so `publish`/deploy pick
43
+ * the stills up with zero pipeline changes.
44
+ *
45
+ * `stills/` is cleared up front, unconditionally — even when the scenario
46
+ * declares no shots — and the manifest is rebuilt in every path. A removed
47
+ * or renamed shot must never linger in a deployed bundle, and a manifest
48
+ * listing deleted files would fail its own existence check at the next
49
+ * publish.
50
+ *
51
+ * When scenario.json records an authoritatively empty shot list (pack ≥
52
+ * generator 0.0.2), the whole server + browser boot is skipped — both
53
+ * invariants above still hold on that path. A recorded non-empty list does
54
+ * NOT bypass the capture page: the running bundle stays the runtime truth
55
+ * for what gets shot; the record only proves there is something to boot for.
56
+ */
57
+ export declare function runShoot(options: RunShootOptions): Promise<ShootResult>;
58
+ //# sourceMappingURL=run-shoot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-shoot.d.ts","sourceRoot":"","sources":["../../../src/shoot/run-shoot.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAe,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvF,uEAAuE;AACvE,eAAO,MAAM,UAAU,WAAW,CAAC;AAWnC,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;CACtD;AAED,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACzC,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAsF7E"}
@@ -0,0 +1,190 @@
1
+ import { join, resolve } from "node:path";
2
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import { PACK_MANIFEST_FILE, SCENARIO_JSON_FILE, buildPackManifest, verifyManifestFilesExist, writePackManifest, } from "../pack/pack-manifest.js";
4
+ import { readBundleViewport } from "../bundle/read-viewport.js";
5
+ import { readBundleShots } from "../bundle/read-shots.js";
6
+ import { startBundleServer } from "../serve/static-server.js";
7
+ import { createPlaywrightShotBrowser } from "./playwright-browser.js";
8
+ /** Directory inside the bundle where stills land (and deploy from). */
9
+ export const STILLS_DIR = "stills";
10
+ /** How long to wait for the capture page's driver before failing. */
11
+ const DEFAULT_TIMEOUT_MS = 30_000;
12
+ /**
13
+ * Extra room around the canonical frame so the element screenshot never has
14
+ * to scroll-stitch (stitching re-rasterizes and would break byte-exactness).
15
+ */
16
+ const VIEWPORT_MARGIN_PX = 64;
17
+ /**
18
+ * Render a packed bundle's declared shots to still images — the pure
19
+ * orchestration behind `scenar shoot`, with no process/exit coupling so both
20
+ * the CLI command and the MCP server can call it.
21
+ *
22
+ * Bundle-centric by design: stills are captured from (and written into) the
23
+ * exact bundle that deploys, so they can never depict anything but what
24
+ * ships. The pack manifest is rebuilt afterwards so `publish`/deploy pick
25
+ * the stills up with zero pipeline changes.
26
+ *
27
+ * `stills/` is cleared up front, unconditionally — even when the scenario
28
+ * declares no shots — and the manifest is rebuilt in every path. A removed
29
+ * or renamed shot must never linger in a deployed bundle, and a manifest
30
+ * listing deleted files would fail its own existence check at the next
31
+ * publish.
32
+ *
33
+ * When scenario.json records an authoritatively empty shot list (pack ≥
34
+ * generator 0.0.2), the whole server + browser boot is skipped — both
35
+ * invariants above still hold on that path. A recorded non-empty list does
36
+ * NOT bypass the capture page: the running bundle stays the runtime truth
37
+ * for what gets shot; the record only proves there is something to boot for.
38
+ */
39
+ export async function runShoot(options) {
40
+ const onLog = options.onLog ?? (() => { });
41
+ const bundleDir = resolve(options.bundleDir);
42
+ const themes = options.themes ?? ["light", "dark"];
43
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
44
+ const scenarioId = await validateBundle(bundleDir);
45
+ const { viewport } = await readBundleViewport(bundleDir);
46
+ onLog(`Scenario: ${scenarioId}`);
47
+ onLog(`Bundle: ${bundleDir}`);
48
+ onLog(`Viewport: ${viewport.width}x${viewport.height} (DPR 2)`);
49
+ onLog(`Themes: ${themes.join(", ")}`);
50
+ await rm(join(bundleDir, STILLS_DIR), { recursive: true, force: true });
51
+ // Short-circuit on pack's authority: a recorded-and-empty shot list means
52
+ // there is provably nothing to capture, so don't pay for a static server
53
+ // and a Chromium launch just to learn that. Absent record = unknown — fall
54
+ // through and let the capture page answer, exactly as before the record
55
+ // existed.
56
+ const recordedShots = await readBundleShots(bundleDir);
57
+ if (recordedShots.recorded && recordedShots.shots.length === 0) {
58
+ onLog("No steps declare a `shot` — nothing to capture.");
59
+ await rebuildManifest(bundleDir, scenarioId);
60
+ return { scenarioId, bundleDir, shots: [], files: [], themes, verified: false };
61
+ }
62
+ const server = await startBundleServer({ rootDir: bundleDir, port: 0 });
63
+ let browser;
64
+ try {
65
+ browser = await (options.browserFactory ?? createPlaywrightShotBrowser)();
66
+ const sessionViewport = {
67
+ width: viewport.width + VIEWPORT_MARGIN_PX,
68
+ height: viewport.height + VIEWPORT_MARGIN_PX,
69
+ };
70
+ const captures = new Map();
71
+ for (const theme of themes) {
72
+ captures.set(theme, await captureTheme(browser, server.url, theme, sessionViewport, timeoutMs));
73
+ }
74
+ const first = captures.get(themes[0]).info;
75
+ assertShotsAgreeAcrossThemes(captures);
76
+ if (first.shots.length === 0) {
77
+ onLog("No steps declare a `shot` — nothing to capture.");
78
+ await rebuildManifest(bundleDir, scenarioId);
79
+ return { scenarioId, bundleDir, shots: [], files: [], themes, verified: false };
80
+ }
81
+ if (options.verify) {
82
+ onLog("Verifying determinism (second capture in fresh sessions)...");
83
+ for (const theme of themes) {
84
+ const rerun = await captureTheme(browser, server.url, theme, sessionViewport, timeoutMs);
85
+ assertByteIdentical(theme, captures.get(theme), rerun);
86
+ }
87
+ onLog(`Determinism verified: every still byte-identical across sessions.`);
88
+ }
89
+ await mkdir(join(bundleDir, STILLS_DIR), { recursive: true });
90
+ const files = [];
91
+ for (const theme of themes) {
92
+ const { info, images } = captures.get(theme);
93
+ for (const [i, shot] of info.shots.entries()) {
94
+ const relative = `${STILLS_DIR}/${shot.name}.${theme}.png`;
95
+ await writeFile(join(bundleDir, ...relative.split("/")), images[i]);
96
+ files.push(relative);
97
+ onLog(`Captured: ${relative}`);
98
+ }
99
+ }
100
+ await rebuildManifest(bundleDir, scenarioId);
101
+ return {
102
+ scenarioId,
103
+ bundleDir,
104
+ shots: first.shots,
105
+ files,
106
+ themes,
107
+ verified: options.verify ?? false,
108
+ };
109
+ }
110
+ finally {
111
+ await browser?.close().catch(() => { });
112
+ await server.close().catch(() => { });
113
+ }
114
+ }
115
+ /** Walk one themed session through every shot, collecting screenshots. */
116
+ async function captureTheme(browser, baseUrl, theme, viewport, timeoutMs) {
117
+ // `?shot` (bare) switches the packed entry into capture mode; the scenar
118
+ // token class needs `theme=dark` on top of the context's colorScheme
119
+ // emulation — both halves of a dark variant (DD-02 D2).
120
+ const url = `${baseUrl}?shot${theme === "dark" ? "&theme=dark" : ""}`;
121
+ const session = await browser.newSession({ theme, viewport });
122
+ try {
123
+ const info = await session.open(url, timeoutMs);
124
+ const images = [];
125
+ // Sequential walk in timeline order — never a cold jump (cross-step
126
+ // React state does not survive one; spike-proven, DD-02).
127
+ for (const shot of info.shots) {
128
+ await session.walkTo(shot.timeMs);
129
+ images.push(await session.screenshotFrame());
130
+ }
131
+ return { info, images };
132
+ }
133
+ finally {
134
+ await session.close().catch(() => { });
135
+ }
136
+ }
137
+ /** A bundle is shootable when it has the three files `scenar pack` writes. */
138
+ async function validateBundle(bundleDir) {
139
+ const info = await stat(bundleDir).catch(() => null);
140
+ if (!info || !info.isDirectory()) {
141
+ throw new Error(`${bundleDir} is not a directory. Pass a bundle produced by \`scenar pack\`.`);
142
+ }
143
+ for (const required of ["index.html", SCENARIO_JSON_FILE, PACK_MANIFEST_FILE]) {
144
+ const fileInfo = await stat(join(bundleDir, required)).catch(() => null);
145
+ if (!fileInfo || !fileInfo.isFile()) {
146
+ throw new Error(`no ${required} in ${bundleDir}. Run \`scenar pack\` to produce a shootable bundle first.`);
147
+ }
148
+ }
149
+ const scenarioJson = JSON.parse(await readFile(join(bundleDir, SCENARIO_JSON_FILE), "utf-8"));
150
+ if (typeof scenarioJson.id !== "string" || scenarioJson.id.length === 0) {
151
+ throw new Error(`${SCENARIO_JSON_FILE} in ${bundleDir} has no scenario id.`);
152
+ }
153
+ return scenarioJson.id;
154
+ }
155
+ /**
156
+ * The shot list is derived from the bundled steps, so every theme must
157
+ * report the identical list — a divergence means the bundle itself renders
158
+ * nondeterministically and nothing downstream can be trusted.
159
+ */
160
+ function assertShotsAgreeAcrossThemes(captures) {
161
+ const lists = [...captures.entries()].map(([theme, { info }]) => [theme, JSON.stringify(info.shots)]);
162
+ const [, reference] = lists[0];
163
+ for (const [theme, list] of lists) {
164
+ if (list !== reference) {
165
+ throw new Error(`internal error: the ${theme} capture reported a different shot list than ${lists[0][0]}`);
166
+ }
167
+ }
168
+ }
169
+ /** Byte-compare two capture passes of the same theme (the `--verify` gate). */
170
+ function assertByteIdentical(theme, first, second) {
171
+ const differing = first.info.shots
172
+ .filter((_, i) => !first.images[i].equals(second.images[i]))
173
+ .map((shot) => shot.name);
174
+ if (differing.length > 0) {
175
+ throw new Error(`determinism check failed (${theme}): shot(s) ${differing.join(", ")} differed ` +
176
+ `across fresh sessions. Some component renders nondeterministically — ` +
177
+ `fix that before trusting these stills (see DD-02's determinism recipe).`);
178
+ }
179
+ }
180
+ /**
181
+ * Re-list the bundle after stills changed. `buildPackManifest` validates
182
+ * every file against the deploy allowlist, so a bad shot filename fails
183
+ * here, locally, rather than mid-upload.
184
+ */
185
+ async function rebuildManifest(bundleDir, scenarioId) {
186
+ const manifest = await buildPackManifest(bundleDir, scenarioId);
187
+ await verifyManifestFilesExist(bundleDir, manifest);
188
+ await writePackManifest(bundleDir, manifest);
189
+ }
190
+ //# sourceMappingURL=run-shoot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-shoot.js","sourceRoot":"","sources":["../../../src/shoot/run-shoot.ts"],"names":[],"mappings":"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"}