@remotion/studio-server 4.0.465 → 4.0.466

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.
@@ -0,0 +1,11 @@
1
+ export type ResolvedCompositionComponent = {
2
+ source: string;
3
+ line: number;
4
+ column: number;
5
+ canAddSequence: boolean;
6
+ };
7
+ export declare const resolveCompositionComponent: ({ remotionRoot, compositionFile, compositionId, }: {
8
+ remotionRoot: string;
9
+ compositionFile: string;
10
+ compositionId: string;
11
+ }) => Promise<ResolvedCompositionComponent>;
@@ -0,0 +1,691 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.resolveCompositionComponent = void 0;
40
+ const node_fs_1 = __importDefault(require("node:fs"));
41
+ const node_path_1 = __importDefault(require("node:path"));
42
+ const recast = __importStar(require("recast"));
43
+ const parse_ast_1 = require("../codemods/parse-ast");
44
+ const allowedFileExtensions = new Set(['.tsx', '.ts', '.jsx', '.js']);
45
+ const extensionsToProbe = ['.tsx', '.ts', '.jsx', '.js'];
46
+ const isInRemotionRoot = ({ remotionRoot, fileName, }) => {
47
+ const relativePath = node_path_1.default.relative(remotionRoot, fileName);
48
+ return !relativePath.startsWith('..') && !node_path_1.default.isAbsolute(relativePath);
49
+ };
50
+ const readSourceFile = ({ remotionRoot, fileName, }) => {
51
+ const resolved = node_path_1.default.resolve(remotionRoot, fileName);
52
+ if (!isInRemotionRoot({ remotionRoot, fileName: resolved })) {
53
+ throw new Error(`Not allowed to open ${fileName}`);
54
+ }
55
+ if (!allowedFileExtensions.has(node_path_1.default.extname(resolved))) {
56
+ throw new Error(`Not allowed to open ${fileName}`);
57
+ }
58
+ return node_fs_1.default.promises.readFile(resolved, 'utf-8');
59
+ };
60
+ const getAttributeName = (attribute) => {
61
+ if (attribute.name.type !== 'JSXIdentifier') {
62
+ return null;
63
+ }
64
+ return attribute.name.name;
65
+ };
66
+ const findAttribute = (element, name) => {
67
+ return element.openingElement.attributes.find((attribute) => {
68
+ if (attribute.type !== 'JSXAttribute') {
69
+ return false;
70
+ }
71
+ return getAttributeName(attribute) === name;
72
+ });
73
+ };
74
+ const getStringAttributeValue = (element, name) => {
75
+ const attribute = findAttribute(element, name);
76
+ if (!(attribute === null || attribute === void 0 ? void 0 : attribute.value)) {
77
+ return null;
78
+ }
79
+ if (attribute.value.type === 'StringLiteral') {
80
+ return attribute.value.value;
81
+ }
82
+ if (attribute.value.type === 'JSXExpressionContainer' &&
83
+ attribute.value.expression.type === 'StringLiteral') {
84
+ return attribute.value.expression.value;
85
+ }
86
+ return null;
87
+ };
88
+ const findCompositionElement = ({ ast, compositionId, }) => {
89
+ let found = null;
90
+ recast.types.visit(ast, {
91
+ visitJSXElement(astPath) {
92
+ if (found) {
93
+ return false;
94
+ }
95
+ const node = astPath.node;
96
+ const openingName = node.openingElement.name;
97
+ if (openingName.type === 'JSXIdentifier' &&
98
+ (openingName.name === 'Composition' || openingName.name === 'Still') &&
99
+ getStringAttributeValue(node, 'id') === compositionId) {
100
+ found = node;
101
+ return false;
102
+ }
103
+ this.traverse(astPath);
104
+ return undefined;
105
+ },
106
+ });
107
+ return found;
108
+ };
109
+ const getComponentIdentifier = (element) => {
110
+ const attribute = findAttribute(element, 'component');
111
+ if (!(attribute === null || attribute === void 0 ? void 0 : attribute.value) ||
112
+ attribute.value.type !== 'JSXExpressionContainer' ||
113
+ attribute.value.expression.type !== 'Identifier') {
114
+ return null;
115
+ }
116
+ return attribute.value.expression.name;
117
+ };
118
+ const isRecord = (value) => {
119
+ return typeof value === 'object' && value !== null;
120
+ };
121
+ const findDynamicImportPath = (value) => {
122
+ if (!isRecord(value)) {
123
+ return null;
124
+ }
125
+ if (value.type === 'CallExpression' &&
126
+ isRecord(value.callee) &&
127
+ value.callee.type === 'Import' &&
128
+ Array.isArray(value.arguments) &&
129
+ isRecord(value.arguments[0]) &&
130
+ value.arguments[0].type === 'StringLiteral' &&
131
+ typeof value.arguments[0].value === 'string') {
132
+ return value.arguments[0].value;
133
+ }
134
+ for (const [key, child] of Object.entries(value)) {
135
+ if (key === 'loc' ||
136
+ key === 'start' ||
137
+ key === 'end' ||
138
+ key === 'comments' ||
139
+ key === 'leadingComments' ||
140
+ key === 'trailingComments' ||
141
+ key === 'innerComments' ||
142
+ key === 'extra' ||
143
+ key === 'original') {
144
+ continue;
145
+ }
146
+ if (Array.isArray(child)) {
147
+ for (const item of child) {
148
+ const nestedResult = findDynamicImportPath(item);
149
+ if (nestedResult) {
150
+ return nestedResult;
151
+ }
152
+ }
153
+ continue;
154
+ }
155
+ const childResult = findDynamicImportPath(child);
156
+ if (childResult) {
157
+ return childResult;
158
+ }
159
+ }
160
+ return null;
161
+ };
162
+ const getLazyImportPath = (element) => {
163
+ const attribute = findAttribute(element, 'lazyComponent');
164
+ if (!(attribute === null || attribute === void 0 ? void 0 : attribute.value) || attribute.value.type !== 'JSXExpressionContainer') {
165
+ return null;
166
+ }
167
+ return findDynamicImportPath(attribute.value.expression);
168
+ };
169
+ const findImportTarget = ({ ast, componentName, }) => {
170
+ let found = null;
171
+ recast.types.visit(ast, {
172
+ visitImportDeclaration(astPath) {
173
+ var _a;
174
+ if (found) {
175
+ return false;
176
+ }
177
+ const node = astPath.node;
178
+ if (typeof node.source.value !== 'string') {
179
+ return false;
180
+ }
181
+ const matchingSpecifier = (_a = node.specifiers) === null || _a === void 0 ? void 0 : _a.find((specifier) => {
182
+ var _a;
183
+ return ((_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) === componentName;
184
+ });
185
+ if (!matchingSpecifier) {
186
+ return false;
187
+ }
188
+ if (matchingSpecifier.type === 'ImportDefaultSpecifier') {
189
+ found = {
190
+ importPath: node.source.value,
191
+ exportName: 'default',
192
+ };
193
+ return false;
194
+ }
195
+ if (matchingSpecifier.type === 'ImportSpecifier' &&
196
+ matchingSpecifier.imported.type === 'Identifier') {
197
+ found = {
198
+ importPath: node.source.value,
199
+ exportName: matchingSpecifier.imported.name,
200
+ };
201
+ return false;
202
+ }
203
+ if (matchingSpecifier.type === 'ImportSpecifier' &&
204
+ matchingSpecifier.imported.type === 'StringLiteral') {
205
+ found = {
206
+ importPath: node.source.value,
207
+ exportName: matchingSpecifier.imported.value,
208
+ };
209
+ return false;
210
+ }
211
+ return false;
212
+ },
213
+ });
214
+ return found;
215
+ };
216
+ const getExportedName = (exported) => {
217
+ if (!exported) {
218
+ return null;
219
+ }
220
+ if (!isRecord(exported)) {
221
+ return null;
222
+ }
223
+ if (exported.type === 'Identifier' && typeof exported.name === 'string') {
224
+ return exported.name;
225
+ }
226
+ if (exported.type === 'StringLiteral' && typeof exported.value === 'string') {
227
+ return exported.value;
228
+ }
229
+ return null;
230
+ };
231
+ const getSpecifierLocalName = (specifier) => {
232
+ if (specifier.local.type === 'Identifier') {
233
+ return specifier.local.name;
234
+ }
235
+ return null;
236
+ };
237
+ const findReExportTargets = ({ ast, exportName, }) => {
238
+ const targets = [];
239
+ recast.types.visit(ast, {
240
+ visitExportNamedDeclaration(astPath) {
241
+ var _a;
242
+ const node = astPath.node;
243
+ if (typeof ((_a = node.source) === null || _a === void 0 ? void 0 : _a.value) !== 'string') {
244
+ return false;
245
+ }
246
+ for (const specifier of node.specifiers) {
247
+ if (specifier.type !== 'ExportSpecifier') {
248
+ continue;
249
+ }
250
+ const exportedName = getExportedName(specifier.exported);
251
+ if (exportedName !== exportName) {
252
+ continue;
253
+ }
254
+ const localName = getSpecifierLocalName(specifier);
255
+ if (!localName) {
256
+ continue;
257
+ }
258
+ targets.push({
259
+ importPath: node.source.value,
260
+ exportName: localName === 'default' ? 'default' : localName,
261
+ });
262
+ }
263
+ return false;
264
+ },
265
+ visitExportAllDeclaration(astPath) {
266
+ const node = astPath.node;
267
+ if (typeof node.source.value !== 'string') {
268
+ return false;
269
+ }
270
+ targets.push({
271
+ importPath: node.source.value,
272
+ exportName,
273
+ });
274
+ return false;
275
+ },
276
+ });
277
+ return targets;
278
+ };
279
+ const resolveImportPath = ({ importPath, fromFile, }) => {
280
+ if (!importPath.startsWith('.')) {
281
+ throw new Error(`Cannot resolve non-relative import ${importPath}`);
282
+ }
283
+ const basePath = node_path_1.default.resolve(node_path_1.default.dirname(fromFile), importPath);
284
+ const candidates = node_path_1.default.extname(basePath)
285
+ ? [basePath]
286
+ : [
287
+ ...extensionsToProbe.map((extension) => `${basePath}${extension}`),
288
+ ...extensionsToProbe.map((extension) => node_path_1.default.join(basePath, `index${extension}`)),
289
+ ];
290
+ const existingFile = candidates.find((candidate) => {
291
+ return node_fs_1.default.existsSync(candidate) && node_fs_1.default.statSync(candidate).isFile();
292
+ });
293
+ if (!existingFile) {
294
+ throw new Error(`Could not find imported component file ${importPath}`);
295
+ }
296
+ return existingFile;
297
+ };
298
+ const locationFromNode = (node) => {
299
+ if (!node.loc) {
300
+ return null;
301
+ }
302
+ return {
303
+ line: node.loc.start.line,
304
+ column: node.loc.start.column,
305
+ };
306
+ };
307
+ const findLocalSymbolLocation = ({ ast, name, }) => {
308
+ let location = null;
309
+ recast.types.visit(ast, {
310
+ visitVariableDeclarator(astPath) {
311
+ if (location) {
312
+ return false;
313
+ }
314
+ const { node } = astPath;
315
+ if (node.id.type === 'Identifier' && node.id.name === name) {
316
+ location = locationFromNode(node);
317
+ return false;
318
+ }
319
+ this.traverse(astPath);
320
+ return undefined;
321
+ },
322
+ visitFunctionDeclaration(astPath) {
323
+ var _a;
324
+ if (location) {
325
+ return false;
326
+ }
327
+ const { node } = astPath;
328
+ if (((_a = node.id) === null || _a === void 0 ? void 0 : _a.name) === name) {
329
+ location = locationFromNode(node);
330
+ return false;
331
+ }
332
+ this.traverse(astPath);
333
+ return undefined;
334
+ },
335
+ visitClassDeclaration(astPath) {
336
+ var _a;
337
+ if (location) {
338
+ return false;
339
+ }
340
+ const { node } = astPath;
341
+ if (((_a = node.id) === null || _a === void 0 ? void 0 : _a.name) === name) {
342
+ location = locationFromNode(node);
343
+ return false;
344
+ }
345
+ this.traverse(astPath);
346
+ return undefined;
347
+ },
348
+ });
349
+ return location;
350
+ };
351
+ const findDefaultExportLocation = (ast) => {
352
+ let location = null;
353
+ let exportedIdentifier = null;
354
+ recast.types.visit(ast, {
355
+ visitExportDefaultDeclaration(astPath) {
356
+ var _a;
357
+ if (location || exportedIdentifier) {
358
+ return false;
359
+ }
360
+ const { node } = astPath;
361
+ if (node.declaration.type === 'Identifier') {
362
+ exportedIdentifier = node.declaration.name;
363
+ return false;
364
+ }
365
+ location = (_a = locationFromNode(node.declaration)) !== null && _a !== void 0 ? _a : locationFromNode(node);
366
+ return false;
367
+ },
368
+ });
369
+ if (exportedIdentifier) {
370
+ return findLocalSymbolLocation({ ast, name: exportedIdentifier });
371
+ }
372
+ return location;
373
+ };
374
+ const findLocalComponentDeclaration = ({ ast, name, }) => {
375
+ let declaration = null;
376
+ recast.types.visit(ast, {
377
+ visitVariableDeclarator(astPath) {
378
+ if (declaration) {
379
+ return false;
380
+ }
381
+ const { node } = astPath;
382
+ if (node.id.type === 'Identifier' && node.id.name === name) {
383
+ declaration = node;
384
+ return false;
385
+ }
386
+ this.traverse(astPath);
387
+ return undefined;
388
+ },
389
+ visitFunctionDeclaration(astPath) {
390
+ var _a;
391
+ if (declaration) {
392
+ return false;
393
+ }
394
+ const { node } = astPath;
395
+ if (((_a = node.id) === null || _a === void 0 ? void 0 : _a.name) === name) {
396
+ declaration = node;
397
+ return false;
398
+ }
399
+ this.traverse(astPath);
400
+ return undefined;
401
+ },
402
+ visitClassDeclaration(astPath) {
403
+ var _a;
404
+ if (declaration) {
405
+ return false;
406
+ }
407
+ const { node } = astPath;
408
+ if (((_a = node.id) === null || _a === void 0 ? void 0 : _a.name) === name) {
409
+ declaration = node;
410
+ return false;
411
+ }
412
+ this.traverse(astPath);
413
+ return undefined;
414
+ },
415
+ });
416
+ return declaration;
417
+ };
418
+ const getTopLevelReturnStatement = (statements) => {
419
+ const returnStatements = [];
420
+ for (const statement of statements) {
421
+ if (recast.types.namedTypes.ReturnStatement.check(statement)) {
422
+ returnStatements.push(statement);
423
+ }
424
+ }
425
+ if (returnStatements.length !== 1) {
426
+ return null;
427
+ }
428
+ const singleReturn = returnStatements[0];
429
+ const finalStatement = statements.at(-1);
430
+ if (singleReturn !== finalStatement) {
431
+ return null;
432
+ }
433
+ return singleReturn;
434
+ };
435
+ const getReturnedJsxFromFunction = (fn) => {
436
+ if (fn.type === 'ArrowFunctionExpression') {
437
+ if (fn.body.type === 'JSXElement' || fn.body.type === 'JSXFragment') {
438
+ return fn.body;
439
+ }
440
+ if (fn.body.type !== 'BlockStatement') {
441
+ return null;
442
+ }
443
+ const arrowReturnStatement = getTopLevelReturnStatement(fn.body.body);
444
+ if (!(arrowReturnStatement === null || arrowReturnStatement === void 0 ? void 0 : arrowReturnStatement.argument)) {
445
+ return null;
446
+ }
447
+ return arrowReturnStatement.argument.type === 'JSXElement' ||
448
+ arrowReturnStatement.argument.type === 'JSXFragment'
449
+ ? arrowReturnStatement.argument
450
+ : null;
451
+ }
452
+ if (fn.body.type !== 'BlockStatement') {
453
+ return null;
454
+ }
455
+ const returnStatement = getTopLevelReturnStatement(fn.body.body);
456
+ if (!(returnStatement === null || returnStatement === void 0 ? void 0 : returnStatement.argument)) {
457
+ return null;
458
+ }
459
+ return returnStatement.argument.type === 'JSXElement' ||
460
+ returnStatement.argument.type === 'JSXFragment'
461
+ ? returnStatement.argument
462
+ : null;
463
+ };
464
+ const findRenderMethod = (declaration) => {
465
+ const renderMethod = declaration.body.body.find((member) => {
466
+ return (member.type === 'ClassMethod' &&
467
+ member.kind === 'method' &&
468
+ member.key.type === 'Identifier' &&
469
+ member.key.name === 'render');
470
+ });
471
+ return (renderMethod === null || renderMethod === void 0 ? void 0 : renderMethod.type) === 'ClassMethod' ? renderMethod : null;
472
+ };
473
+ const getComponentRootNode = (declaration) => {
474
+ if (declaration.type === 'VariableDeclarator') {
475
+ if (!declaration.init ||
476
+ (declaration.init.type !== 'ArrowFunctionExpression' &&
477
+ declaration.init.type !== 'FunctionExpression')) {
478
+ return null;
479
+ }
480
+ return getReturnedJsxFromFunction(declaration.init);
481
+ }
482
+ if (declaration.type === 'ArrowFunctionExpression' ||
483
+ declaration.type === 'FunctionExpression' ||
484
+ declaration.type === 'FunctionDeclaration') {
485
+ return getReturnedJsxFromFunction(declaration);
486
+ }
487
+ if (declaration.type !== 'ClassDeclaration') {
488
+ return null;
489
+ }
490
+ const renderMethod = findRenderMethod(declaration);
491
+ if (!renderMethod) {
492
+ return null;
493
+ }
494
+ const returnStatement = getTopLevelReturnStatement(renderMethod.body.body);
495
+ if (!(returnStatement === null || returnStatement === void 0 ? void 0 : returnStatement.argument)) {
496
+ return null;
497
+ }
498
+ return returnStatement.argument.type === 'JSXElement' ||
499
+ returnStatement.argument.type === 'JSXFragment'
500
+ ? returnStatement.argument
501
+ : null;
502
+ };
503
+ const createSequenceElement = () => {
504
+ return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier('Sequence'), []), recast.types.builders.jsxClosingElement(recast.types.builders.jsxIdentifier('Sequence')), []);
505
+ };
506
+ const getDefaultExportDeclaration = (ast) => {
507
+ let declaration = null;
508
+ let identifierName = null;
509
+ recast.types.visit(ast, {
510
+ visitExportDefaultDeclaration(astPath) {
511
+ if (declaration || identifierName) {
512
+ return false;
513
+ }
514
+ const { node } = astPath;
515
+ if (node.declaration.type === 'Identifier') {
516
+ identifierName = node.declaration.name;
517
+ return false;
518
+ }
519
+ declaration = node.declaration;
520
+ return false;
521
+ },
522
+ });
523
+ if (identifierName) {
524
+ return findLocalComponentDeclaration({ ast, name: identifierName });
525
+ }
526
+ return declaration;
527
+ };
528
+ const getDeclarationByExportName = ({ ast, exportName, }) => {
529
+ if (exportName === 'default') {
530
+ return getDefaultExportDeclaration(ast);
531
+ }
532
+ return findLocalComponentDeclaration({ ast, name: exportName });
533
+ };
534
+ const canAddSequenceToComponent = ({ ast, exportName, }) => {
535
+ const declaration = getDeclarationByExportName({ ast, exportName });
536
+ if (!declaration) {
537
+ return false;
538
+ }
539
+ const rootNode = getComponentRootNode(declaration);
540
+ if (!rootNode) {
541
+ return false;
542
+ }
543
+ if (rootNode.type === 'JSXElement') {
544
+ if (rootNode.openingElement.selfClosing) {
545
+ return false;
546
+ }
547
+ if (!rootNode.children) {
548
+ return false;
549
+ }
550
+ rootNode.children.push(createSequenceElement());
551
+ try {
552
+ recast.print(ast);
553
+ return true;
554
+ }
555
+ catch (_a) {
556
+ return false;
557
+ }
558
+ }
559
+ if (!rootNode.children) {
560
+ return false;
561
+ }
562
+ rootNode.children.push(createSequenceElement());
563
+ try {
564
+ recast.print(ast);
565
+ return true;
566
+ }
567
+ catch (_b) {
568
+ return false;
569
+ }
570
+ };
571
+ const getComponentLocationInFile = async ({ remotionRoot, fileName, exportName, }) => {
572
+ var _a, _b;
573
+ const input = await readSourceFile({ remotionRoot, fileName });
574
+ const ast = (0, parse_ast_1.parseAst)(input);
575
+ const astForSequenceSimulation = (0, parse_ast_1.parseAst)(input);
576
+ const location = exportName === 'default'
577
+ ? findDefaultExportLocation(ast)
578
+ : findLocalSymbolLocation({ ast, name: exportName });
579
+ const canAddSequence = canAddSequenceToComponent({
580
+ ast: astForSequenceSimulation,
581
+ exportName,
582
+ });
583
+ return {
584
+ source: node_path_1.default.relative(remotionRoot, fileName),
585
+ line: (_a = location === null || location === void 0 ? void 0 : location.line) !== null && _a !== void 0 ? _a : 1,
586
+ column: (_b = location === null || location === void 0 ? void 0 : location.column) !== null && _b !== void 0 ? _b : 0,
587
+ canAddSequence,
588
+ };
589
+ };
590
+ const getComponentLocationRecursively = async ({ remotionRoot, fileName, exportName, visited, }) => {
591
+ const key = `${fileName}:${exportName}`;
592
+ if (visited.has(key)) {
593
+ throw new Error(`Could not resolve component export "${exportName}" in ${node_path_1.default.relative(remotionRoot, fileName)}`);
594
+ }
595
+ visited.add(key);
596
+ try {
597
+ const input = await readSourceFile({ remotionRoot, fileName });
598
+ const ast = (0, parse_ast_1.parseAst)(input);
599
+ const localDeclaration = getDeclarationByExportName({
600
+ ast,
601
+ exportName,
602
+ });
603
+ if (localDeclaration) {
604
+ return await getComponentLocationInFile({
605
+ remotionRoot,
606
+ fileName,
607
+ exportName,
608
+ });
609
+ }
610
+ const reExportTargets = findReExportTargets({
611
+ ast,
612
+ exportName,
613
+ });
614
+ for (const target of reExportTargets) {
615
+ try {
616
+ const resolvedImportPath = resolveImportPath({
617
+ importPath: target.importPath,
618
+ fromFile: fileName,
619
+ });
620
+ return await getComponentLocationRecursively({
621
+ remotionRoot,
622
+ fileName: resolvedImportPath,
623
+ exportName: target.exportName,
624
+ visited,
625
+ });
626
+ }
627
+ catch (_a) {
628
+ continue;
629
+ }
630
+ }
631
+ if (reExportTargets.length > 0) {
632
+ throw new Error(`Could not resolve component export "${exportName}" in ${node_path_1.default.relative(remotionRoot, fileName)}`);
633
+ }
634
+ return await getComponentLocationInFile({
635
+ remotionRoot,
636
+ fileName,
637
+ exportName,
638
+ });
639
+ }
640
+ finally {
641
+ visited.delete(key);
642
+ }
643
+ };
644
+ const resolveCompositionComponent = async ({ remotionRoot, compositionFile, compositionId, }) => {
645
+ const compositionFileName = node_path_1.default.resolve(remotionRoot, compositionFile);
646
+ const input = await readSourceFile({
647
+ remotionRoot,
648
+ fileName: compositionFileName,
649
+ });
650
+ const ast = (0, parse_ast_1.parseAst)(input);
651
+ const compositionElement = findCompositionElement({ ast, compositionId });
652
+ if (!compositionElement) {
653
+ throw new Error(`Could not find composition "${compositionId}"`);
654
+ }
655
+ const lazyImportPath = getLazyImportPath(compositionElement);
656
+ if (lazyImportPath) {
657
+ const lazyComponentFile = resolveImportPath({
658
+ importPath: lazyImportPath,
659
+ fromFile: compositionFileName,
660
+ });
661
+ return getComponentLocationRecursively({
662
+ remotionRoot,
663
+ fileName: lazyComponentFile,
664
+ exportName: 'default',
665
+ visited: new Set(),
666
+ });
667
+ }
668
+ const componentName = getComponentIdentifier(compositionElement);
669
+ if (!componentName) {
670
+ throw new Error(`Could not find a component prop for composition "${compositionId}"`);
671
+ }
672
+ const importTarget = findImportTarget({ ast, componentName });
673
+ if (!importTarget) {
674
+ return getComponentLocationInFile({
675
+ remotionRoot,
676
+ fileName: compositionFileName,
677
+ exportName: componentName,
678
+ });
679
+ }
680
+ const importedComponentFile = resolveImportPath({
681
+ importPath: importTarget.importPath,
682
+ fromFile: compositionFileName,
683
+ });
684
+ return getComponentLocationRecursively({
685
+ remotionRoot,
686
+ fileName: importedComponentFile,
687
+ exportName: importTarget.exportName,
688
+ visited: new Set(),
689
+ });
690
+ };
691
+ exports.resolveCompositionComponent = resolveCompositionComponent;
@@ -26,6 +26,7 @@ const handleAddRender = ({ input, entryPoint, remotionRoot, logLevel, binariesDi
26
26
  cancelToken: (0, renderer_1.makeCancelSignal)(),
27
27
  concurrency: input.concurrency,
28
28
  crf: input.crf,
29
+ gopSize: input.gopSize,
29
30
  endFrame: input.endFrame,
30
31
  startFrame: input.startFrame,
31
32
  muted: input.muted,
@@ -43,7 +43,7 @@ const getPropsFromObjectExpression = ({ objExpr, keys, }) => {
43
43
  }
44
44
  const valueExpr = prop.value;
45
45
  if (!(0, can_update_sequence_props_1.isStaticValue)(valueExpr)) {
46
- out[key] = { canUpdate: false, reason: 'computed' };
46
+ out[key] = (0, can_update_sequence_props_1.getComputedStatus)(valueExpr);
47
47
  continue;
48
48
  }
49
49
  out[key] = {
@@ -1,10 +1,9 @@
1
1
  import type { Expression, File, JSXOpeningElement } from '@babel/types';
2
2
  import type { SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
3
- import type { CanUpdateSequencePropsResponseTrue } from 'remotion';
4
- import type { SequenceNodePath } from 'remotion';
5
- import type { CanUpdateSequencePropStatus } from 'remotion';
3
+ import type { CanUpdateSequencePropsResponseTrue, CanUpdateSequencePropStatus, SequenceNodePath } from 'remotion';
6
4
  export declare const isStaticValue: (node: Expression) => boolean;
7
5
  export declare const extractStaticValue: (node: Expression) => unknown;
6
+ export declare const getComputedStatus: (node: Expression) => CanUpdateSequencePropStatus;
8
7
  export declare const findJsxElementAtNodePath: (ast: File, nodePath: SequenceNodePath) => JSXOpeningElement | null;
9
8
  export declare const lineColumnToNodePath: (ast: File, targetLine: number) => SequenceNodePath | null;
10
9
  export declare const computeSequencePropsOnlyStatus: ({ fileName, nodePath, keys, remotionRoot, }: {
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.computeSequencePropsStatusFromFilenameByLine = exports.computeSequencePropsStatus = exports.computeSequencePropsStatusFromContent = exports.computeSequencePropsOnlyStatus = exports.lineColumnToNodePath = exports.findJsxElementAtNodePath = exports.extractStaticValue = exports.isStaticValue = void 0;
39
+ exports.computeSequencePropsStatusFromFilenameByLine = exports.computeSequencePropsStatus = exports.computeSequencePropsStatusFromContent = exports.computeSequencePropsOnlyStatus = exports.lineColumnToNodePath = exports.findJsxElementAtNodePath = exports.getComputedStatus = exports.extractStaticValue = exports.isStaticValue = void 0;
40
40
  const node_fs_1 = require("node:fs");
41
41
  const node_path_1 = __importDefault(require("node:path"));
42
42
  const renderer_1 = require("@remotion/renderer");
@@ -117,6 +117,76 @@ const extractStaticValue = (node) => {
117
117
  }
118
118
  };
119
119
  exports.extractStaticValue = extractStaticValue;
120
+ const getNumericValue = (node) => {
121
+ if (node.type === 'NumericLiteral') {
122
+ return node.value;
123
+ }
124
+ if (node.type === 'UnaryExpression' &&
125
+ (node.operator === '-' || node.operator === '+') &&
126
+ node.argument.type === 'NumericLiteral') {
127
+ return node.operator === '-' ? -node.argument.value : node.argument.value;
128
+ }
129
+ if (node.type === 'TSAsExpression') {
130
+ return getNumericValue(node.expression);
131
+ }
132
+ return null;
133
+ };
134
+ const getInterpolateKeyframes = (node) => {
135
+ if (node.type === 'TSAsExpression') {
136
+ return getInterpolateKeyframes(node.expression);
137
+ }
138
+ if (node.type !== 'CallExpression') {
139
+ return undefined;
140
+ }
141
+ const callExpression = node;
142
+ if (callExpression.callee.type !== 'Identifier' ||
143
+ callExpression.callee.name !== 'interpolate') {
144
+ return undefined;
145
+ }
146
+ const inputArg = callExpression.arguments[1];
147
+ const outputArg = callExpression.arguments[2];
148
+ if (!inputArg ||
149
+ !outputArg ||
150
+ inputArg.type !== 'ArrayExpression' ||
151
+ outputArg.type !== 'ArrayExpression') {
152
+ return undefined;
153
+ }
154
+ if (inputArg.elements.length !== outputArg.elements.length) {
155
+ return undefined;
156
+ }
157
+ const keyframes = [];
158
+ for (let i = 0; i < inputArg.elements.length; i++) {
159
+ const inputElement = inputArg.elements[i];
160
+ const outputElement = outputArg.elements[i];
161
+ if (!inputElement ||
162
+ !outputElement ||
163
+ inputElement.type === 'SpreadElement' ||
164
+ outputElement.type === 'SpreadElement') {
165
+ return undefined;
166
+ }
167
+ const frame = getNumericValue(inputElement);
168
+ if (frame === null || !(0, exports.isStaticValue)(outputElement)) {
169
+ return undefined;
170
+ }
171
+ keyframes.push({
172
+ frame,
173
+ value: (0, exports.extractStaticValue)(outputElement),
174
+ });
175
+ }
176
+ return keyframes.length > 0 ? keyframes : undefined;
177
+ };
178
+ const getComputedStatus = (node) => {
179
+ const keyframes = getInterpolateKeyframes(node);
180
+ if (!keyframes) {
181
+ return { canUpdate: false, reason: 'computed' };
182
+ }
183
+ return {
184
+ canUpdate: false,
185
+ reason: 'computed',
186
+ keyframes,
187
+ };
188
+ };
189
+ exports.getComputedStatus = getComputedStatus;
120
190
  const getPropsStatus = (jsxElement) => {
121
191
  const props = {};
122
192
  for (const attr of jsxElement.attributes) {
@@ -144,11 +214,14 @@ const getPropsStatus = (jsxElement) => {
144
214
  }
145
215
  if (value.type === 'JSXExpressionContainer') {
146
216
  const { expression } = value;
147
- if (expression.type === 'JSXEmptyExpression' ||
148
- !(0, exports.isStaticValue)(expression)) {
217
+ if (expression.type === 'JSXEmptyExpression') {
149
218
  props[name] = { canUpdate: false, reason: 'computed' };
150
219
  continue;
151
220
  }
221
+ if (!(0, exports.isStaticValue)(expression)) {
222
+ props[name] = (0, exports.getComputedStatus)(expression);
223
+ continue;
224
+ }
152
225
  props[name] = {
153
226
  canUpdate: true,
154
227
  codeValue: (0, exports.extractStaticValue)(expression),
@@ -242,7 +315,7 @@ const getNestedPropStatus = (jsxElement, parentKey, childKey) => {
242
315
  }
243
316
  const propValue = prop.value;
244
317
  if (!(0, exports.isStaticValue)(propValue)) {
245
- return { canUpdate: false, reason: 'computed' };
318
+ return (0, exports.getComputedStatus)(propValue);
246
319
  }
247
320
  const codeValue = (0, exports.extractStaticValue)(propValue);
248
321
  if (!validateStyleValue(childKey, codeValue)) {
@@ -27,7 +27,9 @@ const attrName = (str) => (0, exports.fg)(166, 226, 46, str);
27
27
  exports.attrName = attrName;
28
28
  const equals = (str) => (0, exports.fg)(249, 38, 114, str);
29
29
  exports.equals = equals;
30
- const punctuation = (str) => (0, exports.fg)(248, 248, 242, str);
30
+ // Use the terminal's default foreground color so punctuation is visible in both
31
+ // light and dark terminal themes.
32
+ const punctuation = (str) => str;
31
33
  exports.punctuation = punctuation;
32
34
  const stringValue = (str) => (0, exports.fg)(230, 219, 116, str);
33
35
  exports.stringValue = stringValue;
package/dist/routes.js CHANGED
@@ -45,6 +45,7 @@ const client_render_queue_1 = require("./client-render-queue");
45
45
  const get_file_source_1 = require("./helpers/get-file-source");
46
46
  const get_installed_installable_packages_1 = require("./helpers/get-installed-installable-packages");
47
47
  const open_in_editor_1 = require("./helpers/open-in-editor");
48
+ const resolve_composition_component_1 = require("./helpers/resolve-composition-component");
48
49
  const resolve_output_path_1 = require("./helpers/resolve-output-path");
49
50
  const api_routes_1 = require("./preview-server/api-routes");
50
51
  const get_package_manager_1 = require("./preview-server/get-package-manager");
@@ -201,6 +202,41 @@ const handleOpenInEditor = async (remotionRoot, req, res, logLevel) => {
201
202
  }));
202
203
  }
203
204
  };
205
+ const handleGetCompositionComponentInfo = async (remotionRoot, req, res) => {
206
+ if (req.method === 'OPTIONS') {
207
+ res.statusCode = 200;
208
+ res.end();
209
+ return;
210
+ }
211
+ res.setHeader('content-type', 'application/json');
212
+ try {
213
+ const body = (await (0, parse_body_1.parseRequestBody)(req));
214
+ if (typeof body.compositionFile !== 'string') {
215
+ throw new TypeError('Need to pass compositionFile');
216
+ }
217
+ if (typeof body.compositionId !== 'string') {
218
+ throw new TypeError('Need to pass compositionId');
219
+ }
220
+ const location = await (0, resolve_composition_component_1.resolveCompositionComponent)({
221
+ remotionRoot,
222
+ compositionFile: body.compositionFile,
223
+ compositionId: body.compositionId,
224
+ });
225
+ res.writeHead(200);
226
+ res.end(JSON.stringify({
227
+ success: true,
228
+ location,
229
+ canAddSequence: location.canAddSequence,
230
+ }));
231
+ }
232
+ catch (err) {
233
+ res.writeHead(200);
234
+ res.end(JSON.stringify({
235
+ success: false,
236
+ error: err.message,
237
+ }));
238
+ }
239
+ };
204
240
  const validateSameOrigin = (req) => {
205
241
  const { origin, host } = req.headers;
206
242
  if (origin) {
@@ -330,6 +366,9 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
330
366
  if (url.pathname === '/api/open-in-editor') {
331
367
  return handleOpenInEditor(remotionRoot, request, response, logLevel);
332
368
  }
369
+ if (url.pathname === '/api/composition-component-info') {
370
+ return handleGetCompositionComponentInfo(remotionRoot, request, response);
371
+ }
333
372
  if (url.pathname === `${staticHash}/api/add-asset`) {
334
373
  return handleAddAsset({
335
374
  req: request,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/studio-server"
4
4
  },
5
5
  "name": "@remotion/studio-server",
6
- "version": "4.0.465",
6
+ "version": "4.0.466",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -27,11 +27,11 @@
27
27
  "@babel/parser": "7.24.1",
28
28
  "semver": "7.5.3",
29
29
  "prettier": "3.8.1",
30
- "remotion": "4.0.465",
30
+ "remotion": "4.0.466",
31
31
  "recast": "0.23.11",
32
- "@remotion/bundler": "4.0.465",
33
- "@remotion/renderer": "4.0.465",
34
- "@remotion/studio-shared": "4.0.465",
32
+ "@remotion/bundler": "4.0.466",
33
+ "@remotion/renderer": "4.0.466",
34
+ "@remotion/studio-shared": "4.0.466",
35
35
  "memfs": "3.4.3",
36
36
  "open": "8.4.2"
37
37
  },
@@ -39,7 +39,7 @@
39
39
  "ast-types": "0.16.1",
40
40
  "react": "19.2.3",
41
41
  "@types/semver": "7.5.3",
42
- "@remotion/eslint-config-internal": "4.0.465",
42
+ "@remotion/eslint-config-internal": "4.0.466",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14",
45
45
  "@typescript/native-preview": "7.0.0-dev.20260217.1"