@scenar/cli 0.1.12 → 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 +5 -5
- package/src/__tests__/render-command.test.ts +1 -0
- package/src/commands/render.d.ts.map +1 -1
- package/src/commands/render.js +109 -53
- package/src/commands/render.js.map +1 -1
- package/src/commands/render.ts +152 -51
- package/src/render/detect-render-export.d.ts +20 -0
- package/src/render/detect-render-export.d.ts.map +1 -0
- package/src/render/detect-render-export.js +62 -0
- package/src/render/detect-render-export.js.map +1 -0
- package/src/render/detect-render-export.ts +68 -0
- package/src/render/generate-entry.d.ts +41 -0
- package/src/render/generate-entry.d.ts.map +1 -0
- package/src/render/generate-entry.js +111 -0
- package/src/render/generate-entry.js.map +1 -0
- package/src/render/generate-entry.ts +155 -0
- package/src/render/resolve-providers.d.ts +9 -0
- package/src/render/resolve-providers.d.ts.map +1 -0
- package/src/render/resolve-providers.js +32 -0
- package/src/render/resolve-providers.js.map +1 -0
- package/src/render/resolve-providers.ts +36 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** File names checked in priority order for the renderStep export. */
|
|
4
|
+
const RENDER_FILE_CANDIDATES = ["render.tsx", "render.ts", "index.tsx", "index.ts"];
|
|
5
|
+
/**
|
|
6
|
+
* Locate the file that exports `renderStep` in the scenario directory.
|
|
7
|
+
*
|
|
8
|
+
* Checks `render.tsx` first (recommended — a lean file that only imports
|
|
9
|
+
* view components), then falls back to `index.tsx`. Using a dedicated
|
|
10
|
+
* `render.tsx` avoids pulling browser-only dependencies (preview
|
|
11
|
+
* runtime, MSW, etc.) into the Remotion webpack bundle.
|
|
12
|
+
*
|
|
13
|
+
* Uses a lightweight source-text scan rather than a dynamic import.
|
|
14
|
+
* The actual module resolution and type checking happens later when
|
|
15
|
+
* Remotion's webpack bundler compiles the generated entry — which has
|
|
16
|
+
* the full project dependency graph available. Importing the file
|
|
17
|
+
* here at CLI time would pull in React, MUI, and other view-layer
|
|
18
|
+
* dependencies that are not available in the Node CLI context.
|
|
19
|
+
*
|
|
20
|
+
* Returns the absolute path to the file containing renderStep.
|
|
21
|
+
* Throws with a descriptive message if no suitable file is found.
|
|
22
|
+
*/
|
|
23
|
+
export async function detectRenderExport(scenarioDir) {
|
|
24
|
+
for (const filename of RENDER_FILE_CANDIDATES) {
|
|
25
|
+
const candidate = join(scenarioDir, filename);
|
|
26
|
+
try {
|
|
27
|
+
await access(candidate);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const source = await readFile(candidate, "utf-8");
|
|
33
|
+
if (hasRenderStepExport(source)) {
|
|
34
|
+
return candidate;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`No renderStep export found in ${scenarioDir}.\n\n` +
|
|
38
|
+
"The render command expects the scenario directory to contain a\n" +
|
|
39
|
+
"render.tsx (or index.tsx) that exports a renderStep function:\n\n" +
|
|
40
|
+
" // render.tsx (recommended — keeps video export deps minimal)\n" +
|
|
41
|
+
" export function renderStep(data: YourStepType, stepIndex: number): ReactNode {\n" +
|
|
42
|
+
" // map step data to view elements\n" +
|
|
43
|
+
" }\n\n" +
|
|
44
|
+
"Using a dedicated render.tsx avoids pulling browser-only preview\n" +
|
|
45
|
+
"dependencies into the Remotion webpack bundle.\n\n" +
|
|
46
|
+
"Alternatively, use --entry to provide a custom Remotion entry point.");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Heuristic check for `renderStep` being exported from the source.
|
|
50
|
+
*
|
|
51
|
+
* Matches patterns like:
|
|
52
|
+
* export function renderStep(
|
|
53
|
+
* export const renderStep =
|
|
54
|
+
* export { renderStep }
|
|
55
|
+
* export { renderStep as renderStep }
|
|
56
|
+
* export { foo as renderStep }
|
|
57
|
+
*/
|
|
58
|
+
function hasRenderStepExport(source) {
|
|
59
|
+
return /export\s+(function|const|let|var)\s+renderStep[\s(<]/.test(source) ||
|
|
60
|
+
/export\s*\{[^}]*\brenderStep\b[^}]*\}/.test(source);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=detect-render-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-render-export.js","sourceRoot":"","sources":["../../../src/render/detect-render-export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,sEAAsE;AACtE,MAAM,sBAAsB,GAAG,CAAC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,CAAU,CAAC;AAE7F;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,WAAmB;IAC1D,KAAK,MAAM,QAAQ,IAAI,sBAAsB,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QAE9C,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAClD,IAAI,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,iCAAiC,WAAW,OAAO;QACnD,kEAAkE;QAClE,mEAAmE;QACnE,mEAAmE;QACnE,oFAAoF;QACpF,yCAAyC;QACzC,SAAS;QACT,oEAAoE;QACpE,oDAAoD;QACpD,sEAAsE,CACvE,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,mBAAmB,CAAC,MAAc;IACzC,OAAO,sDAAsD,CAAC,IAAI,CAAC,MAAM,CAAC;QACxE,uCAAuC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** File names checked in priority order for the renderStep export. */
|
|
5
|
+
const RENDER_FILE_CANDIDATES = ["render.tsx", "render.ts", "index.tsx", "index.ts"] as const;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Locate the file that exports `renderStep` in the scenario directory.
|
|
9
|
+
*
|
|
10
|
+
* Checks `render.tsx` first (recommended — a lean file that only imports
|
|
11
|
+
* view components), then falls back to `index.tsx`. Using a dedicated
|
|
12
|
+
* `render.tsx` avoids pulling browser-only dependencies (preview
|
|
13
|
+
* runtime, MSW, etc.) into the Remotion webpack bundle.
|
|
14
|
+
*
|
|
15
|
+
* Uses a lightweight source-text scan rather than a dynamic import.
|
|
16
|
+
* The actual module resolution and type checking happens later when
|
|
17
|
+
* Remotion's webpack bundler compiles the generated entry — which has
|
|
18
|
+
* the full project dependency graph available. Importing the file
|
|
19
|
+
* here at CLI time would pull in React, MUI, and other view-layer
|
|
20
|
+
* dependencies that are not available in the Node CLI context.
|
|
21
|
+
*
|
|
22
|
+
* Returns the absolute path to the file containing renderStep.
|
|
23
|
+
* Throws with a descriptive message if no suitable file is found.
|
|
24
|
+
*/
|
|
25
|
+
export async function detectRenderExport(scenarioDir: string): Promise<string> {
|
|
26
|
+
for (const filename of RENDER_FILE_CANDIDATES) {
|
|
27
|
+
const candidate = join(scenarioDir, filename);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
await access(candidate);
|
|
31
|
+
} catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const source = await readFile(candidate, "utf-8");
|
|
36
|
+
if (hasRenderStepExport(source)) {
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
throw new Error(
|
|
42
|
+
`No renderStep export found in ${scenarioDir}.\n\n` +
|
|
43
|
+
"The render command expects the scenario directory to contain a\n" +
|
|
44
|
+
"render.tsx (or index.tsx) that exports a renderStep function:\n\n" +
|
|
45
|
+
" // render.tsx (recommended — keeps video export deps minimal)\n" +
|
|
46
|
+
" export function renderStep(data: YourStepType, stepIndex: number): ReactNode {\n" +
|
|
47
|
+
" // map step data to view elements\n" +
|
|
48
|
+
" }\n\n" +
|
|
49
|
+
"Using a dedicated render.tsx avoids pulling browser-only preview\n" +
|
|
50
|
+
"dependencies into the Remotion webpack bundle.\n\n" +
|
|
51
|
+
"Alternatively, use --entry to provide a custom Remotion entry point.",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Heuristic check for `renderStep` being exported from the source.
|
|
57
|
+
*
|
|
58
|
+
* Matches patterns like:
|
|
59
|
+
* export function renderStep(
|
|
60
|
+
* export const renderStep =
|
|
61
|
+
* export { renderStep }
|
|
62
|
+
* export { renderStep as renderStep }
|
|
63
|
+
* export { foo as renderStep }
|
|
64
|
+
*/
|
|
65
|
+
function hasRenderStepExport(source: string): boolean {
|
|
66
|
+
return /export\s+(function|const|let|var)\s+renderStep[\s(<]/.test(source) ||
|
|
67
|
+
/export\s*\{[^}]*\brenderStep\b[^}]*\}/.test(source);
|
|
68
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inputs required to generate a Remotion entry file for a scenario.
|
|
3
|
+
*
|
|
4
|
+
* All paths are absolute filesystem paths. The generator converts them
|
|
5
|
+
* to posix-style forward-slash paths suitable for webpack imports.
|
|
6
|
+
*/
|
|
7
|
+
export interface EntryGeneratorInput {
|
|
8
|
+
/** Absolute path to the scenario directory (e.g. /proj/scenarios/my-tour). */
|
|
9
|
+
scenarioDir: string;
|
|
10
|
+
/** Absolute path to the file that exports renderStep. */
|
|
11
|
+
renderFilePath: string;
|
|
12
|
+
/** Scenario ID derived from the directory basename. */
|
|
13
|
+
scenarioId: string;
|
|
14
|
+
/** Whether the scenario has a narration manifest. */
|
|
15
|
+
hasNarration: boolean;
|
|
16
|
+
/** Absolute path to the providers file, or null if none found. */
|
|
17
|
+
providersPath: string | null;
|
|
18
|
+
fps: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
compositionId: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Produce the TypeScript source for a Remotion entry file that renders
|
|
25
|
+
* a single scenario.
|
|
26
|
+
*
|
|
27
|
+
* The output is a self-contained `index.tsx` that can be passed to
|
|
28
|
+
* `@remotion/bundler.bundle({ entryPoint })`. All imports use absolute
|
|
29
|
+
* paths so the file works from any temp directory.
|
|
30
|
+
*
|
|
31
|
+
* The steps file may export the steps array under any name (e.g.
|
|
32
|
+
* `awsConnectionTourSteps`). Rather than guessing the export name,
|
|
33
|
+
* the generated entry uses a namespace import and duck-types the array
|
|
34
|
+
* at module-evaluation time — the same heuristic the CLI's own
|
|
35
|
+
* `loadStepsFromTs` uses.
|
|
36
|
+
*
|
|
37
|
+
* This is a pure function with no side effects — callers write the
|
|
38
|
+
* result to disk and manage cleanup.
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateRemotionEntry(input: EntryGeneratorInput): string;
|
|
41
|
+
//# sourceMappingURL=generate-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-entry.d.ts","sourceRoot":"","sources":["../../../src/render/generate-entry.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,YAAY,EAAE,OAAO,CAAC;IACtB,kEAAkE;IAClE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CA2GxE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Produce the TypeScript source for a Remotion entry file that renders
|
|
4
|
+
* a single scenario.
|
|
5
|
+
*
|
|
6
|
+
* The output is a self-contained `index.tsx` that can be passed to
|
|
7
|
+
* `@remotion/bundler.bundle({ entryPoint })`. All imports use absolute
|
|
8
|
+
* paths so the file works from any temp directory.
|
|
9
|
+
*
|
|
10
|
+
* The steps file may export the steps array under any name (e.g.
|
|
11
|
+
* `awsConnectionTourSteps`). Rather than guessing the export name,
|
|
12
|
+
* the generated entry uses a namespace import and duck-types the array
|
|
13
|
+
* at module-evaluation time — the same heuristic the CLI's own
|
|
14
|
+
* `loadStepsFromTs` uses.
|
|
15
|
+
*
|
|
16
|
+
* This is a pure function with no side effects — callers write the
|
|
17
|
+
* result to disk and manage cleanup.
|
|
18
|
+
*/
|
|
19
|
+
export function generateRemotionEntry(input) {
|
|
20
|
+
const scenarioPath = toPosix(input.scenarioDir);
|
|
21
|
+
const stepsImport = `${scenarioPath}/steps`;
|
|
22
|
+
const renderImport = toPosix(input.renderFilePath).replace(/\.[^.]+$/, "");
|
|
23
|
+
const lines = [];
|
|
24
|
+
// --- Imports ---
|
|
25
|
+
lines.push(`import { registerRoot } from "remotion";`);
|
|
26
|
+
lines.push(`import { Composition, AbsoluteFill } from "remotion";`);
|
|
27
|
+
lines.push(`import { ScenarioComposition, calculateScenarioTimeline } from "@scenar/remotion";`);
|
|
28
|
+
lines.push(`import { renderStep } from ${JSON.stringify(renderImport)};`);
|
|
29
|
+
lines.push(`import * as _stepsModule from ${JSON.stringify(stepsImport)};`);
|
|
30
|
+
if (input.hasNarration) {
|
|
31
|
+
const manifestPath = `${scenarioPath}/narration/manifest.json`;
|
|
32
|
+
lines.push(`import _manifest from ${JSON.stringify(manifestPath)};`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
lines.push(`const _manifest = undefined;`);
|
|
36
|
+
}
|
|
37
|
+
if (input.providersPath) {
|
|
38
|
+
const providersImport = toPosix(input.providersPath).replace(/\.[^.]+$/, "");
|
|
39
|
+
lines.push(`import { PreviewProviders as _Providers } from ${JSON.stringify(providersImport)};`);
|
|
40
|
+
}
|
|
41
|
+
// --- Steps resolution (duck-type the first array with delayMs) ---
|
|
42
|
+
lines.push(``);
|
|
43
|
+
lines.push(`function _findSteps(mod: Record<string, unknown>): unknown[] {`);
|
|
44
|
+
lines.push(` for (const val of Object.values(mod)) {`);
|
|
45
|
+
lines.push(` if (`);
|
|
46
|
+
lines.push(` Array.isArray(val) &&`);
|
|
47
|
+
lines.push(` val.length > 0 &&`);
|
|
48
|
+
lines.push(` typeof val[0] === "object" &&`);
|
|
49
|
+
lines.push(` val[0] !== null &&`);
|
|
50
|
+
lines.push(` "delayMs" in val[0]`);
|
|
51
|
+
lines.push(` ) return val;`);
|
|
52
|
+
lines.push(` }`);
|
|
53
|
+
lines.push(` throw new Error("No steps array found in ${stepsImport.replace(/"/g, '\\"')}");`);
|
|
54
|
+
lines.push(`}`);
|
|
55
|
+
lines.push(``);
|
|
56
|
+
lines.push(`const _steps: any = _findSteps(_stepsModule as unknown as Record<string, unknown>);`);
|
|
57
|
+
// --- Bundle + timeline ---
|
|
58
|
+
lines.push(``);
|
|
59
|
+
lines.push(`const _bundle = {`);
|
|
60
|
+
lines.push(` id: ${JSON.stringify(input.scenarioId)},`);
|
|
61
|
+
lines.push(` steps: _steps,`);
|
|
62
|
+
lines.push(` narrationManifest: _manifest,`);
|
|
63
|
+
lines.push(`};`);
|
|
64
|
+
lines.push(``);
|
|
65
|
+
lines.push(`const _timeline = calculateScenarioTimeline(_bundle.steps, _bundle.narrationManifest, ${input.fps});`);
|
|
66
|
+
// --- Video root component ---
|
|
67
|
+
lines.push(``);
|
|
68
|
+
lines.push(`function _VideoRoot() {`);
|
|
69
|
+
if (input.providersPath) {
|
|
70
|
+
lines.push(` return (`);
|
|
71
|
+
lines.push(` <AbsoluteFill>`);
|
|
72
|
+
lines.push(` <_Providers>`);
|
|
73
|
+
lines.push(` <ScenarioComposition bundle={_bundle}>`);
|
|
74
|
+
lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
|
|
75
|
+
lines.push(` </ScenarioComposition>`);
|
|
76
|
+
lines.push(` </_Providers>`);
|
|
77
|
+
lines.push(` </AbsoluteFill>`);
|
|
78
|
+
lines.push(` );`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
lines.push(` return (`);
|
|
82
|
+
lines.push(` <AbsoluteFill>`);
|
|
83
|
+
lines.push(` <ScenarioComposition bundle={_bundle}>`);
|
|
84
|
+
lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
|
|
85
|
+
lines.push(` </ScenarioComposition>`);
|
|
86
|
+
lines.push(` </AbsoluteFill>`);
|
|
87
|
+
lines.push(` );`);
|
|
88
|
+
}
|
|
89
|
+
lines.push(`}`);
|
|
90
|
+
// --- Composition registration ---
|
|
91
|
+
lines.push(``);
|
|
92
|
+
lines.push(`const _RemotionRoot = () => (`);
|
|
93
|
+
lines.push(` <Composition`);
|
|
94
|
+
lines.push(` id=${JSON.stringify(input.compositionId)}`);
|
|
95
|
+
lines.push(` component={_VideoRoot}`);
|
|
96
|
+
lines.push(` fps={${input.fps}}`);
|
|
97
|
+
lines.push(` width={${input.width}}`);
|
|
98
|
+
lines.push(` height={${input.height}}`);
|
|
99
|
+
lines.push(` durationInFrames={_timeline.durationInFrames}`);
|
|
100
|
+
lines.push(` />`);
|
|
101
|
+
lines.push(`);`);
|
|
102
|
+
lines.push(``);
|
|
103
|
+
lines.push(`registerRoot(_RemotionRoot);`);
|
|
104
|
+
lines.push(``);
|
|
105
|
+
return lines.join("\n");
|
|
106
|
+
}
|
|
107
|
+
/** Convert a filesystem path to posix separators (for webpack imports). */
|
|
108
|
+
function toPosix(fsPath) {
|
|
109
|
+
return fsPath.split(posix.sep === "/" ? /\\/ : /[\\/]/).join("/");
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=generate-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-entry.js","sourceRoot":"","sources":["../../../src/render/generate-entry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Inputs required to generate a Remotion entry file for a scenario.
|
|
5
|
+
*
|
|
6
|
+
* All paths are absolute filesystem paths. The generator converts them
|
|
7
|
+
* to posix-style forward-slash paths suitable for webpack imports.
|
|
8
|
+
*/
|
|
9
|
+
export interface EntryGeneratorInput {
|
|
10
|
+
/** Absolute path to the scenario directory (e.g. /proj/scenarios/my-tour). */
|
|
11
|
+
scenarioDir: string;
|
|
12
|
+
/** Absolute path to the file that exports renderStep. */
|
|
13
|
+
renderFilePath: string;
|
|
14
|
+
/** Scenario ID derived from the directory basename. */
|
|
15
|
+
scenarioId: string;
|
|
16
|
+
/** Whether the scenario has a narration manifest. */
|
|
17
|
+
hasNarration: boolean;
|
|
18
|
+
/** Absolute path to the providers file, or null if none found. */
|
|
19
|
+
providersPath: string | null;
|
|
20
|
+
fps: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
compositionId: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Produce the TypeScript source for a Remotion entry file that renders
|
|
28
|
+
* a single scenario.
|
|
29
|
+
*
|
|
30
|
+
* The output is a self-contained `index.tsx` that can be passed to
|
|
31
|
+
* `@remotion/bundler.bundle({ entryPoint })`. All imports use absolute
|
|
32
|
+
* paths so the file works from any temp directory.
|
|
33
|
+
*
|
|
34
|
+
* The steps file may export the steps array under any name (e.g.
|
|
35
|
+
* `awsConnectionTourSteps`). Rather than guessing the export name,
|
|
36
|
+
* the generated entry uses a namespace import and duck-types the array
|
|
37
|
+
* at module-evaluation time — the same heuristic the CLI's own
|
|
38
|
+
* `loadStepsFromTs` uses.
|
|
39
|
+
*
|
|
40
|
+
* This is a pure function with no side effects — callers write the
|
|
41
|
+
* result to disk and manage cleanup.
|
|
42
|
+
*/
|
|
43
|
+
export function generateRemotionEntry(input: EntryGeneratorInput): string {
|
|
44
|
+
const scenarioPath = toPosix(input.scenarioDir);
|
|
45
|
+
const stepsImport = `${scenarioPath}/steps`;
|
|
46
|
+
const renderImport = toPosix(input.renderFilePath).replace(/\.[^.]+$/, "");
|
|
47
|
+
|
|
48
|
+
const lines: string[] = [];
|
|
49
|
+
|
|
50
|
+
// --- Imports ---
|
|
51
|
+
|
|
52
|
+
lines.push(`import { registerRoot } from "remotion";`);
|
|
53
|
+
lines.push(`import { Composition, AbsoluteFill } from "remotion";`);
|
|
54
|
+
lines.push(
|
|
55
|
+
`import { ScenarioComposition, calculateScenarioTimeline } from "@scenar/remotion";`,
|
|
56
|
+
);
|
|
57
|
+
lines.push(`import { renderStep } from ${JSON.stringify(renderImport)};`);
|
|
58
|
+
lines.push(`import * as _stepsModule from ${JSON.stringify(stepsImport)};`);
|
|
59
|
+
|
|
60
|
+
if (input.hasNarration) {
|
|
61
|
+
const manifestPath = `${scenarioPath}/narration/manifest.json`;
|
|
62
|
+
lines.push(`import _manifest from ${JSON.stringify(manifestPath)};`);
|
|
63
|
+
} else {
|
|
64
|
+
lines.push(`const _manifest = undefined;`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (input.providersPath) {
|
|
68
|
+
const providersImport = toPosix(input.providersPath).replace(/\.[^.]+$/, "");
|
|
69
|
+
lines.push(
|
|
70
|
+
`import { PreviewProviders as _Providers } from ${JSON.stringify(providersImport)};`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- Steps resolution (duck-type the first array with delayMs) ---
|
|
75
|
+
|
|
76
|
+
lines.push(``);
|
|
77
|
+
lines.push(`function _findSteps(mod: Record<string, unknown>): unknown[] {`);
|
|
78
|
+
lines.push(` for (const val of Object.values(mod)) {`);
|
|
79
|
+
lines.push(` if (`);
|
|
80
|
+
lines.push(` Array.isArray(val) &&`);
|
|
81
|
+
lines.push(` val.length > 0 &&`);
|
|
82
|
+
lines.push(` typeof val[0] === "object" &&`);
|
|
83
|
+
lines.push(` val[0] !== null &&`);
|
|
84
|
+
lines.push(` "delayMs" in val[0]`);
|
|
85
|
+
lines.push(` ) return val;`);
|
|
86
|
+
lines.push(` }`);
|
|
87
|
+
lines.push(` throw new Error("No steps array found in ${stepsImport.replace(/"/g, '\\"')}");`);
|
|
88
|
+
lines.push(`}`);
|
|
89
|
+
lines.push(``);
|
|
90
|
+
lines.push(`const _steps: any = _findSteps(_stepsModule as unknown as Record<string, unknown>);`);
|
|
91
|
+
|
|
92
|
+
// --- Bundle + timeline ---
|
|
93
|
+
|
|
94
|
+
lines.push(``);
|
|
95
|
+
lines.push(`const _bundle = {`);
|
|
96
|
+
lines.push(` id: ${JSON.stringify(input.scenarioId)},`);
|
|
97
|
+
lines.push(` steps: _steps,`);
|
|
98
|
+
lines.push(` narrationManifest: _manifest,`);
|
|
99
|
+
lines.push(`};`);
|
|
100
|
+
lines.push(``);
|
|
101
|
+
lines.push(
|
|
102
|
+
`const _timeline = calculateScenarioTimeline(_bundle.steps, _bundle.narrationManifest, ${input.fps});`,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// --- Video root component ---
|
|
106
|
+
|
|
107
|
+
lines.push(``);
|
|
108
|
+
lines.push(`function _VideoRoot() {`);
|
|
109
|
+
|
|
110
|
+
if (input.providersPath) {
|
|
111
|
+
lines.push(` return (`);
|
|
112
|
+
lines.push(` <AbsoluteFill>`);
|
|
113
|
+
lines.push(` <_Providers>`);
|
|
114
|
+
lines.push(` <ScenarioComposition bundle={_bundle}>`);
|
|
115
|
+
lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
|
|
116
|
+
lines.push(` </ScenarioComposition>`);
|
|
117
|
+
lines.push(` </_Providers>`);
|
|
118
|
+
lines.push(` </AbsoluteFill>`);
|
|
119
|
+
lines.push(` );`);
|
|
120
|
+
} else {
|
|
121
|
+
lines.push(` return (`);
|
|
122
|
+
lines.push(` <AbsoluteFill>`);
|
|
123
|
+
lines.push(` <ScenarioComposition bundle={_bundle}>`);
|
|
124
|
+
lines.push(` {(data: any, stepIndex: number) => renderStep(data, stepIndex)}`);
|
|
125
|
+
lines.push(` </ScenarioComposition>`);
|
|
126
|
+
lines.push(` </AbsoluteFill>`);
|
|
127
|
+
lines.push(` );`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
lines.push(`}`);
|
|
131
|
+
|
|
132
|
+
// --- Composition registration ---
|
|
133
|
+
|
|
134
|
+
lines.push(``);
|
|
135
|
+
lines.push(`const _RemotionRoot = () => (`);
|
|
136
|
+
lines.push(` <Composition`);
|
|
137
|
+
lines.push(` id=${JSON.stringify(input.compositionId)}`);
|
|
138
|
+
lines.push(` component={_VideoRoot}`);
|
|
139
|
+
lines.push(` fps={${input.fps}}`);
|
|
140
|
+
lines.push(` width={${input.width}}`);
|
|
141
|
+
lines.push(` height={${input.height}}`);
|
|
142
|
+
lines.push(` durationInFrames={_timeline.durationInFrames}`);
|
|
143
|
+
lines.push(` />`);
|
|
144
|
+
lines.push(`);`);
|
|
145
|
+
lines.push(``);
|
|
146
|
+
lines.push(`registerRoot(_RemotionRoot);`);
|
|
147
|
+
lines.push(``);
|
|
148
|
+
|
|
149
|
+
return lines.join("\n");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Convert a filesystem path to posix separators (for webpack imports). */
|
|
153
|
+
function toPosix(fsPath: string): string {
|
|
154
|
+
return fsPath.split(posix.sep === "/" ? /\\/ : /[\\/]/).join("/");
|
|
155
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk up from `startDir` looking for `.scenar/providers.tsx`.
|
|
3
|
+
*
|
|
4
|
+
* Returns the absolute path if found, or `null` if the filesystem
|
|
5
|
+
* root is reached without finding it. Stops at the filesystem root
|
|
6
|
+
* to avoid scanning outside the project.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveProvidersPath(startDir: string): Promise<string | null>;
|
|
9
|
+
//# sourceMappingURL=resolve-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-providers.d.ts","sourceRoot":"","sources":["../../../src/render/resolve-providers.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
const PROVIDERS_FILENAME = "providers.tsx";
|
|
4
|
+
const SCENAR_DIR = ".scenar";
|
|
5
|
+
/**
|
|
6
|
+
* Walk up from `startDir` looking for `.scenar/providers.tsx`.
|
|
7
|
+
*
|
|
8
|
+
* Returns the absolute path if found, or `null` if the filesystem
|
|
9
|
+
* root is reached without finding it. Stops at the filesystem root
|
|
10
|
+
* to avoid scanning outside the project.
|
|
11
|
+
*/
|
|
12
|
+
export async function resolveProvidersPath(startDir) {
|
|
13
|
+
let current = resolve(startDir);
|
|
14
|
+
// eslint-disable-next-line no-constant-condition
|
|
15
|
+
while (true) {
|
|
16
|
+
const candidate = join(current, SCENAR_DIR, PROVIDERS_FILENAME);
|
|
17
|
+
try {
|
|
18
|
+
await access(candidate);
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Not found at this level — move up.
|
|
23
|
+
}
|
|
24
|
+
const parent = dirname(current);
|
|
25
|
+
if (parent === current) {
|
|
26
|
+
// Reached filesystem root.
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
current = parent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=resolve-providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-providers.js","sourceRoot":"","sources":["../../../src/render/resolve-providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAC3C,MAAM,UAAU,GAAG,SAAS,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAgB;IAEhB,IAAI,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEhC,iDAAiD;IACjD,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC;QAChE,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,qCAAqC;QACvC,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,2BAA2B;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
const PROVIDERS_FILENAME = "providers.tsx";
|
|
5
|
+
const SCENAR_DIR = ".scenar";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Walk up from `startDir` looking for `.scenar/providers.tsx`.
|
|
9
|
+
*
|
|
10
|
+
* Returns the absolute path if found, or `null` if the filesystem
|
|
11
|
+
* root is reached without finding it. Stops at the filesystem root
|
|
12
|
+
* to avoid scanning outside the project.
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveProvidersPath(
|
|
15
|
+
startDir: string,
|
|
16
|
+
): Promise<string | null> {
|
|
17
|
+
let current = resolve(startDir);
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line no-constant-condition
|
|
20
|
+
while (true) {
|
|
21
|
+
const candidate = join(current, SCENAR_DIR, PROVIDERS_FILENAME);
|
|
22
|
+
try {
|
|
23
|
+
await access(candidate);
|
|
24
|
+
return candidate;
|
|
25
|
+
} catch {
|
|
26
|
+
// Not found at this level — move up.
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const parent = dirname(current);
|
|
30
|
+
if (parent === current) {
|
|
31
|
+
// Reached filesystem root.
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
current = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|