@scenar/cli 0.9.0 → 0.10.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.
Files changed (66) hide show
  1. package/example-bundle/assets/index-DpJUOVBT.js +388 -0
  2. package/example-bundle/assets/style-ix_q0zsc.css +1 -0
  3. package/example-bundle/embed.js +1 -1
  4. package/example-bundle/index.html +2 -2
  5. package/example-bundle/pack-manifest.json +11 -11
  6. package/example-bundle/scenario.json +6 -2
  7. package/package.json +4 -4
  8. package/src/__tests__/collect-pack-shots.test.ts +115 -0
  9. package/src/__tests__/find-authored-viewport.test.ts +58 -0
  10. package/src/__tests__/pack-generate-embed-entry.test.ts +5 -1
  11. package/src/__tests__/pack-manifest.test.ts +17 -0
  12. package/src/__tests__/read-shots.test.ts +57 -0
  13. package/src/__tests__/resolve-pack-viewport.test.ts +46 -0
  14. package/src/__tests__/run-shoot.test.ts +51 -2
  15. package/src/bundle/read-shots.d.ts +28 -0
  16. package/src/bundle/read-shots.d.ts.map +1 -0
  17. package/src/bundle/read-shots.js +28 -0
  18. package/src/bundle/read-shots.js.map +1 -0
  19. package/src/bundle/read-shots.ts +38 -0
  20. package/src/commands/pack.d.ts.map +1 -1
  21. package/src/commands/pack.js +23 -4
  22. package/src/commands/pack.js.map +1 -1
  23. package/src/commands/pack.ts +26 -5
  24. package/src/pack/build.d.ts +33 -1
  25. package/src/pack/build.d.ts.map +1 -1
  26. package/src/pack/build.js +35 -10
  27. package/src/pack/build.js.map +1 -1
  28. package/src/pack/build.ts +63 -17
  29. package/src/pack/collect-pack-shots.d.ts +47 -0
  30. package/src/pack/collect-pack-shots.d.ts.map +1 -0
  31. package/src/pack/collect-pack-shots.js +66 -0
  32. package/src/pack/collect-pack-shots.js.map +1 -0
  33. package/src/pack/collect-pack-shots.ts +89 -0
  34. package/src/pack/generate-embed-entry.d.ts +7 -0
  35. package/src/pack/generate-embed-entry.d.ts.map +1 -1
  36. package/src/pack/generate-embed-entry.js +14 -3
  37. package/src/pack/generate-embed-entry.js.map +1 -1
  38. package/src/pack/generate-embed-entry.ts +15 -3
  39. package/src/pack/pack-manifest.d.ts +12 -3
  40. package/src/pack/pack-manifest.d.ts.map +1 -1
  41. package/src/pack/pack-manifest.js +13 -3
  42. package/src/pack/pack-manifest.js.map +1 -1
  43. package/src/pack/pack-manifest.ts +13 -2
  44. package/src/pack/run-pack.d.ts +13 -2
  45. package/src/pack/run-pack.d.ts.map +1 -1
  46. package/src/pack/run-pack.js +56 -17
  47. package/src/pack/run-pack.js.map +1 -1
  48. package/src/pack/run-pack.ts +81 -19
  49. package/src/pack/viewport.d.ts +17 -0
  50. package/src/pack/viewport.d.ts.map +1 -1
  51. package/src/pack/viewport.js +19 -0
  52. package/src/pack/viewport.js.map +1 -1
  53. package/src/pack/viewport.ts +29 -0
  54. package/src/shoot/run-shoot.d.ts +6 -0
  55. package/src/shoot/run-shoot.d.ts.map +1 -1
  56. package/src/shoot/run-shoot.js +18 -0
  57. package/src/shoot/run-shoot.js.map +1 -1
  58. package/src/shoot/run-shoot.ts +19 -0
  59. package/src/util/load-ts.d.ts +35 -2
  60. package/src/util/load-ts.d.ts.map +1 -1
  61. package/src/util/load-ts.js +76 -10
  62. package/src/util/load-ts.js.map +1 -1
  63. package/src/util/load-ts.ts +87 -11
  64. package/tsconfig.tsbuildinfo +1 -1
  65. package/example-bundle/assets/index-BoIR0_nN.js +0 -147
  66. package/example-bundle/assets/style-YRMeJPPX.css +0 -1
@@ -0,0 +1,28 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { SCENARIO_JSON_FILE } from "../pack/pack-manifest.js";
4
+ /**
5
+ * Read the declared shot names recorded in a bundle's scenario.json.
6
+ *
7
+ * The sibling of {@link readBundleViewport}, with the same forgiving posture:
8
+ * any missing/unreadable/malformed state degrades to `recorded: false` rather
9
+ * than throwing, because an absent record only costs the caller a browser
10
+ * boot — it must never fail an operation that a pre-`shots` bundle could
11
+ * complete. Present-and-empty is meaningfully different: it is pack's
12
+ * authoritative statement that the scenario declares no shots, which is what
13
+ * lets `runShoot` skip the browser entirely.
14
+ */
15
+ export async function readBundleShots(bundleDir) {
16
+ try {
17
+ const raw = await readFile(join(bundleDir, SCENARIO_JSON_FILE), "utf-8");
18
+ const shots = JSON.parse(raw).shots;
19
+ if (Array.isArray(shots) && shots.every((name) => typeof name === "string")) {
20
+ return { recorded: true, shots };
21
+ }
22
+ }
23
+ catch {
24
+ // Missing/unreadable/invalid scenario.json — the shots are unknown.
25
+ }
26
+ return { recorded: false };
27
+ }
28
+ //# sourceMappingURL=read-shots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-shots.js","sourceRoot":"","sources":["../../../src/bundle/read-shots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAa9D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAiB;IACrD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC,CAAC;QACzE,MAAM,KAAK,GAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC,KAAK,CAAC;QAC7D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;YAC5E,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oEAAoE;IACtE,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { SCENARIO_JSON_FILE } from "../pack/pack-manifest.js";
4
+
5
+ /** The outcome of reading a bundle's recorded shot names. */
6
+ export type BundleShots =
7
+ /** scenario.json carries an authoritative list (possibly empty). */
8
+ | { readonly recorded: true; readonly shots: readonly string[] }
9
+ /**
10
+ * No usable `shots` record — the bundle predates the key, pack could not
11
+ * load the steps module, or the file is missing/malformed. The shots are
12
+ * unknown; only booting the capture page can answer.
13
+ */
14
+ | { readonly recorded: false };
15
+
16
+ /**
17
+ * Read the declared shot names recorded in a bundle's scenario.json.
18
+ *
19
+ * The sibling of {@link readBundleViewport}, with the same forgiving posture:
20
+ * any missing/unreadable/malformed state degrades to `recorded: false` rather
21
+ * than throwing, because an absent record only costs the caller a browser
22
+ * boot — it must never fail an operation that a pre-`shots` bundle could
23
+ * complete. Present-and-empty is meaningfully different: it is pack's
24
+ * authoritative statement that the scenario declares no shots, which is what
25
+ * lets `runShoot` skip the browser entirely.
26
+ */
27
+ export async function readBundleShots(bundleDir: string): Promise<BundleShots> {
28
+ try {
29
+ const raw = await readFile(join(bundleDir, SCENARIO_JSON_FILE), "utf-8");
30
+ const shots = (JSON.parse(raw) as { shots?: unknown }).shots;
31
+ if (Array.isArray(shots) && shots.every((name) => typeof name === "string")) {
32
+ return { recorded: true, shots };
33
+ }
34
+ } catch {
35
+ // Missing/unreadable/invalid scenario.json — the shots are unknown.
36
+ }
37
+ return { recorded: false };
38
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuD1D"}
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6D1D"}
@@ -22,8 +22,12 @@ export function registerPackCommand(program) {
22
22
  "Output defaults to ./<scenario-id>-bundle. Use --out to override.")
23
23
  .argument("<dir>", "path to a scenario directory (must contain steps.ts)")
24
24
  .option("--out <path>", "output directory for the bundle")
25
- .option("--width <number>", `canonical viewport width in px (default: ${DEFAULT_WIDTH})`, String(DEFAULT_WIDTH))
26
- .option("--shell-height <number>", `shell height in px (default: ${DEFAULT_SHELL_HEIGHT})`, String(DEFAULT_SHELL_HEIGHT))
25
+ // No commander defaults on the viewport flags: an absent flag must stay
26
+ // absent so the scenario's own authored viewport can apply (resolution
27
+ // in runPack: flags > authored > default). A baked-in default here made
28
+ // `createScenario({ viewport })` silently unreachable.
29
+ .option("--width <number>", `canonical viewport width in px (default: the scenario's authored viewport, else ${DEFAULT_WIDTH})`)
30
+ .option("--shell-height <number>", `shell height in px (default: the scenario's authored viewport, else ${DEFAULT_SHELL_HEIGHT})`)
27
31
  .option("--stage", "float the scenario on a backdrop with a window shadow (screen-recording framing); " +
28
32
  "the embed paints its own background instead of staying transparent")
29
33
  .option("--keep-temp", "keep the generated entry directory for debugging")
@@ -32,8 +36,8 @@ export function registerPackCommand(program) {
32
36
  const result = await runPack({
33
37
  scenarioDir: dir,
34
38
  outDir: options.out,
35
- width: Number(options.width) || DEFAULT_WIDTH,
36
- shellHeight: Number(options.shellHeight) || DEFAULT_SHELL_HEIGHT,
39
+ width: parseViewportFlag("--width", options.width),
40
+ shellHeight: parseViewportFlag("--shell-height", options.shellHeight),
37
41
  stage: options.stage ?? false,
38
42
  keepTemp: options.keepTemp,
39
43
  onLog: (message) => process.stderr.write(`${message}\n`),
@@ -49,6 +53,21 @@ export function registerPackCommand(program) {
49
53
  }
50
54
  });
51
55
  }
56
+ /**
57
+ * Parse a viewport flag into a positive number, or `undefined` when the flag
58
+ * was not given. A malformed value errors instead of silently falling back —
59
+ * the previous `Number(x) || DEFAULT` coercion turned `--width 128O` (typo)
60
+ * into a default-size bundle with no warning.
61
+ */
62
+ function parseViewportFlag(flag, value) {
63
+ if (value === undefined)
64
+ return undefined;
65
+ const parsed = Number(value);
66
+ if (!Number.isFinite(parsed) || parsed <= 0) {
67
+ throw new Error(`${flag} must be a positive number, got "${value}".`);
68
+ }
69
+ return parsed;
70
+ }
52
71
  function formatBytes(bytes) {
53
72
  if (bytes < 1024)
54
73
  return `${bytes} B`;
@@ -1 +1 @@
1
- {"version":3,"file":"pack.js","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pack.js","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"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"}
@@ -36,11 +36,17 @@ export function registerPackCommand(program: Command): void {
36
36
  )
37
37
  .argument("<dir>", "path to a scenario directory (must contain steps.ts)")
38
38
  .option("--out <path>", "output directory for the bundle")
39
- .option("--width <number>", `canonical viewport width in px (default: ${DEFAULT_WIDTH})`, String(DEFAULT_WIDTH))
39
+ // No commander defaults on the viewport flags: an absent flag must stay
40
+ // absent so the scenario's own authored viewport can apply (resolution
41
+ // in runPack: flags > authored > default). A baked-in default here made
42
+ // `createScenario({ viewport })` silently unreachable.
43
+ .option(
44
+ "--width <number>",
45
+ `canonical viewport width in px (default: the scenario's authored viewport, else ${DEFAULT_WIDTH})`,
46
+ )
40
47
  .option(
41
48
  "--shell-height <number>",
42
- `shell height in px (default: ${DEFAULT_SHELL_HEIGHT})`,
43
- String(DEFAULT_SHELL_HEIGHT),
49
+ `shell height in px (default: the scenario's authored viewport, else ${DEFAULT_SHELL_HEIGHT})`,
44
50
  )
45
51
  .option(
46
52
  "--stage",
@@ -53,8 +59,8 @@ export function registerPackCommand(program: Command): void {
53
59
  const result = await runPack({
54
60
  scenarioDir: dir,
55
61
  outDir: options.out,
56
- width: Number(options.width) || DEFAULT_WIDTH,
57
- shellHeight: Number(options.shellHeight) || DEFAULT_SHELL_HEIGHT,
62
+ width: parseViewportFlag("--width", options.width),
63
+ shellHeight: parseViewportFlag("--shell-height", options.shellHeight),
58
64
  stage: options.stage ?? false,
59
65
  keepTemp: options.keepTemp,
60
66
  onLog: (message) => process.stderr.write(`${message}\n`),
@@ -73,6 +79,21 @@ export function registerPackCommand(program: Command): void {
73
79
  });
74
80
  }
75
81
 
82
+ /**
83
+ * Parse a viewport flag into a positive number, or `undefined` when the flag
84
+ * was not given. A malformed value errors instead of silently falling back —
85
+ * the previous `Number(x) || DEFAULT` coercion turned `--width 128O` (typo)
86
+ * into a default-size bundle with no warning.
87
+ */
88
+ function parseViewportFlag(flag: string, value: string | undefined): number | undefined {
89
+ if (value === undefined) return undefined;
90
+ const parsed = Number(value);
91
+ if (!Number.isFinite(parsed) || parsed <= 0) {
92
+ throw new Error(`${flag} must be a positive number, got "${value}".`);
93
+ }
94
+ return parsed;
95
+ }
96
+
76
97
  function formatBytes(bytes: number): string {
77
98
  if (bytes < 1024) return `${bytes} B`;
78
99
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
@@ -1,10 +1,41 @@
1
1
  /**
2
- * Vite build invocation for `scenar pack`.
2
+ * Vite loading + build invocation for `scenar pack`.
3
3
  *
4
4
  * Vite is loaded dynamically (like the Remotion bundler in `render`) so the CLI
5
5
  * stays lean for users who only validate/narrate. The build runs in "app mode"
6
6
  * from a generated index.html, producing a self-contained static bundle.
7
+ *
8
+ * The load and the resolution config are exported separately because pack has
9
+ * a second Vite consumer: `collect-pack-shots` SSR-loads the scenario's steps
10
+ * module to record its shot names in scenario.json. Both consumers must
11
+ * resolve the scenario's imports identically — what pack records may never
12
+ * disagree with what the bundle contains — so they share one config source.
13
+ */
14
+ /** The subset of Vite's module surface pack uses (typed loosely — Vite is an optional peer). */
15
+ interface ViteModule {
16
+ build: (config: Record<string, unknown>) => Promise<unknown>;
17
+ createServer: (config: Record<string, unknown>) => Promise<ViteDevServer>;
18
+ }
19
+ /** The subset of Vite's dev-server surface `collect-pack-shots` uses. */
20
+ export interface ViteDevServer {
21
+ ssrLoadModule: (specifier: string) => Promise<Record<string, unknown>>;
22
+ close: () => Promise<void>;
23
+ }
24
+ /** Vite + the React plugin factory, dynamically loaded as a pair. */
25
+ export interface ViteToolkit {
26
+ readonly vite: ViteModule;
27
+ readonly react: (...args: unknown[]) => unknown;
28
+ }
29
+ /**
30
+ * Dynamically import Vite and @vitejs/plugin-react, with an actionable
31
+ * install hint when either is missing (they are optional peers).
32
+ */
33
+ export declare function loadViteToolkit(): Promise<ViteToolkit>;
34
+ /**
35
+ * The resolution config shared by the pack build and the pack-time steps
36
+ * load — the parts that decide WHICH files an import specifier reaches.
7
37
  */
38
+ export declare function sharedViteConfig(toolkit: ViteToolkit): Record<string, unknown>;
8
39
  export interface ViteBuildInput {
9
40
  /** Build root — the temp dir holding index.html + the generated entry. */
10
41
  root: string;
@@ -14,4 +45,5 @@ export interface ViteBuildInput {
14
45
  entryHtmlPath: string;
15
46
  }
16
47
  export declare function runViteBuild(input: ViteBuildInput): Promise<void>;
48
+ export {};
17
49
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/pack/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAyCvE"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/pack/build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,gGAAgG;AAChG,UAAU,UAAU;IAClB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;CAC3E;AAED,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;CACjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CAsB5D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ9E;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBvE"}
package/src/pack/build.js CHANGED
@@ -1,29 +1,54 @@
1
1
  /**
2
- * Vite build invocation for `scenar pack`.
2
+ * Vite loading + build invocation for `scenar pack`.
3
3
  *
4
4
  * Vite is loaded dynamically (like the Remotion bundler in `render`) so the CLI
5
5
  * stays lean for users who only validate/narrate. The build runs in "app mode"
6
6
  * from a generated index.html, producing a self-contained static bundle.
7
+ *
8
+ * The load and the resolution config are exported separately because pack has
9
+ * a second Vite consumer: `collect-pack-shots` SSR-loads the scenario's steps
10
+ * module to record its shot names in scenario.json. Both consumers must
11
+ * resolve the scenario's imports identically — what pack records may never
12
+ * disagree with what the bundle contains — so they share one config source.
7
13
  */
8
- export async function runViteBuild(input) {
9
- const vite = await import("vite").catch(() => {
14
+ /**
15
+ * Dynamically import Vite and @vitejs/plugin-react, with an actionable
16
+ * install hint when either is missing (they are optional peers).
17
+ */
18
+ export async function loadViteToolkit() {
19
+ const vite = await import("vite").catch((error) => {
10
20
  throw new Error("Could not load vite.\n" +
11
21
  "scenar pack bundles the embed with Vite. Install it:\n" +
12
- " pnpm add -D vite @vitejs/plugin-react");
22
+ " pnpm add -D vite @vitejs/plugin-react\n" +
23
+ `(import failed with: ${error instanceof Error ? error.message : String(error)})`);
13
24
  });
14
- const reactPluginMod = await import("@vitejs/plugin-react").catch(() => {
25
+ const reactPluginMod = await import("@vitejs/plugin-react").catch((error) => {
15
26
  throw new Error("Could not load @vitejs/plugin-react.\n" +
16
- "Install it: pnpm add -D vite @vitejs/plugin-react");
27
+ "Install it: pnpm add -D vite @vitejs/plugin-react\n" +
28
+ `(import failed with: ${error instanceof Error ? error.message : String(error)})`);
17
29
  });
18
30
  const react = (reactPluginMod.default ?? reactPluginMod);
19
- await vite.build({
20
- root: input.root,
21
- base: "./", // relative asset URLs: the bundle is served from a per-deploy origin root.
31
+ return { vite: vite, react };
32
+ }
33
+ /**
34
+ * The resolution config shared by the pack build and the pack-time steps
35
+ * load — the parts that decide WHICH files an import specifier reaches.
36
+ */
37
+ export function sharedViteConfig(toolkit) {
38
+ return {
22
39
  configFile: false, // never pick up a vite.config.* from the consumer project.
23
40
  logLevel: "warn",
24
- plugins: [react()],
41
+ plugins: [toolkit.react()],
25
42
  // A single React copy — the scenario, @scenar/react, and react-dom must agree.
26
43
  resolve: { dedupe: ["react", "react-dom"] },
44
+ };
45
+ }
46
+ export async function runViteBuild(input) {
47
+ const toolkit = await loadViteToolkit();
48
+ await toolkit.vite.build({
49
+ ...sharedViteConfig(toolkit),
50
+ root: input.root,
51
+ base: "./", // relative asset URLs: the bundle is served from a per-deploy origin root.
27
52
  build: {
28
53
  outDir: input.outDir,
29
54
  emptyOutDir: true,
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/pack/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAqB;IACtD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QAC3C,MAAM,IAAI,KAAK,CACb,wBAAwB;YACtB,wDAAwD;YACxD,yCAAyC,CAC5C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QACrE,MAAM,IAAI,KAAK,CACb,wCAAwC;YACtC,mDAAmD,CACtD,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,CAAC,cAAc,CAAC,OAAO,IAAI,cAAc,CAE3C,CAAC;IAEb,MAAO,IAAI,CAAC,KAA+D,CAAC;QAC1E,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,IAAI,EAAE,2EAA2E;QACvF,UAAU,EAAE,KAAK,EAAE,2DAA2D;QAC9E,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC;QAClB,+EAA+E;QAC/E,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE;QAC3C,KAAK,EAAE;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,KAAK,EAAE,iDAAiD;YACtE,SAAS,EAAE,KAAK,EAAE,uCAAuC;YACzD,mFAAmF;YACnF,qFAAqF;YACrF,gFAAgF;YAChF,QAAQ,EAAE,KAAK,EAAE,wDAAwD;YACzE,gFAAgF;YAChF,aAAa,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;YAClC,aAAa,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,EAAE;SAC9C;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/pack/build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAoBH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACzD,MAAM,IAAI,KAAK,CACb,wBAAwB;YACtB,wDAAwD;YACxD,2CAA2C;YAC3C,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CACpF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACnF,MAAM,IAAI,KAAK,CACb,wCAAwC;YACtC,qDAAqD;YACrD,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CACpF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,CAAC,cAAc,CAAC,OAAO,IAAI,cAAc,CAE3C,CAAC;IAEb,OAAO,EAAE,IAAI,EAAE,IAA6B,EAAE,KAAK,EAAE,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAoB;IACnD,OAAO;QACL,UAAU,EAAE,KAAK,EAAE,2DAA2D;QAC9E,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC1B,+EAA+E;QAC/E,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE;KAC5C,CAAC;AACJ,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAqB;IACtD,MAAM,OAAO,GAAG,MAAM,eAAe,EAAE,CAAC;IAExC,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QACvB,GAAG,gBAAgB,CAAC,OAAO,CAAC;QAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,IAAI,EAAE,2EAA2E;QACvF,KAAK,EAAE;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,KAAK,EAAE,iDAAiD;YACtE,SAAS,EAAE,KAAK,EAAE,uCAAuC;YACzD,mFAAmF;YACnF,qFAAqF;YACrF,gFAAgF;YAChF,QAAQ,EAAE,KAAK,EAAE,wDAAwD;YACzE,gFAAgF;YAChF,aAAa,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;YAClC,aAAa,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,EAAE;SAC9C;KACF,CAAC,CAAC;AACL,CAAC"}
package/src/pack/build.ts CHANGED
@@ -1,47 +1,93 @@
1
1
  /**
2
- * Vite build invocation for `scenar pack`.
2
+ * Vite loading + build invocation for `scenar pack`.
3
3
  *
4
4
  * Vite is loaded dynamically (like the Remotion bundler in `render`) so the CLI
5
5
  * stays lean for users who only validate/narrate. The build runs in "app mode"
6
6
  * from a generated index.html, producing a self-contained static bundle.
7
+ *
8
+ * The load and the resolution config are exported separately because pack has
9
+ * a second Vite consumer: `collect-pack-shots` SSR-loads the scenario's steps
10
+ * module to record its shot names in scenario.json. Both consumers must
11
+ * resolve the scenario's imports identically — what pack records may never
12
+ * disagree with what the bundle contains — so they share one config source.
7
13
  */
8
14
 
9
- export interface ViteBuildInput {
10
- /** Build root — the temp dir holding index.html + the generated entry. */
11
- root: string;
12
- /** Absolute output directory for the static bundle. */
13
- outDir: string;
14
- /** Absolute path to the generated index.html (the Rollup input). */
15
- entryHtmlPath: string;
15
+ /** The subset of Vite's module surface pack uses (typed loosely — Vite is an optional peer). */
16
+ interface ViteModule {
17
+ build: (config: Record<string, unknown>) => Promise<unknown>;
18
+ createServer: (config: Record<string, unknown>) => Promise<ViteDevServer>;
16
19
  }
17
20
 
18
- export async function runViteBuild(input: ViteBuildInput): Promise<void> {
19
- const vite = await import("vite").catch(() => {
21
+ /** The subset of Vite's dev-server surface `collect-pack-shots` uses. */
22
+ export interface ViteDevServer {
23
+ ssrLoadModule: (specifier: string) => Promise<Record<string, unknown>>;
24
+ close: () => Promise<void>;
25
+ }
26
+
27
+ /** Vite + the React plugin factory, dynamically loaded as a pair. */
28
+ export interface ViteToolkit {
29
+ readonly vite: ViteModule;
30
+ readonly react: (...args: unknown[]) => unknown;
31
+ }
32
+
33
+ /**
34
+ * Dynamically import Vite and @vitejs/plugin-react, with an actionable
35
+ * install hint when either is missing (they are optional peers).
36
+ */
37
+ export async function loadViteToolkit(): Promise<ViteToolkit> {
38
+ const vite = await import("vite").catch((error: unknown) => {
20
39
  throw new Error(
21
40
  "Could not load vite.\n" +
22
41
  "scenar pack bundles the embed with Vite. Install it:\n" +
23
- " pnpm add -D vite @vitejs/plugin-react",
42
+ " pnpm add -D vite @vitejs/plugin-react\n" +
43
+ `(import failed with: ${error instanceof Error ? error.message : String(error)})`,
24
44
  );
25
45
  });
26
46
 
27
- const reactPluginMod = await import("@vitejs/plugin-react").catch(() => {
47
+ const reactPluginMod = await import("@vitejs/plugin-react").catch((error: unknown) => {
28
48
  throw new Error(
29
49
  "Could not load @vitejs/plugin-react.\n" +
30
- "Install it: pnpm add -D vite @vitejs/plugin-react",
50
+ "Install it: pnpm add -D vite @vitejs/plugin-react\n" +
51
+ `(import failed with: ${error instanceof Error ? error.message : String(error)})`,
31
52
  );
32
53
  });
33
54
  const react = (reactPluginMod.default ?? reactPluginMod) as (
34
55
  ...args: unknown[]
35
56
  ) => unknown;
36
57
 
37
- await (vite.build as (config: Record<string, unknown>) => Promise<unknown>)({
38
- root: input.root,
39
- base: "./", // relative asset URLs: the bundle is served from a per-deploy origin root.
58
+ return { vite: vite as unknown as ViteModule, react };
59
+ }
60
+
61
+ /**
62
+ * The resolution config shared by the pack build and the pack-time steps
63
+ * load — the parts that decide WHICH files an import specifier reaches.
64
+ */
65
+ export function sharedViteConfig(toolkit: ViteToolkit): Record<string, unknown> {
66
+ return {
40
67
  configFile: false, // never pick up a vite.config.* from the consumer project.
41
68
  logLevel: "warn",
42
- plugins: [react()],
69
+ plugins: [toolkit.react()],
43
70
  // A single React copy — the scenario, @scenar/react, and react-dom must agree.
44
71
  resolve: { dedupe: ["react", "react-dom"] },
72
+ };
73
+ }
74
+
75
+ export interface ViteBuildInput {
76
+ /** Build root — the temp dir holding index.html + the generated entry. */
77
+ root: string;
78
+ /** Absolute output directory for the static bundle. */
79
+ outDir: string;
80
+ /** Absolute path to the generated index.html (the Rollup input). */
81
+ entryHtmlPath: string;
82
+ }
83
+
84
+ export async function runViteBuild(input: ViteBuildInput): Promise<void> {
85
+ const toolkit = await loadViteToolkit();
86
+
87
+ await toolkit.vite.build({
88
+ ...sharedViteConfig(toolkit),
89
+ root: input.root,
90
+ base: "./", // relative asset URLs: the bundle is served from a per-deploy origin root.
45
91
  build: {
46
92
  outDir: input.outDir,
47
93
  emptyOutDir: true,
@@ -0,0 +1,47 @@
1
+ import { type AuthoredViewport } from "../util/load-ts.js";
2
+ /**
3
+ * The outcome of pack-time shot discovery.
4
+ *
5
+ * `recorded: true` is authoritative — `shots` is exactly what the packed
6
+ * capture page will report (possibly empty, which lets `scenar shoot` skip
7
+ * the browser entirely), and `authoredViewport` is the canonical viewport
8
+ * the scenario authors for itself (null = it authors none). `recorded:
9
+ * false` means the steps module could not be loaded under Node, so the
10
+ * shots are unknown and scenario.json must omit the key: absent = "boot a
11
+ * browser to find out".
12
+ */
13
+ export type CollectedPackShots = {
14
+ readonly recorded: true;
15
+ readonly shots: readonly string[];
16
+ readonly authoredViewport: AuthoredViewport | null;
17
+ } | {
18
+ readonly recorded: false;
19
+ readonly reason: string;
20
+ };
21
+ /**
22
+ * Discover a scenario's declared shot names — and its authored canonical
23
+ * viewport, if it declares one — at pack time, so the names can be recorded
24
+ * in the bundle's scenario.json and the viewport can participate in the
25
+ * packer's resolution (CLI flags > authored > default). One SSR load serves
26
+ * both discoveries; the viewport costs nothing extra.
27
+ *
28
+ * The steps module is loaded through Vite's SSR pipeline using the exact
29
+ * specifier the generated browser entry imports ({@link stepsModuleSpecifier})
30
+ * and the same resolution config as the pack build ({@link sharedViteConfig}) —
31
+ * so what pack records and what the bundle contains agree by construction.
32
+ * This is runtime truth, not source-text approximation: a shot name built
33
+ * from a constant is discovered just like a literal one.
34
+ *
35
+ * Failure semantics split by who is at fault:
36
+ * - *Environmental* — the module fails to import under Node's SSR runtime
37
+ * (e.g. it touches `window` at import time). Returns `recorded: false`
38
+ * with the reason; the caller packs on without a `shots` record. Anything
39
+ * that packs today still packs.
40
+ * - *Authoring* — the module imports but is broken: no `delayMs`-bearing
41
+ * steps array (the packed embed would throw the same error in the browser
42
+ * the moment it loads), or an invalid/duplicate shot name (the capture
43
+ * page would throw at shoot time). Throws, failing the pack — these
44
+ * bundles must not ship, and the error is most fixable right here.
45
+ */
46
+ export declare function collectPackShots(scenarioDir: string): Promise<CollectedPackShots>;
47
+ //# sourceMappingURL=collect-pack-shots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-pack-shots.d.ts","sourceRoot":"","sources":["../../../src/pack/collect-pack-shots.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,gBAAgB,EAAwC,MAAM,oBAAoB,CAAC;AAIjG;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IACE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACpD,GACD;IAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuCvF"}
@@ -0,0 +1,66 @@
1
+ import { collectShotNames } from "@scenar/core";
2
+ import { findAuthoredViewport, findStepsArray } from "../util/load-ts.js";
3
+ import { loadViteToolkit, sharedViteConfig } from "./build.js";
4
+ import { stepsModuleSpecifier } from "./generate-embed-entry.js";
5
+ /**
6
+ * Discover a scenario's declared shot names — and its authored canonical
7
+ * viewport, if it declares one — at pack time, so the names can be recorded
8
+ * in the bundle's scenario.json and the viewport can participate in the
9
+ * packer's resolution (CLI flags > authored > default). One SSR load serves
10
+ * both discoveries; the viewport costs nothing extra.
11
+ *
12
+ * The steps module is loaded through Vite's SSR pipeline using the exact
13
+ * specifier the generated browser entry imports ({@link stepsModuleSpecifier})
14
+ * and the same resolution config as the pack build ({@link sharedViteConfig}) —
15
+ * so what pack records and what the bundle contains agree by construction.
16
+ * This is runtime truth, not source-text approximation: a shot name built
17
+ * from a constant is discovered just like a literal one.
18
+ *
19
+ * Failure semantics split by who is at fault:
20
+ * - *Environmental* — the module fails to import under Node's SSR runtime
21
+ * (e.g. it touches `window` at import time). Returns `recorded: false`
22
+ * with the reason; the caller packs on without a `shots` record. Anything
23
+ * that packs today still packs.
24
+ * - *Authoring* — the module imports but is broken: no `delayMs`-bearing
25
+ * steps array (the packed embed would throw the same error in the browser
26
+ * the moment it loads), or an invalid/duplicate shot name (the capture
27
+ * page would throw at shoot time). Throws, failing the pack — these
28
+ * bundles must not ship, and the error is most fixable right here.
29
+ */
30
+ export async function collectPackShots(scenarioDir) {
31
+ const specifier = stepsModuleSpecifier(scenarioDir);
32
+ const toolkit = await loadViteToolkit();
33
+ const server = await toolkit.vite.createServer({
34
+ ...sharedViteConfig(toolkit),
35
+ root: scenarioDir,
36
+ // A transform-only server: no HTTP listener, no HMR websocket, and no
37
+ // dependency pre-bundling scan — ssrLoadModule is the only consumer.
38
+ server: { middlewareMode: true, hmr: false },
39
+ optimizeDeps: { noDiscovery: true },
40
+ });
41
+ let stepsModule;
42
+ try {
43
+ stepsModule = await server.ssrLoadModule(specifier);
44
+ }
45
+ catch (error) {
46
+ return {
47
+ recorded: false,
48
+ reason: error instanceof Error ? error.message : String(error),
49
+ };
50
+ }
51
+ finally {
52
+ await server.close();
53
+ }
54
+ const steps = findStepsArray(stepsModule);
55
+ if (steps === null) {
56
+ throw new Error(`No steps array found in ${specifier} — expected an exported array whose ` +
57
+ `first element has a delayMs property. The packed embed would throw this ` +
58
+ `same error in the browser the moment it loads, so pack refuses to ship it.`);
59
+ }
60
+ return {
61
+ recorded: true,
62
+ shots: collectShotNames(steps),
63
+ authoredViewport: findAuthoredViewport(stepsModule),
64
+ };
65
+ }
66
+ //# sourceMappingURL=collect-pack-shots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-pack-shots.js","sourceRoot":"","sources":["../../../src/pack/collect-pack-shots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAyB,oBAAoB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAqBjE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,WAAmB;IACxD,MAAM,SAAS,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAEpD,MAAM,OAAO,GAAG,MAAM,eAAe,EAAE,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;QAC7C,GAAG,gBAAgB,CAAC,OAAO,CAAC;QAC5B,IAAI,EAAE,WAAW;QACjB,sEAAsE;QACtE,qEAAqE;QACrE,MAAM,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE;QAC5C,YAAY,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;KACpC,CAAC,CAAC;IAEH,IAAI,WAAoC,CAAC;IACzC,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC/D,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,2BAA2B,SAAS,sCAAsC;YACxE,0EAA0E;YAC1E,4EAA4E,CAC/E,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC;QAC9B,gBAAgB,EAAE,oBAAoB,CAAC,WAAW,CAAC;KACpD,CAAC;AACJ,CAAC"}