@norahe/remotion-workflow 0.1.0 → 0.1.2
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 +1 -1
- package/src/cli.mjs +188 -2
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* [INPUT]: process args, filesystem state, and optional create-video scaffolding.
|
|
4
4
|
* [OUTPUT]: initialized workflow guide files in an existing or newly created project.
|
|
5
|
-
* [POS]: CLI entrypoint for @
|
|
5
|
+
* [POS]: CLI entrypoint for @norahe/remotion-workflow package.
|
|
6
6
|
* [PROTOCOL]: update this header when code changes, then check AGENTS.md
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -41,6 +41,183 @@ const writeIfMissing = (filePath, content) => {
|
|
|
41
41
|
return true;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
const upsertPackageJsonDependency = (projectDir, depName, version) => {
|
|
45
|
+
const packagePath = path.join(projectDir, 'package.json');
|
|
46
|
+
if (!fs.existsSync(packagePath)) return false;
|
|
47
|
+
const raw = fs.readFileSync(packagePath, 'utf8');
|
|
48
|
+
const pkg = JSON.parse(raw);
|
|
49
|
+
const inDeps = Boolean(pkg.dependencies?.[depName]);
|
|
50
|
+
const inDevDeps = Boolean(pkg.devDependencies?.[depName]);
|
|
51
|
+
if (inDeps || inDevDeps) return false;
|
|
52
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
53
|
+
pkg.dependencies[depName] = version;
|
|
54
|
+
const sortedDeps = Object.keys(pkg.dependencies)
|
|
55
|
+
.sort((a, b) => a.localeCompare(b))
|
|
56
|
+
.reduce((acc, key) => {
|
|
57
|
+
acc[key] = pkg.dependencies[key];
|
|
58
|
+
return acc;
|
|
59
|
+
}, {});
|
|
60
|
+
pkg.dependencies = sortedDeps;
|
|
61
|
+
fs.writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const buildStarterSchemaTs = () => {
|
|
66
|
+
return [
|
|
67
|
+
'/**',
|
|
68
|
+
' * [INPUT]: zod runtime and Remotion props panel validation.',
|
|
69
|
+
' * [OUTPUT]: starter schema for visual/audio/timing controls.',
|
|
70
|
+
' * [POS]: Nora workflow starter schema for quick project bootstrapping.',
|
|
71
|
+
' * [PROTOCOL]: update this header when code changes, then check AGENTS.md',
|
|
72
|
+
' */',
|
|
73
|
+
'',
|
|
74
|
+
"import {z} from 'zod';",
|
|
75
|
+
'',
|
|
76
|
+
'export const starterSchema = z.object({',
|
|
77
|
+
' visual: z.object({',
|
|
78
|
+
' headingScalePct: z.number().int().min(80).max(130),',
|
|
79
|
+
' bodyScalePct: z.number().int().min(80).max(130),',
|
|
80
|
+
' }),',
|
|
81
|
+
' audio: z.object({',
|
|
82
|
+
' enableVoiceover: z.boolean(),',
|
|
83
|
+
' enableMusic: z.boolean(),',
|
|
84
|
+
' musicVolumePct: z.number().int().min(0).max(100),',
|
|
85
|
+
' voiceVolumePct: z.number().int().min(0).max(200),',
|
|
86
|
+
' }),',
|
|
87
|
+
' timing: z.object({',
|
|
88
|
+
' totalFrames: z.number().int().min(300).max(3600),',
|
|
89
|
+
' scene1Frames: z.number().int().min(60).max(1800),',
|
|
90
|
+
' }),',
|
|
91
|
+
'});',
|
|
92
|
+
'',
|
|
93
|
+
'export type StarterProps = z.infer<typeof starterSchema>;',
|
|
94
|
+
].join('\n');
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const buildStarterDefaultPropsTs = () => {
|
|
98
|
+
return [
|
|
99
|
+
'/**',
|
|
100
|
+
' * [INPUT]: StarterProps type from starter schema.',
|
|
101
|
+
' * [OUTPUT]: default props values for the starter composition.',
|
|
102
|
+
' * [POS]: single source of truth for initial right-panel values.',
|
|
103
|
+
' * [PROTOCOL]: update this header when code changes, then check AGENTS.md',
|
|
104
|
+
' */',
|
|
105
|
+
'',
|
|
106
|
+
"import type {StarterProps} from './schema';",
|
|
107
|
+
'',
|
|
108
|
+
'export const starterDefaultProps: StarterProps = {',
|
|
109
|
+
' visual: {',
|
|
110
|
+
' headingScalePct: 100,',
|
|
111
|
+
' bodyScalePct: 100,',
|
|
112
|
+
' },',
|
|
113
|
+
' audio: {',
|
|
114
|
+
' enableVoiceover: true,',
|
|
115
|
+
' enableMusic: true,',
|
|
116
|
+
' musicVolumePct: 20,',
|
|
117
|
+
' voiceVolumePct: 100,',
|
|
118
|
+
' },',
|
|
119
|
+
' timing: {',
|
|
120
|
+
' totalFrames: 1800,',
|
|
121
|
+
' scene1Frames: 360,',
|
|
122
|
+
' },',
|
|
123
|
+
'};',
|
|
124
|
+
].join('\n');
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const buildStarterCompositionTsx = () => {
|
|
128
|
+
return [
|
|
129
|
+
'/**',
|
|
130
|
+
' * [INPUT]: starter props from schema/defaultProps.',
|
|
131
|
+
' * [OUTPUT]: minimal, editable scene proving controls are wired.',
|
|
132
|
+
' * [POS]: first-touch composition for non-technical users in Studio.',
|
|
133
|
+
' * [PROTOCOL]: update this header when code changes, then check AGENTS.md',
|
|
134
|
+
' */',
|
|
135
|
+
'',
|
|
136
|
+
"import React from 'react';",
|
|
137
|
+
"import {AbsoluteFill, useCurrentFrame, interpolate} from 'remotion';",
|
|
138
|
+
"import type {StarterProps} from './schema';",
|
|
139
|
+
'',
|
|
140
|
+
'export const NoraWorkflowStarter: React.FC<StarterProps> = (props) => {',
|
|
141
|
+
' const frame = useCurrentFrame();',
|
|
142
|
+
' const fade = interpolate(frame, [0, 20], [0, 1], {extrapolateRight: "clamp"});',
|
|
143
|
+
' const headingSize = 78 * (props.visual.headingScalePct / 100);',
|
|
144
|
+
' const bodySize = 34 * (props.visual.bodyScalePct / 100);',
|
|
145
|
+
'',
|
|
146
|
+
' return (',
|
|
147
|
+
' <AbsoluteFill',
|
|
148
|
+
' style={{',
|
|
149
|
+
" background: 'radial-gradient(circle at 25% 20%, rgba(32,114,255,0.20), transparent 35%), #040c1d',",
|
|
150
|
+
" color: '#f4f7ff',",
|
|
151
|
+
" fontFamily: 'Inter, system-ui, sans-serif',",
|
|
152
|
+
" opacity: fade,",
|
|
153
|
+
" justifyContent: 'center',",
|
|
154
|
+
" alignItems: 'center',",
|
|
155
|
+
" textAlign: 'center',",
|
|
156
|
+
' }}',
|
|
157
|
+
' >',
|
|
158
|
+
' <div style={{padding: "0 120px"}}>',
|
|
159
|
+
' <h1 style={{fontSize: headingSize, margin: 0, lineHeight: 1.1}}>Nora Workflow Starter</h1>',
|
|
160
|
+
' <p style={{fontSize: bodySize, marginTop: 24, opacity: 0.88}}>',
|
|
161
|
+
' Edit props on the right panel: visual / audio / timing. This is your default schema.',
|
|
162
|
+
' </p>',
|
|
163
|
+
' </div>',
|
|
164
|
+
' </AbsoluteFill>',
|
|
165
|
+
' );',
|
|
166
|
+
'};',
|
|
167
|
+
].join('\n');
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const ensureStarterFiles = (projectDir) => {
|
|
171
|
+
const srcDir = path.join(projectDir, 'src');
|
|
172
|
+
const starterDir = path.join(srcDir, 'NoraWorkflow');
|
|
173
|
+
ensureDir(starterDir);
|
|
174
|
+
writeIfMissing(path.join(starterDir, 'schema.ts'), `${buildStarterSchemaTs()}\n`);
|
|
175
|
+
writeIfMissing(path.join(starterDir, 'defaultProps.ts'), `${buildStarterDefaultPropsTs()}\n`);
|
|
176
|
+
writeIfMissing(path.join(starterDir, 'StarterComposition.tsx'), `${buildStarterCompositionTsx()}\n`);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const ensureStarterInRoot = (projectDir) => {
|
|
180
|
+
const rootTsxPath = path.join(projectDir, 'src', 'Root.tsx');
|
|
181
|
+
if (!fs.existsSync(rootTsxPath)) return false;
|
|
182
|
+
const raw = fs.readFileSync(rootTsxPath, 'utf8');
|
|
183
|
+
if (raw.includes('NoraWorkflowStarter')) return false;
|
|
184
|
+
|
|
185
|
+
const importLines = [
|
|
186
|
+
"import {NoraWorkflowStarter} from './NoraWorkflow/StarterComposition';",
|
|
187
|
+
"import {starterSchema} from './NoraWorkflow/schema';",
|
|
188
|
+
"import {starterDefaultProps} from './NoraWorkflow/defaultProps';",
|
|
189
|
+
];
|
|
190
|
+
let next = raw;
|
|
191
|
+
|
|
192
|
+
for (const line of importLines) {
|
|
193
|
+
if (!next.includes(line)) {
|
|
194
|
+
next = `${line}\n${next}`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const compositionBlock = [
|
|
199
|
+
' <Composition',
|
|
200
|
+
' id="NoraWorkflow-Starter"',
|
|
201
|
+
' component={NoraWorkflowStarter}',
|
|
202
|
+
' durationInFrames={starterDefaultProps.timing.totalFrames}',
|
|
203
|
+
' fps={30}',
|
|
204
|
+
' width={1920}',
|
|
205
|
+
' height={1080}',
|
|
206
|
+
' schema={starterSchema}',
|
|
207
|
+
' defaultProps={starterDefaultProps}',
|
|
208
|
+
' />',
|
|
209
|
+
].join('\n');
|
|
210
|
+
|
|
211
|
+
if (next.includes('</>')) {
|
|
212
|
+
next = next.replace('</>', `${compositionBlock}\n </>`);
|
|
213
|
+
} else {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fs.writeFileSync(rootTsxPath, next, 'utf8');
|
|
218
|
+
return true;
|
|
219
|
+
};
|
|
220
|
+
|
|
44
221
|
const isRemotionProject = (cwd) => {
|
|
45
222
|
const packagePath = path.join(cwd, 'package.json');
|
|
46
223
|
const remotionConfigPathTs = path.join(cwd, 'remotion.config.ts');
|
|
@@ -80,7 +257,7 @@ const buildGuide = (projectName) => {
|
|
|
80
257
|
`Project: ${projectName}`,
|
|
81
258
|
'',
|
|
82
259
|
'1) Prompt selection',
|
|
83
|
-
'- Open prompt browser: https://
|
|
260
|
+
'- Open prompt browser: https://prompts-mauve.vercel.app/app/',
|
|
84
261
|
'- Copy either `Copy AI Template` (prompt-based) or `Copy Input Guide` (blank from scratch).',
|
|
85
262
|
'',
|
|
86
263
|
'2) Input location',
|
|
@@ -134,5 +311,14 @@ if (command !== 'init') {
|
|
|
134
311
|
const projectName = getArgValue('--project-name');
|
|
135
312
|
const cwd = process.cwd();
|
|
136
313
|
const projectDir = createProjectIfNeeded(cwd, projectName);
|
|
314
|
+
ensureStarterFiles(projectDir);
|
|
315
|
+
const rootUpdated = ensureStarterInRoot(projectDir);
|
|
316
|
+
const zodAdded = upsertPackageJsonDependency(projectDir, 'zod', '^3.25.76');
|
|
137
317
|
ensureWorkflowFiles(projectDir);
|
|
318
|
+
if (rootUpdated) {
|
|
319
|
+
console.log('[workflow] Added NoraWorkflow starter composition to src/Root.tsx');
|
|
320
|
+
}
|
|
321
|
+
if (zodAdded) {
|
|
322
|
+
console.log('[workflow] Added dependency: zod');
|
|
323
|
+
}
|
|
138
324
|
console.log(`[workflow] Done. Use project: ${projectDir}`);
|