@scenar/cli 0.1.11 → 0.1.13

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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@scenar/cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Scenar CLI — validate scenarios, generate narration, and render videos.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/scenar-ai/scenar.git",
9
+ "url": "https://github.com/stigmer/scenar.git",
10
10
  "directory": "packages/cli"
11
11
  },
12
12
  "keywords": [
@@ -27,9 +27,9 @@
27
27
  "main": "./src/index.js",
28
28
  "types": "./src/index.d.ts",
29
29
  "dependencies": {
30
- "@scenar/core": "0.1.11",
31
- "@scenar/preview": "0.1.11",
32
- "@scenar/sdk": "0.1.11",
30
+ "@scenar/core": "0.1.13",
31
+ "@scenar/preview": "0.1.13",
32
+ "@scenar/sdk": "0.1.13",
33
33
  "commander": "^13.0.0",
34
34
  "yaml": "^2.7.0"
35
35
  },
@@ -41,6 +41,7 @@ describe("scenar render", () => {
41
41
  expect(optionNames).toContain("--height");
42
42
  expect(optionNames).toContain("--entry");
43
43
  expect(optionNames).toContain("--composition-id");
44
+ expect(optionNames).toContain("--webpack-override");
44
45
  });
45
46
 
46
47
  it("defaults fps to 30, width to 1920, height to 1080", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/commands/render.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0E5D"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/commands/render.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8G5D"}
@@ -1,12 +1,19 @@
1
1
  import { resolve, join } from "node:path";
2
- import { stat, mkdir, access } from "node:fs/promises";
2
+ import { stat, mkdir, access, writeFile, rm } from "node:fs/promises";
3
+ import { pathToFileURL } from "node:url";
3
4
  import { loadBundle } from "../util/load-bundle.js";
5
+ import { generateRemotionEntry } from "../render/generate-entry.js";
6
+ import { resolveProvidersPath } from "../render/resolve-providers.js";
7
+ import { detectRenderExport } from "../render/detect-render-export.js";
4
8
  export function registerRenderCommand(program) {
5
9
  program
6
10
  .command("render")
7
11
  .description("Render a scenario as an MP4 video using Remotion.\n\n" +
8
12
  "Accepts a scenario directory containing steps.ts and an optional\n" +
9
13
  "narration/ subfolder with manifest.json + audio clips.\n\n" +
14
+ "When the scenario directory contains an index.tsx that exports a\n" +
15
+ "renderStep function, the CLI auto-generates the Remotion entry\n" +
16
+ "point — no remotion/ directory or bundle.ts needed.\n\n" +
10
17
  "Output defaults to ./<scenario-id>.mp4 in the current working\n" +
11
18
  "directory. Use --out to write to a different path.")
12
19
  .argument("<dir>", "path to a scenario directory (must contain steps.ts)")
@@ -15,7 +22,8 @@ export function registerRenderCommand(program) {
15
22
  .option("--width <number>", "video width in pixels (default: 1920)", "1920")
16
23
  .option("--height <number>", "video height in pixels (default: 1080)", "1080")
17
24
  .option("--composition-id <id>", "Remotion composition ID to render")
18
- .option("--entry <path>", "path to a Remotion entry file (Root.tsx)")
25
+ .option("--entry <path>", "path to a custom Remotion entry file")
26
+ .option("--webpack-override <path>", "path to a module that default-exports a Remotion WebpackOverrideFn")
19
27
  .action(async (dir, options) => {
20
28
  const resolved = resolve(dir);
21
29
  let info;
@@ -36,25 +44,47 @@ export function registerRenderCommand(program) {
36
44
  const fps = Number(options.fps) || 30;
37
45
  const width = Number(options.width) || 1920;
38
46
  const height = Number(options.height) || 1080;
47
+ let tempDir;
39
48
  try {
40
49
  const bundle = await loadBundle(resolved);
41
50
  const scenarioId = bundle.id;
51
+ const compositionId = options.compositionId ?? scenarioId;
42
52
  const outputPath = resolveOutputPath(options.out, scenarioId);
43
- const entryPoint = await resolveEntryPoint(options.entry);
53
+ const { entryPoint, generated } = await resolveEntryPoint({
54
+ entryOption: options.entry,
55
+ scenarioDir: resolved,
56
+ scenarioId,
57
+ compositionId,
58
+ hasNarration: !!bundle.narrationManifest,
59
+ fps,
60
+ width,
61
+ height,
62
+ });
63
+ tempDir = generated?.tempDir;
64
+ const webpackOverride = options.webpackOverride
65
+ ? await loadWebpackOverride(options.webpackOverride)
66
+ : undefined;
44
67
  process.stderr.write(`Scenario: ${scenarioId}\n`);
45
68
  process.stderr.write(`Steps: ${bundle.steps.length}\n`);
46
69
  process.stderr.write(`Audio: ${bundle.narrationManifest ? "yes (manifest found)" : "none"}\n`);
47
- process.stderr.write(`Entry: ${entryPoint}\n`);
70
+ process.stderr.write(`Entry: ${generated ? "(auto-generated)" : entryPoint}\n`);
71
+ if (generated?.providersPath) {
72
+ process.stderr.write(`Providers: ${generated.providersPath}\n`);
73
+ }
74
+ if (webpackOverride) {
75
+ process.stderr.write(`Webpack: custom override loaded\n`);
76
+ }
48
77
  process.stderr.write(`Output: ${outputPath}\n`);
49
78
  process.stderr.write(`Config: ${width}x${height} @ ${fps}fps\n\n`);
50
79
  await renderScenario({
51
80
  bundle,
52
81
  entryPoint,
53
- compositionId: options.compositionId ?? scenarioId,
82
+ compositionId,
54
83
  outputPath,
55
84
  fps,
56
85
  width,
57
86
  height,
87
+ webpackOverride,
58
88
  });
59
89
  process.stderr.write(`\n\x1b[32m✓\x1b[0m Video saved to ${outputPath}\n`);
60
90
  }
@@ -63,6 +93,11 @@ export function registerRenderCommand(program) {
63
93
  process.stderr.write(`\x1b[31mError:\x1b[0m ${msg}\n`);
64
94
  process.exitCode = 1;
65
95
  }
96
+ finally {
97
+ if (tempDir) {
98
+ await rm(tempDir, { recursive: true, force: true }).catch(() => { });
99
+ }
100
+ }
66
101
  });
67
102
  }
68
103
  // ---------------------------------------------------------------------------
@@ -77,62 +112,77 @@ function resolveOutputPath(outOption, scenarioId) {
77
112
  }
78
113
  return resolve(join(outOption, `${scenarioId}.mp4`));
79
114
  }
80
- // ---------------------------------------------------------------------------
81
- // Entry point resolution
82
- // ---------------------------------------------------------------------------
83
- async function resolveEntryPoint(entryOption) {
84
- if (entryOption) {
85
- const resolved = resolve(entryOption);
115
+ async function resolveEntryPoint(input) {
116
+ // Explicit --entry: use as-is, no generation.
117
+ if (input.entryOption) {
118
+ const resolved = resolve(input.entryOption);
86
119
  try {
87
120
  await access(resolved);
88
121
  }
89
122
  catch {
90
- throw new Error(`Remotion entry point not found: ${entryOption}`);
123
+ throw new Error(`Remotion entry point not found: ${input.entryOption}`);
91
124
  }
92
- return resolved;
125
+ return { entryPoint: resolved };
93
126
  }
94
- const candidates = [
95
- resolve("remotion/index.tsx"),
96
- resolve("remotion/index.ts"),
97
- resolve("src/remotion/index.tsx"),
98
- resolve("src/remotion/index.ts"),
99
- ];
100
- for (const candidate of candidates) {
101
- try {
102
- await access(candidate);
103
- return candidate;
104
- }
105
- catch {
106
- // Try next.
107
- }
127
+ // Auto-generate: detect renderStep export, find providers, write temp entry.
128
+ const renderFilePath = await detectRenderExport(input.scenarioDir);
129
+ const providersPath = await resolveProvidersPath(input.scenarioDir);
130
+ const entrySource = generateRemotionEntry({
131
+ scenarioDir: input.scenarioDir,
132
+ renderFilePath,
133
+ scenarioId: input.scenarioId,
134
+ hasNarration: input.hasNarration,
135
+ providersPath,
136
+ fps: input.fps,
137
+ width: input.width,
138
+ height: input.height,
139
+ compositionId: input.compositionId,
140
+ });
141
+ // Write the entry inside the scenario directory so webpack's standard
142
+ // node_modules resolution walks up from here into the consumer project.
143
+ // A /tmp/ location would fail because no node_modules exist there.
144
+ const tempDir = join(input.scenarioDir, ".scenar-render");
145
+ await mkdir(tempDir, { recursive: true });
146
+ const entryPath = join(tempDir, "index.tsx");
147
+ await writeFile(entryPath, entrySource, "utf-8");
148
+ return {
149
+ entryPoint: entryPath,
150
+ generated: { tempDir, providersPath },
151
+ };
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // Webpack override loading
155
+ // ---------------------------------------------------------------------------
156
+ async function loadWebpackOverride(overridePath) {
157
+ const resolved = resolve(overridePath);
158
+ try {
159
+ await access(resolved);
108
160
  }
109
- throw new Error("No Remotion entry point found.\n\n" +
110
- "Create a remotion/index.tsx that exports your Remotion Root component,\n" +
111
- "or use --entry to specify a custom path.\n\n" +
112
- "Example remotion/index.tsx:\n\n" +
113
- ' import { Composition } from "remotion";\n' +
114
- ' import { ScenarioComposition, calculateScenarioTimeline } from "@scenar/remotion";\n' +
115
- " import { myBundle } from \"../scenarios/my-demo/bundle\";\n\n" +
116
- " export const RemotionRoot = () => (\n" +
117
- " <Composition\n" +
118
- ' id="my-demo"\n' +
119
- " component={() => (\n" +
120
- " <ScenarioComposition bundle={myBundle}>\n" +
121
- " {(data) => <MyView data={data} />}\n" +
122
- " </ScenarioComposition>\n" +
123
- " )}\n" +
124
- " fps={30}\n" +
125
- " width={1920}\n" +
126
- " height={1080}\n" +
127
- " durationInFrames={\n" +
128
- " calculateScenarioTimeline(myBundle.steps, myBundle.narrationManifest, 30)\n" +
129
- " .durationInFrames\n" +
130
- " }\n" +
131
- " />\n" +
132
- " );\n");
161
+ catch {
162
+ throw new Error(`Webpack override file not found: ${overridePath}`);
163
+ }
164
+ let mod;
165
+ try {
166
+ mod = await import(pathToFileURL(resolved).href);
167
+ }
168
+ catch (err) {
169
+ const detail = err instanceof Error ? err.message : String(err);
170
+ throw new Error(`Failed to import webpack override from ${overridePath}:\n${detail}`);
171
+ }
172
+ const override = (mod.default ?? mod.webpackOverride);
173
+ if (typeof override !== "function") {
174
+ throw new Error(`${overridePath} does not export a webpack override function.\n\n` +
175
+ "Expected a default export or named 'webpackOverride' export:\n\n" +
176
+ ' import type { WebpackOverrideFn } from "@remotion/bundler";\n\n' +
177
+ " const webpackOverride: WebpackOverrideFn = (config) => ({\n" +
178
+ " ...config,\n" +
179
+ " // your overrides\n" +
180
+ " });\n\n" +
181
+ " export default webpackOverride;\n");
182
+ }
183
+ return override;
133
184
  }
134
185
  async function renderScenario(config) {
135
- // Dynamic imports — these are optional peer deps.
136
186
  const bundler = await import("@remotion/bundler").catch(() => {
137
187
  throw new Error("Could not load @remotion/bundler.\n" +
138
188
  "Install it: pnpm add @remotion/bundler@4.0.448");
@@ -142,7 +192,13 @@ async function renderScenario(config) {
142
192
  "Install it: pnpm add @remotion/renderer@4.0.448");
143
193
  });
144
194
  process.stderr.write("Bundling Remotion project...\n");
145
- const serveUrl = await bundler.bundle({ entryPoint: config.entryPoint });
195
+ const bundleOptions = {
196
+ entryPoint: config.entryPoint,
197
+ };
198
+ if (config.webpackOverride) {
199
+ bundleOptions.webpackOverride = config.webpackOverride;
200
+ }
201
+ const serveUrl = await bundler.bundle(bundleOptions);
146
202
  process.stderr.write(`Selecting composition: ${config.compositionId}\n`);
147
203
  const composition = await renderer.selectComposition({
148
204
  serveUrl,
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/commands/render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/commands/render.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,11 @@
1
1
  import { resolve, join } from "node:path";
2
- import { stat, mkdir, access } from "node:fs/promises";
2
+ import { stat, mkdir, access, writeFile, rm } from "node:fs/promises";
3
+ import { pathToFileURL } from "node:url";
3
4
  import { Command } from "commander";
4
5
  import { loadBundle, type CliBundle } from "../util/load-bundle.js";
6
+ import { generateRemotionEntry } from "../render/generate-entry.js";
7
+ import { resolveProvidersPath } from "../render/resolve-providers.js";
8
+ import { detectRenderExport } from "../render/detect-render-export.js";
5
9
 
6
10
  interface RenderOptions {
7
11
  out?: string;
@@ -10,6 +14,7 @@ interface RenderOptions {
10
14
  height?: string;
11
15
  compositionId?: string;
12
16
  entry?: string;
17
+ webpackOverride?: string;
13
18
  }
14
19
 
15
20
  export function registerRenderCommand(program: Command): void {
@@ -19,6 +24,9 @@ export function registerRenderCommand(program: Command): void {
19
24
  "Render a scenario as an MP4 video using Remotion.\n\n" +
20
25
  "Accepts a scenario directory containing steps.ts and an optional\n" +
21
26
  "narration/ subfolder with manifest.json + audio clips.\n\n" +
27
+ "When the scenario directory contains an index.tsx that exports a\n" +
28
+ "renderStep function, the CLI auto-generates the Remotion entry\n" +
29
+ "point — no remotion/ directory or bundle.ts needed.\n\n" +
22
30
  "Output defaults to ./<scenario-id>.mp4 in the current working\n" +
23
31
  "directory. Use --out to write to a different path.",
24
32
  )
@@ -28,7 +36,11 @@ export function registerRenderCommand(program: Command): void {
28
36
  .option("--width <number>", "video width in pixels (default: 1920)", "1920")
29
37
  .option("--height <number>", "video height in pixels (default: 1080)", "1080")
30
38
  .option("--composition-id <id>", "Remotion composition ID to render")
31
- .option("--entry <path>", "path to a Remotion entry file (Root.tsx)")
39
+ .option("--entry <path>", "path to a custom Remotion entry file")
40
+ .option(
41
+ "--webpack-override <path>",
42
+ "path to a module that default-exports a Remotion WebpackOverrideFn",
43
+ )
32
44
  .action(async (dir: string, options: RenderOptions) => {
33
45
  const resolved = resolve(dir);
34
46
 
@@ -54,29 +66,54 @@ export function registerRenderCommand(program: Command): void {
54
66
  const width = Number(options.width) || 1920;
55
67
  const height = Number(options.height) || 1080;
56
68
 
69
+ let tempDir: string | undefined;
70
+
57
71
  try {
58
72
  const bundle = await loadBundle(resolved);
59
73
  const scenarioId = bundle.id;
74
+ const compositionId = options.compositionId ?? scenarioId;
60
75
  const outputPath = resolveOutputPath(options.out, scenarioId);
61
- const entryPoint = await resolveEntryPoint(options.entry);
76
+
77
+ const { entryPoint, generated } = await resolveEntryPoint({
78
+ entryOption: options.entry,
79
+ scenarioDir: resolved,
80
+ scenarioId,
81
+ compositionId,
82
+ hasNarration: !!bundle.narrationManifest,
83
+ fps,
84
+ width,
85
+ height,
86
+ });
87
+ tempDir = generated?.tempDir;
88
+
89
+ const webpackOverride = options.webpackOverride
90
+ ? await loadWebpackOverride(options.webpackOverride)
91
+ : undefined;
62
92
 
63
93
  process.stderr.write(`Scenario: ${scenarioId}\n`);
64
94
  process.stderr.write(`Steps: ${bundle.steps.length}\n`);
65
95
  process.stderr.write(
66
96
  `Audio: ${bundle.narrationManifest ? "yes (manifest found)" : "none"}\n`,
67
97
  );
68
- process.stderr.write(`Entry: ${entryPoint}\n`);
98
+ process.stderr.write(`Entry: ${generated ? "(auto-generated)" : entryPoint}\n`);
99
+ if (generated?.providersPath) {
100
+ process.stderr.write(`Providers: ${generated.providersPath}\n`);
101
+ }
102
+ if (webpackOverride) {
103
+ process.stderr.write(`Webpack: custom override loaded\n`);
104
+ }
69
105
  process.stderr.write(`Output: ${outputPath}\n`);
70
106
  process.stderr.write(`Config: ${width}x${height} @ ${fps}fps\n\n`);
71
107
 
72
108
  await renderScenario({
73
109
  bundle,
74
110
  entryPoint,
75
- compositionId: options.compositionId ?? scenarioId,
111
+ compositionId,
76
112
  outputPath,
77
113
  fps,
78
114
  width,
79
115
  height,
116
+ webpackOverride,
80
117
  });
81
118
 
82
119
  process.stderr.write(`\n\x1b[32m✓\x1b[0m Video saved to ${outputPath}\n`);
@@ -84,6 +121,10 @@ export function registerRenderCommand(program: Command): void {
84
121
  const msg = error instanceof Error ? error.message : String(error);
85
122
  process.stderr.write(`\x1b[31mError:\x1b[0m ${msg}\n`);
86
123
  process.exitCode = 1;
124
+ } finally {
125
+ if (tempDir) {
126
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {});
127
+ }
87
128
  }
88
129
  });
89
130
  }
@@ -106,59 +147,110 @@ function resolveOutputPath(outOption: string | undefined, scenarioId: string): s
106
147
  // Entry point resolution
107
148
  // ---------------------------------------------------------------------------
108
149
 
109
- async function resolveEntryPoint(entryOption: string | undefined): Promise<string> {
110
- if (entryOption) {
111
- const resolved = resolve(entryOption);
150
+ interface EntryResolutionInput {
151
+ entryOption: string | undefined;
152
+ scenarioDir: string;
153
+ scenarioId: string;
154
+ compositionId: string;
155
+ hasNarration: boolean;
156
+ fps: number;
157
+ width: number;
158
+ height: number;
159
+ }
160
+
161
+ interface EntryResolutionResult {
162
+ entryPoint: string;
163
+ generated?: {
164
+ tempDir: string;
165
+ providersPath: string | null;
166
+ };
167
+ }
168
+
169
+ async function resolveEntryPoint(
170
+ input: EntryResolutionInput,
171
+ ): Promise<EntryResolutionResult> {
172
+ // Explicit --entry: use as-is, no generation.
173
+ if (input.entryOption) {
174
+ const resolved = resolve(input.entryOption);
112
175
  try {
113
176
  await access(resolved);
114
177
  } catch {
115
- throw new Error(`Remotion entry point not found: ${entryOption}`);
178
+ throw new Error(`Remotion entry point not found: ${input.entryOption}`);
116
179
  }
117
- return resolved;
180
+ return { entryPoint: resolved };
118
181
  }
119
182
 
120
- const candidates = [
121
- resolve("remotion/index.tsx"),
122
- resolve("remotion/index.ts"),
123
- resolve("src/remotion/index.tsx"),
124
- resolve("src/remotion/index.ts"),
125
- ];
183
+ // Auto-generate: detect renderStep export, find providers, write temp entry.
184
+ const renderFilePath = await detectRenderExport(input.scenarioDir);
185
+ const providersPath = await resolveProvidersPath(input.scenarioDir);
126
186
 
127
- for (const candidate of candidates) {
128
- try {
129
- await access(candidate);
130
- return candidate;
131
- } catch {
132
- // Try next.
133
- }
187
+ const entrySource = generateRemotionEntry({
188
+ scenarioDir: input.scenarioDir,
189
+ renderFilePath,
190
+ scenarioId: input.scenarioId,
191
+ hasNarration: input.hasNarration,
192
+ providersPath,
193
+ fps: input.fps,
194
+ width: input.width,
195
+ height: input.height,
196
+ compositionId: input.compositionId,
197
+ });
198
+
199
+ // Write the entry inside the scenario directory so webpack's standard
200
+ // node_modules resolution walks up from here into the consumer project.
201
+ // A /tmp/ location would fail because no node_modules exist there.
202
+ const tempDir = join(input.scenarioDir, ".scenar-render");
203
+ await mkdir(tempDir, { recursive: true });
204
+
205
+ const entryPath = join(tempDir, "index.tsx");
206
+ await writeFile(entryPath, entrySource, "utf-8");
207
+
208
+ return {
209
+ entryPoint: entryPath,
210
+ generated: { tempDir, providersPath },
211
+ };
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Webpack override loading
216
+ // ---------------------------------------------------------------------------
217
+
218
+ async function loadWebpackOverride(
219
+ overridePath: string,
220
+ ): Promise<(config: unknown) => unknown> {
221
+ const resolved = resolve(overridePath);
222
+
223
+ try {
224
+ await access(resolved);
225
+ } catch {
226
+ throw new Error(`Webpack override file not found: ${overridePath}`);
134
227
  }
135
228
 
136
- throw new Error(
137
- "No Remotion entry point found.\n\n" +
138
- "Create a remotion/index.tsx that exports your Remotion Root component,\n" +
139
- "or use --entry to specify a custom path.\n\n" +
140
- "Example remotion/index.tsx:\n\n" +
141
- ' import { Composition } from "remotion";\n' +
142
- ' import { ScenarioComposition, calculateScenarioTimeline } from "@scenar/remotion";\n' +
143
- " import { myBundle } from \"../scenarios/my-demo/bundle\";\n\n" +
144
- " export const RemotionRoot = () => (\n" +
145
- " <Composition\n" +
146
- ' id="my-demo"\n' +
147
- " component={() => (\n" +
148
- " <ScenarioComposition bundle={myBundle}>\n" +
149
- " {(data) => <MyView data={data} />}\n" +
150
- " </ScenarioComposition>\n" +
151
- " )}\n" +
152
- " fps={30}\n" +
153
- " width={1920}\n" +
154
- " height={1080}\n" +
155
- " durationInFrames={\n" +
156
- " calculateScenarioTimeline(myBundle.steps, myBundle.narrationManifest, 30)\n" +
157
- " .durationInFrames\n" +
158
- " }\n" +
159
- " />\n" +
160
- " );\n",
161
- );
229
+ let mod: Record<string, unknown>;
230
+ try {
231
+ mod = await import(pathToFileURL(resolved).href) as Record<string, unknown>;
232
+ } catch (err) {
233
+ const detail = err instanceof Error ? err.message : String(err);
234
+ throw new Error(
235
+ `Failed to import webpack override from ${overridePath}:\n${detail}`,
236
+ );
237
+ }
238
+
239
+ const override = (mod.default ?? mod.webpackOverride) as unknown;
240
+ if (typeof override !== "function") {
241
+ throw new Error(
242
+ `${overridePath} does not export a webpack override function.\n\n` +
243
+ "Expected a default export or named 'webpackOverride' export:\n\n" +
244
+ ' import type { WebpackOverrideFn } from "@remotion/bundler";\n\n' +
245
+ " const webpackOverride: WebpackOverrideFn = (config) => ({\n" +
246
+ " ...config,\n" +
247
+ " // your overrides\n" +
248
+ " });\n\n" +
249
+ " export default webpackOverride;\n",
250
+ );
251
+ }
252
+
253
+ return override as (config: unknown) => unknown;
162
254
  }
163
255
 
164
256
  // ---------------------------------------------------------------------------
@@ -173,10 +265,10 @@ interface RenderConfig {
173
265
  fps: number;
174
266
  width: number;
175
267
  height: number;
268
+ webpackOverride?: (config: unknown) => unknown;
176
269
  }
177
270
 
178
271
  async function renderScenario(config: RenderConfig): Promise<void> {
179
- // Dynamic imports — these are optional peer deps.
180
272
  const bundler = await import("@remotion/bundler").catch(() => {
181
273
  throw new Error(
182
274
  "Could not load @remotion/bundler.\n" +
@@ -192,7 +284,16 @@ async function renderScenario(config: RenderConfig): Promise<void> {
192
284
  });
193
285
 
194
286
  process.stderr.write("Bundling Remotion project...\n");
195
- const serveUrl = await bundler.bundle({ entryPoint: config.entryPoint });
287
+
288
+ const bundleOptions: Record<string, unknown> = {
289
+ entryPoint: config.entryPoint,
290
+ };
291
+ if (config.webpackOverride) {
292
+ bundleOptions.webpackOverride = config.webpackOverride;
293
+ }
294
+ const serveUrl = await (bundler.bundle as (opts: Record<string, unknown>) => Promise<string>)(
295
+ bundleOptions,
296
+ );
196
297
 
197
298
  process.stderr.write(`Selecting composition: ${config.compositionId}\n`);
198
299
  const composition = await renderer.selectComposition({
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Locate the file that exports `renderStep` in the scenario directory.
3
+ *
4
+ * Checks `render.tsx` first (recommended — a lean file that only imports
5
+ * view components), then falls back to `index.tsx`. Using a dedicated
6
+ * `render.tsx` avoids pulling browser-only dependencies (preview
7
+ * runtime, MSW, etc.) into the Remotion webpack bundle.
8
+ *
9
+ * Uses a lightweight source-text scan rather than a dynamic import.
10
+ * The actual module resolution and type checking happens later when
11
+ * Remotion's webpack bundler compiles the generated entry — which has
12
+ * the full project dependency graph available. Importing the file
13
+ * here at CLI time would pull in React, MUI, and other view-layer
14
+ * dependencies that are not available in the Node CLI context.
15
+ *
16
+ * Returns the absolute path to the file containing renderStep.
17
+ * Throws with a descriptive message if no suitable file is found.
18
+ */
19
+ export declare function detectRenderExport(scenarioDir: string): Promise<string>;
20
+ //# sourceMappingURL=detect-render-export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-render-export.d.ts","sourceRoot":"","sources":["../../../src/render/detect-render-export.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4B7E"}