@remotion/studio-server 4.0.511 → 4.0.513

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.
@@ -11,10 +11,11 @@
11
11
  * so prettier makes the same line-breaking decisions as if the content
12
12
  * were at its actual column position in the file.
13
13
  */
14
- export declare const formatInlineContent: ({ inlineContent, linePrefix, endOfLine, }: {
14
+ export declare const formatInlineContent: ({ inlineContent, linePrefix, endOfLine, prettierConfigOverride, }: {
15
15
  inlineContent: string;
16
16
  linePrefix: string;
17
17
  endOfLine: "auto" | "lf";
18
+ prettierConfigOverride?: Record<string, unknown> | null | undefined;
18
19
  }) => Promise<{
19
20
  formatted: string;
20
21
  didFormat: boolean;
@@ -47,7 +47,7 @@ exports.formatInlineContent = void 0;
47
47
  * so prettier makes the same line-breaking decisions as if the content
48
48
  * were at its actual column position in the file.
49
49
  */
50
- const formatInlineContent = async ({ inlineContent, linePrefix, endOfLine, }) => {
50
+ const formatInlineContent = async ({ inlineContent, linePrefix, endOfLine, prettierConfigOverride, }) => {
51
51
  var _a;
52
52
  var _b, _c, _d;
53
53
  let prettier = null;
@@ -58,13 +58,19 @@ const formatInlineContent = async ({ inlineContent, linePrefix, endOfLine, }) =>
58
58
  return { formatted: inlineContent, didFormat: false };
59
59
  }
60
60
  const { format, resolveConfig, resolveConfigFile } = prettier;
61
- const configFilePath = await resolveConfigFile();
62
- if (!configFilePath) {
63
- return { formatted: inlineContent, didFormat: false };
61
+ let prettierConfig;
62
+ if (prettierConfigOverride !== undefined && prettierConfigOverride !== null) {
63
+ prettierConfig = prettierConfigOverride;
64
64
  }
65
- const prettierConfig = await resolveConfig(configFilePath);
66
- if (!prettierConfig) {
67
- return { formatted: inlineContent, didFormat: false };
65
+ else {
66
+ const configFilePath = await resolveConfigFile();
67
+ if (!configFilePath) {
68
+ return { formatted: inlineContent, didFormat: false };
69
+ }
70
+ prettierConfig = await resolveConfig(configFilePath);
71
+ if (!prettierConfig) {
72
+ return { formatted: inlineContent, didFormat: false };
73
+ }
68
74
  }
69
75
  const tabWidth = (_b = prettierConfig.tabWidth) !== null && _b !== void 0 ? _b : 2;
70
76
  const baseIndent = (_c = (_a = linePrefix.match(/^(\s*)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _c !== void 0 ? _c : '';
@@ -85,13 +91,16 @@ const formatInlineContent = async ({ inlineContent, linePrefix, endOfLine, }) =>
85
91
  });
86
92
  // Extract the formatted value from the wrapper
87
93
  const withoutSemicolon = formattedWrapped.replace(/;\s*$/, '');
94
+ const wrappedInParentheses = withoutSemicolon.startsWith(`${wrapperPrefix}(\n`);
88
95
  let formattedProps;
89
- if (withoutSemicolon.startsWith(wrapperPrefix)) {
96
+ if (withoutSemicolon.startsWith(wrapperPrefix) && !wrappedInParentheses) {
90
97
  formattedProps = withoutSemicolon.slice(wrapperPrefix.length);
91
98
  }
92
99
  else {
93
100
  // Prettier broke the line after `=` — extract and dedent one level
94
- const lines = withoutSemicolon.split('\n').slice(1);
101
+ const lines = withoutSemicolon
102
+ .split('\n')
103
+ .slice(1, wrappedInParentheses ? -1 : undefined);
95
104
  const useTabs = prettierConfig.useTabs;
96
105
  const oneIndent = useTabs ? '\t' : ' '.repeat(tabWidth);
97
106
  formattedProps = lines
@@ -1,3 +1,4 @@
1
1
  import type { File } from '@babel/types';
2
2
  export declare const parseAst: (input: string) => File;
3
+ export declare const parseAstForReadOnly: (input: string) => File;
3
4
  export declare const serializeAst: (ast: File) => string;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.serializeAst = exports.parseAst = void 0;
36
+ exports.serializeAst = exports.parseAstForReadOnly = exports.parseAst = void 0;
37
37
  const recast = __importStar(require("recast"));
38
38
  const tsParser = __importStar(require("recast/parsers/babel-ts"));
39
39
  const imports_1 = require("../helpers/imports");
@@ -43,6 +43,10 @@ const parseAst = (input) => {
43
43
  });
44
44
  };
45
45
  exports.parseAst = parseAst;
46
+ const parseAstForReadOnly = (input) => {
47
+ return tsParser.parse(input);
48
+ };
49
+ exports.parseAstForReadOnly = parseAstForReadOnly;
46
50
  const serializeAst = (ast) => {
47
51
  const raw = recast.print(ast, {
48
52
  parser: tsParser,
@@ -1,3 +1,4 @@
1
+ import type { File } from '@babel/types';
1
2
  import { type RemovedProp, type SequencePropsNodeUpdate, type SequencePropsNodeUpdateResult, type SequencePropUpdate, updateSequencePropsAst } from '@remotion/studio-codemods';
2
3
  import type { InteractivitySchema, SequenceNodePath, VideoConfigValues } from 'remotion';
3
4
  export { type RemovedProp, type SequencePropsNodeUpdate, type SequencePropsNodeUpdateResult, type SequencePropUpdate, updateSequencePropsAst, };
@@ -6,6 +7,7 @@ type UpdateMultipleSequencePropsResult = {
6
7
  output: string;
7
8
  formatted: boolean;
8
9
  results: SequencePropsNodeUpdateResult[];
10
+ ast: File;
9
11
  };
10
12
  type UpdateSequencePropsResult = {
11
13
  output: string;
@@ -14,10 +16,11 @@ type UpdateSequencePropsResult = {
14
16
  logLine: number;
15
17
  removedProps: RemovedProp[];
16
18
  };
17
- export declare const updateMultipleSequenceProps: ({ input, changes, prettierConfigOverride, }: {
19
+ export declare const updateMultipleSequenceProps: ({ input, changes, prettierConfigOverride, ast: providedAst, }: {
18
20
  input: string;
19
21
  changes: SequencePropsNodeUpdate[];
20
22
  prettierConfigOverride: PrettierConfigOverride;
23
+ ast?: File | undefined;
21
24
  }) => Promise<UpdateMultipleSequencePropsResult>;
22
25
  export declare const updateSequenceProps: ({ input, nodePath, updates, schema, prettierConfigOverride, videoConfigValues, }: {
23
26
  input: string;
@@ -4,13 +4,57 @@ exports.updateSequenceProps = exports.updateMultipleSequenceProps = exports.upda
4
4
  const studio_codemods_1 = require("@remotion/studio-codemods");
5
5
  Object.defineProperty(exports, "updateSequencePropsAst", { enumerable: true, get: function () { return studio_codemods_1.updateSequencePropsAst; } });
6
6
  const format_file_content_1 = require("../format-file-content");
7
- const updateMultipleSequenceProps = async ({ input, changes, prettierConfigOverride, }) => {
8
- const { output: unformattedOutput, results } = (0, studio_codemods_1.updateMultipleSequenceProps)({ input, changes });
7
+ const format_inline_content_1 = require("../format-inline-content");
8
+ const updateMultipleSequenceProps = async ({ input, changes, prettierConfigOverride, ast: providedAst, }) => {
9
+ const { output: unformattedOutput, results, ast, openingElementRanges, } = (0, studio_codemods_1.updateMultipleSequenceProps)({
10
+ input,
11
+ changes,
12
+ ast: providedAst,
13
+ });
14
+ if (unformattedOutput === input) {
15
+ return { output: input, formatted: true, results, ast };
16
+ }
17
+ if ((openingElementRanges === null || openingElementRanges === void 0 ? void 0 : openingElementRanges.length) === 1) {
18
+ const [range] = openingElementRanges;
19
+ const openingElement = unformattedOutput.slice(range.start, range.end);
20
+ const formattableOpeningElement = range.selfClosing
21
+ ? openingElement
22
+ : openingElement.slice(0, -1) + ' />';
23
+ const lineStart = unformattedOutput.lastIndexOf('\n', range.start) + 1;
24
+ const linePrefix = unformattedOutput.slice(lineStart, range.start);
25
+ const { formatted: formattedOpeningElement, didFormat } = await (0, format_inline_content_1.formatInlineContent)({
26
+ inlineContent: formattableOpeningElement,
27
+ linePrefix,
28
+ endOfLine: 'lf',
29
+ prettierConfigOverride,
30
+ });
31
+ let finalOpeningElement = formattedOpeningElement;
32
+ if (!range.selfClosing) {
33
+ const slashIndex = finalOpeningElement.lastIndexOf('/>');
34
+ if (slashIndex !== finalOpeningElement.length - 2) {
35
+ throw new Error('Could not format JSX opening element');
36
+ }
37
+ const lastLineStart = finalOpeningElement.lastIndexOf('\n') + 1;
38
+ const beforeSlash = finalOpeningElement.slice(lastLineStart, slashIndex);
39
+ finalOpeningElement =
40
+ beforeSlash.trim().length === 0
41
+ ? finalOpeningElement.slice(0, slashIndex) + '>'
42
+ : finalOpeningElement.slice(0, slashIndex).trimEnd() + '>';
43
+ }
44
+ return {
45
+ output: unformattedOutput.slice(0, range.start) +
46
+ finalOpeningElement +
47
+ unformattedOutput.slice(range.end),
48
+ formatted: didFormat,
49
+ results,
50
+ ast,
51
+ };
52
+ }
9
53
  const { output, formatted } = await (0, format_file_content_1.formatFileContent)({
10
54
  input: unformattedOutput,
11
55
  prettierConfigOverride,
12
56
  });
13
- return { output, formatted, results };
57
+ return { output, formatted, results, ast };
14
58
  };
15
59
  exports.updateMultipleSequenceProps = updateMultipleSequenceProps;
16
60
  const updateSequenceProps = async ({ input, nodePath, updates, schema, prettierConfigOverride, videoConfigValues, }) => {
@@ -1202,16 +1202,14 @@ const canAddSequenceToComponent = ({ ast, exportName, }) => {
1202
1202
  return false;
1203
1203
  }
1204
1204
  };
1205
- const getComponentLocationInFile = async ({ remotionRoot, fileName, exportName, }) => {
1205
+ const getComponentLocationInFile = async ({ remotionRoot, fileName, exportName, ast: providedAst, }) => {
1206
1206
  var _a, _b;
1207
- const input = await readSourceFile({ remotionRoot, fileName });
1208
- const ast = (0, parse_ast_1.parseAst)(input);
1209
- const astForSequenceSimulation = (0, parse_ast_1.parseAst)(input);
1207
+ const ast = providedAst !== null && providedAst !== void 0 ? providedAst : (0, parse_ast_1.parseAst)(await readSourceFile({ remotionRoot, fileName }));
1210
1208
  const location = exportName === 'default'
1211
1209
  ? findDefaultExportLocation(ast)
1212
1210
  : findLocalSymbolLocation({ ast, name: exportName });
1213
1211
  const canAddSequence = canAddSequenceToComponent({
1214
- ast: astForSequenceSimulation,
1212
+ ast,
1215
1213
  exportName,
1216
1214
  });
1217
1215
  return {
@@ -1241,6 +1239,7 @@ const getComponentLocationRecursively = async ({ remotionRoot, fileName, exportN
1241
1239
  remotionRoot,
1242
1240
  fileName,
1243
1241
  exportName,
1242
+ ast,
1244
1243
  });
1245
1244
  }
1246
1245
  const reExportTargets = findReExportTargets({
@@ -1271,6 +1270,7 @@ const getComponentLocationRecursively = async ({ remotionRoot, fileName, exportN
1271
1270
  remotionRoot,
1272
1271
  fileName,
1273
1272
  exportName,
1273
+ ast,
1274
1274
  });
1275
1275
  }
1276
1276
  finally {
@@ -1283,7 +1283,7 @@ const resolveCompositionComponentWithFile = async ({ remotionRoot, compositionFi
1283
1283
  remotionRoot,
1284
1284
  fileName: compositionFileName,
1285
1285
  });
1286
- const ast = (0, parse_ast_1.parseAst)(input);
1286
+ const ast = (0, parse_ast_1.parseAstForReadOnly)(input);
1287
1287
  const compositionElement = findCompositionElement({ ast, compositionId });
1288
1288
  if (!compositionElement) {
1289
1289
  throw new Error(`Could not find composition "${compositionId}"`);
@@ -10,6 +10,7 @@ const video_config_values_1 = require("../../helpers/video-config-values");
10
10
  const can_update_sequence_props_1 = require("./can-update-sequence-props");
11
11
  const staticStatus = (codeValue) => ({
12
12
  status: 'static',
13
+ keyframeDisplayOffsetAdjustment: null,
13
14
  codeValue,
14
15
  });
15
16
  const findEffectsAttr = (jsx) => {
@@ -110,6 +111,7 @@ const getPropsFromObjectExpression = ({ ast, objExpr, keys, videoConfigValues, }
110
111
  out[key] = numericExpression
111
112
  ? {
112
113
  status: 'static',
114
+ keyframeDisplayOffsetAdjustment: null,
113
115
  codeValue: numericExpression.value,
114
116
  ...(numericExpression.type === 'literal'
115
117
  ? {}
@@ -120,6 +122,7 @@ const getPropsFromObjectExpression = ({ ast, objExpr, keys, videoConfigValues, }
120
122
  }
121
123
  out[key] = {
122
124
  status: 'static',
125
+ keyframeDisplayOffsetAdjustment: null,
123
126
  codeValue: (0, can_update_sequence_props_1.extractStaticValue)(valueExpr),
124
127
  };
125
128
  }
@@ -2,6 +2,7 @@ import type { Expression, File, JSXElement, JSXOpeningElement } from '@babel/typ
2
2
  import type { SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
3
3
  import type { CanUpdateSequencePropsResponseTrue, CanUpdateSequencePropStatus, SequenceNodePath, VideoConfigValues } from 'remotion';
4
4
  import { type VideoConfigIdentifierValues } from '../../helpers/video-config-values';
5
+ export declare const takeCachedSequencePropsStatusAst: (fileContents: string) => File | null;
5
6
  type StaticValueOptions = {
6
7
  allowSpecialValues: boolean;
7
8
  };
@@ -24,6 +25,31 @@ export declare const hasJsxChildrenAttribute: (jsxElement: JSXOpeningElement) =>
24
25
  export declare const getStaticJsxTextContent: (jsxElement: JSXElement) => StaticJsxTextContent | null;
25
26
  export declare const findNodePathForJsxElement: (ast: File, target: JSXOpeningElement) => SequenceNodePath | null;
26
27
  export declare const lineColumnToNodePath: (ast: File, targetLine: number, targetColumn?: number | undefined, fileContents?: string | undefined) => SequenceNodePath | null;
28
+ export declare const lineColumnsToNodePaths: ({ ast, targets, fileContents, }: {
29
+ ast: File;
30
+ targets: {
31
+ line: number;
32
+ column: number;
33
+ }[];
34
+ fileContents: string;
35
+ }) => (SequenceNodePath | null)[];
36
+ export declare const resolveSequencePropsNodePathsFromFilename: ({ fileName, targets, remotionRoot, }: {
37
+ fileName: string;
38
+ targets: {
39
+ line: number;
40
+ column: number;
41
+ }[];
42
+ remotionRoot: string;
43
+ }) => (SequenceNodePath | null)[];
44
+ export declare const computeSequencePropsStatusFromAst: ({ ast, nodePath, componentIdentity, keys, assetKeys, effects, videoConfigValues, }: {
45
+ ast: File;
46
+ nodePath: SequenceNodePath;
47
+ componentIdentity: string | null;
48
+ keys: string[];
49
+ assetKeys?: string[] | undefined;
50
+ effects: string[][];
51
+ videoConfigValues: VideoConfigValues | null;
52
+ }) => CanUpdateSequencePropsResponseTrue;
27
53
  export declare const computeSequencePropsStatusFromContent: ({ fileContents, nodePath, componentIdentity, keys, assetKeys, effects, videoConfigValues, }: {
28
54
  fileContents: string;
29
55
  nodePath: SequenceNodePath;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.computeSequencePropsStatusFromFilenameByLocation = exports.computeSequencePropsStatus = exports.computeSequencePropsStatusFromContent = exports.lineColumnToNodePath = exports.findNodePathForJsxElement = exports.getStaticJsxTextContent = exports.hasJsxChildrenAttribute = exports.getStaticJsxChildrenAttribute = exports.findJsxElementNodeAtNodePath = exports.findJsxElementAtNodePath = exports.findJsxElementPathAtNodePath = exports.getNodePathForRecastPath = exports.getComputedStatus = exports.extractStaticValue = exports.isStaticValue = void 0;
36
+ exports.computeSequencePropsStatusFromFilenameByLocation = exports.computeSequencePropsStatus = exports.computeSequencePropsStatusFromContent = exports.computeSequencePropsStatusFromAst = exports.resolveSequencePropsNodePathsFromFilename = exports.lineColumnsToNodePaths = exports.lineColumnToNodePath = exports.findNodePathForJsxElement = exports.getStaticJsxTextContent = exports.hasJsxChildrenAttribute = exports.getStaticJsxChildrenAttribute = exports.findJsxElementNodeAtNodePath = exports.findJsxElementAtNodePath = exports.findJsxElementPathAtNodePath = exports.getNodePathForRecastPath = exports.getComputedStatus = exports.extractStaticValue = exports.isStaticValue = exports.takeCachedSequencePropsStatusAst = void 0;
37
37
  const node_fs_1 = require("node:fs");
38
38
  const renderer_1 = require("@remotion/renderer");
39
39
  const studio_shared_1 = require("@remotion/studio-shared");
@@ -55,8 +55,41 @@ const can_update_effect_props_1 = require("./can-update-effect-props");
55
55
  // computation is read-only, so all subscribers can share one parsed snapshot
56
56
  // until the notification burst has finished.
57
57
  let cachedSequencePropsStatusAst = null;
58
+ // A subsequent save can consume the last read-only snapshot if the file has
59
+ // not changed. The save mutates the AST, so it must only be reused once.
60
+ let reusableSequencePropsStatusAst = null;
61
+ const getCachedSequencePropsStatusAst = (fileContents) => {
62
+ if ((cachedSequencePropsStatusAst === null || cachedSequencePropsStatusAst === void 0 ? void 0 : cachedSequencePropsStatusAst.fileContents) !== fileContents) {
63
+ const snapshot = {
64
+ fileContents,
65
+ ast: (0, parse_ast_1.parseAst)(fileContents),
66
+ videoConfigIdentifierValues: new Map(),
67
+ };
68
+ cachedSequencePropsStatusAst = snapshot;
69
+ reusableSequencePropsStatusAst = snapshot;
70
+ queueMicrotask(() => {
71
+ if (cachedSequencePropsStatusAst === snapshot) {
72
+ cachedSequencePropsStatusAst = null;
73
+ }
74
+ });
75
+ }
76
+ return cachedSequencePropsStatusAst;
77
+ };
78
+ const takeCachedSequencePropsStatusAst = (fileContents) => {
79
+ if ((reusableSequencePropsStatusAst === null || reusableSequencePropsStatusAst === void 0 ? void 0 : reusableSequencePropsStatusAst.fileContents) !== fileContents) {
80
+ return null;
81
+ }
82
+ const { ast } = reusableSequencePropsStatusAst;
83
+ reusableSequencePropsStatusAst = null;
84
+ if ((cachedSequencePropsStatusAst === null || cachedSequencePropsStatusAst === void 0 ? void 0 : cachedSequencePropsStatusAst.ast) === ast) {
85
+ cachedSequencePropsStatusAst = null;
86
+ }
87
+ return ast;
88
+ };
89
+ exports.takeCachedSequencePropsStatusAst = takeCachedSequencePropsStatusAst;
58
90
  const staticStatus = (codeValue, numericExpression) => ({
59
91
  status: 'static',
92
+ keyframeDisplayOffsetAdjustment: null,
60
93
  codeValue,
61
94
  ...(numericExpression === null || numericExpression.type === 'literal'
62
95
  ? {}
@@ -389,13 +422,20 @@ const getInterpolationKeyframes = (node, ast, videoConfigValues) => {
389
422
  const outputArg = callExpression.arguments[2];
390
423
  if (!frameArg ||
391
424
  frameArg.type === 'SpreadElement' ||
392
- !isCurrentFrameIdentifier(frameArg, ast) ||
393
425
  !inputArg ||
394
426
  !outputArg ||
395
427
  inputArg.type !== 'ArrayExpression' ||
396
428
  outputArg.type !== 'ArrayExpression') {
397
429
  return undefined;
398
430
  }
431
+ const frameDisplayOffset = getCurrentFrameDisplayOffsetAdjustment({
432
+ node: frameArg,
433
+ ast,
434
+ videoConfigValues,
435
+ });
436
+ if (frameDisplayOffset === null) {
437
+ return undefined;
438
+ }
399
439
  if (inputArg.elements.length !== outputArg.elements.length) {
400
440
  return undefined;
401
441
  }
@@ -436,39 +476,200 @@ const getInterpolationKeyframes = (node, ast, videoConfigValues) => {
436
476
  clamping: metadata.clamping,
437
477
  posterize: metadata.posterize,
438
478
  output: metadata.output,
479
+ keyframeDisplayOffsetAdjustment: frameDisplayOffset.hasEnclosingElement
480
+ ? frameDisplayOffset.adjustment
481
+ : null,
439
482
  };
440
483
  };
441
- const isUseCurrentFrameCall = (node) => {
442
- return (node.type === 'CallExpression' &&
443
- node.callee.type === 'Identifier' &&
444
- node.callee.name === 'useCurrentFrame' &&
445
- node.arguments.length === 0);
484
+ const getRemotionImportNames = ({ ast, importedName, }) => {
485
+ var _a;
486
+ var _b, _c;
487
+ const direct = new Set();
488
+ const namespaces = new Set();
489
+ for (const statement of ast.program.body) {
490
+ if (statement.type !== 'ImportDeclaration' ||
491
+ statement.source.value !== 'remotion') {
492
+ continue;
493
+ }
494
+ for (const specifier of (_b = statement.specifiers) !== null && _b !== void 0 ? _b : []) {
495
+ if (specifier.type === 'ImportSpecifier' &&
496
+ ((specifier.imported.type === 'Identifier' &&
497
+ specifier.imported.name === importedName) ||
498
+ (specifier.imported.type === 'StringLiteral' &&
499
+ specifier.imported.value === importedName))) {
500
+ direct.add((_c = (_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) !== null && _c !== void 0 ? _c : importedName);
501
+ }
502
+ if (specifier.type === 'ImportNamespaceSpecifier') {
503
+ namespaces.add(specifier.local.name);
504
+ }
505
+ }
506
+ }
507
+ return { direct, namespaces };
508
+ };
509
+ const isUseCurrentFrameCall = (node, ast) => {
510
+ if (node.type !== 'CallExpression' || node.arguments.length !== 0) {
511
+ return false;
512
+ }
513
+ const imports = getRemotionImportNames({
514
+ ast,
515
+ importedName: 'useCurrentFrame',
516
+ });
517
+ if (node.callee.type === 'Identifier') {
518
+ return imports.direct.has(node.callee.name);
519
+ }
520
+ return (node.callee.type === 'MemberExpression' &&
521
+ !node.callee.computed &&
522
+ node.callee.object.type === 'Identifier' &&
523
+ node.callee.property.type === 'Identifier' &&
524
+ imports.namespaces.has(node.callee.object.name) &&
525
+ node.callee.property.name === 'useCurrentFrame');
526
+ };
527
+ const findNodePath = (ast, target) => {
528
+ let found = null;
529
+ recast.types.visit(ast, {
530
+ visitNode(p) {
531
+ if (p.node === target) {
532
+ found = p;
533
+ return false;
534
+ }
535
+ return this.traverse(p);
536
+ },
537
+ });
538
+ return found;
539
+ };
540
+ const getJsxNumericAttribute = ({ openingElement, name, defaultValue, videoConfigValues, }) => {
541
+ var _a, _b;
542
+ var _c;
543
+ if (openingElement.attributes.some((attribute) => attribute.type === 'JSXSpreadAttribute')) {
544
+ return null;
545
+ }
546
+ for (let index = openingElement.attributes.length - 1; index >= 0; index--) {
547
+ const attribute = openingElement.attributes[index];
548
+ if (attribute.type !== 'JSXAttribute' ||
549
+ attribute.name.type !== 'JSXIdentifier' ||
550
+ attribute.name.name !== name) {
551
+ continue;
552
+ }
553
+ if (((_a = attribute.value) === null || _a === void 0 ? void 0 : _a.type) !== 'JSXExpressionContainer' ||
554
+ attribute.value.expression.type === 'JSXEmptyExpression') {
555
+ return null;
556
+ }
557
+ return ((_c = (_b = (0, video_config_numeric_expression_1.parseVideoConfigNumericExpression)({
558
+ node: attribute.value.expression,
559
+ videoConfigValues,
560
+ })) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : null);
561
+ }
562
+ return defaultValue;
563
+ };
564
+ const getFrameDisplayOffsetAdjustmentBetweenPaths = ({ startPath, endPath, videoConfigValues, }) => {
565
+ let current = startPath;
566
+ let hasSeenControlledElement = false;
567
+ let hasEnclosingElement = false;
568
+ let adjustment = 0;
569
+ while (current && current.value !== endPath.value) {
570
+ const currentNode = current.value;
571
+ if (currentNode.type === 'FunctionDeclaration' ||
572
+ currentNode.type === 'FunctionExpression' ||
573
+ currentNode.type === 'ArrowFunctionExpression') {
574
+ return null;
575
+ }
576
+ if (currentNode.type === 'JSXElement') {
577
+ if (!hasSeenControlledElement) {
578
+ hasSeenControlledElement = true;
579
+ }
580
+ else {
581
+ hasEnclosingElement = true;
582
+ // Sequence-backed built-ins and userland components can both shift
583
+ // their children, so the prop names are the semantic boundary.
584
+ const from = getJsxNumericAttribute({
585
+ openingElement: currentNode.openingElement,
586
+ name: 'from',
587
+ defaultValue: 0,
588
+ videoConfigValues,
589
+ });
590
+ const trimBefore = getJsxNumericAttribute({
591
+ openingElement: currentNode.openingElement,
592
+ name: 'trimBefore',
593
+ defaultValue: 0,
594
+ videoConfigValues,
595
+ });
596
+ if (from === null || trimBefore === null) {
597
+ return null;
598
+ }
599
+ adjustment -= from - trimBefore;
600
+ }
601
+ }
602
+ current = current.parentPath;
603
+ }
604
+ return current ? { adjustment, hasEnclosingElement } : null;
605
+ };
606
+ const getDefaultFrameDisplayOffsetAdjustment = ({ jsxElement, ast, videoConfigValues, }) => {
607
+ var _a;
608
+ const jsxPath = findNodePath(ast, jsxElement);
609
+ if (!jsxPath) {
610
+ return null;
611
+ }
612
+ let functionPath = jsxPath.parentPath;
613
+ while (functionPath) {
614
+ const node = functionPath.value;
615
+ if (node.type === 'FunctionDeclaration' ||
616
+ node.type === 'FunctionExpression' ||
617
+ node.type === 'ArrowFunctionExpression') {
618
+ break;
619
+ }
620
+ functionPath = functionPath.parentPath;
621
+ }
622
+ if (!functionPath) {
623
+ return null;
624
+ }
625
+ const result = getFrameDisplayOffsetAdjustmentBetweenPaths({
626
+ startPath: jsxPath,
627
+ endPath: functionPath,
628
+ videoConfigValues,
629
+ });
630
+ return (_a = result === null || result === void 0 ? void 0 : result.adjustment) !== null && _a !== void 0 ? _a : null;
446
631
  };
447
- const isCurrentFrameIdentifier = (node, ast) => {
632
+ const getCurrentFrameDisplayOffsetAdjustment = ({ node, ast, videoConfigValues, }) => {
633
+ var _a;
448
634
  if (node.type === 'TSAsExpression') {
449
- return isCurrentFrameIdentifier(node.expression, ast);
635
+ return getCurrentFrameDisplayOffsetAdjustment({
636
+ node: node.expression,
637
+ ast,
638
+ videoConfigValues,
639
+ });
450
640
  }
451
641
  if (node.type !== 'Identifier') {
452
- return false;
642
+ return null;
453
643
  }
454
- let hasUseCurrentFrameDeclaration = false;
455
- let hasOtherDeclaration = false;
456
- recast.types.visit(ast, {
644
+ const nodePath = findNodePath(ast, node);
645
+ const bindingScope = (_a = nodePath === null || nodePath === void 0 ? void 0 : nodePath.scope) === null || _a === void 0 ? void 0 : _a.lookup(node.name);
646
+ if (!nodePath || !bindingScope) {
647
+ return null;
648
+ }
649
+ let matchingDeclarations = 0;
650
+ let isCurrentFrameBinding = false;
651
+ recast.types.visit(bindingScope.path, {
457
652
  visitVariableDeclarator(p) {
653
+ var _a;
458
654
  const { id, init } = p.node;
459
- if (id.type !== 'Identifier' || id.name !== node.name) {
655
+ if (id.type !== 'Identifier' ||
656
+ id.name !== node.name ||
657
+ ((_a = p.scope.lookup(node.name)) === null || _a === void 0 ? void 0 : _a.path.node) !== bindingScope.path.node) {
460
658
  return this.traverse(p);
461
659
  }
462
- if (init && isUseCurrentFrameCall(init)) {
463
- hasUseCurrentFrameDeclaration = true;
464
- }
465
- else {
466
- hasOtherDeclaration = true;
467
- }
660
+ matchingDeclarations++;
661
+ isCurrentFrameBinding = Boolean(init && isUseCurrentFrameCall(init, ast));
468
662
  return false;
469
663
  },
470
664
  });
471
- return hasUseCurrentFrameDeclaration && !hasOtherDeclaration;
665
+ if (matchingDeclarations !== 1 || !isCurrentFrameBinding) {
666
+ return null;
667
+ }
668
+ return getFrameDisplayOffsetAdjustmentBetweenPaths({
669
+ startPath: nodePath,
670
+ endPath: bindingScope.path,
671
+ videoConfigValues,
672
+ });
472
673
  };
473
674
  const getComputedStatus = (node, ast, videoConfigValues) => {
474
675
  const interpolation = getInterpolationKeyframes(node, ast, videoConfigValues);
@@ -478,6 +679,7 @@ const getComputedStatus = (node, ast, videoConfigValues) => {
478
679
  return {
479
680
  status: 'keyframed',
480
681
  interpolationFunction: interpolation.interpolationFunction,
682
+ keyframeDisplayOffsetAdjustment: interpolation.keyframeDisplayOffsetAdjustment,
481
683
  keyframes: interpolation.keyframes,
482
684
  easing: interpolation.easing,
483
685
  clamping: interpolation.clamping,
@@ -648,8 +850,7 @@ const findNodePathForJsxElement = (ast, target) => {
648
850
  };
649
851
  exports.findNodePathForJsxElement = findNodePathForJsxElement;
650
852
  const RECAST_TAB_WIDTH = 4;
651
- const sourceColumnToRecastColumn = ({ fileContents, line, column, }) => {
652
- const sourceLine = fileContents.split('\n')[line - 1];
853
+ const sourceColumnToRecastColumn = ({ sourceLine, column, }) => {
653
854
  if (sourceLine === undefined) {
654
855
  return column;
655
856
  }
@@ -671,8 +872,7 @@ const lineColumnToNodePath = (ast, targetLine, targetColumn, fileContents) => {
671
872
  const recastTargetColumn = targetColumn === undefined || fileContents === undefined
672
873
  ? targetColumn
673
874
  : sourceColumnToRecastColumn({
674
- fileContents,
675
- line: targetLine,
875
+ sourceLine: fileContents.split('\n')[targetLine - 1],
676
876
  column: targetColumn,
677
877
  });
678
878
  recast.types.visit(ast, {
@@ -694,6 +894,55 @@ const lineColumnToNodePath = (ast, targetLine, targetColumn, fileContents) => {
694
894
  return (_a = lineMatches.at(-1)) !== null && _a !== void 0 ? _a : null;
695
895
  };
696
896
  exports.lineColumnToNodePath = lineColumnToNodePath;
897
+ const lineColumnsToNodePaths = ({ ast, targets, fileContents, }) => {
898
+ const sourceLines = fileContents.split('\n');
899
+ const targetIndicesByLine = new Map();
900
+ const recastTargetColumns = targets.map(({ line, column }, index) => {
901
+ var _a;
902
+ const indices = (_a = targetIndicesByLine.get(line)) !== null && _a !== void 0 ? _a : [];
903
+ indices.push(index);
904
+ targetIndicesByLine.set(line, indices);
905
+ return sourceColumnToRecastColumn({
906
+ sourceLine: sourceLines[line - 1],
907
+ column,
908
+ });
909
+ });
910
+ const lineMatches = targets.map(() => null);
911
+ const exactMatches = targets.map(() => null);
912
+ const exactMatchCounts = targets.map(() => 0);
913
+ recast.types.visit(ast, {
914
+ visitJSXOpeningElement(p) {
915
+ var _a, _b;
916
+ const { node } = p;
917
+ const line = (_a = node.loc) === null || _a === void 0 ? void 0 : _a.start.line;
918
+ const targetIndices = line ? targetIndicesByLine.get(line) : undefined;
919
+ if (targetIndices) {
920
+ const nodePath = (0, exports.getNodePathForRecastPath)(p, ast);
921
+ for (const index of targetIndices) {
922
+ lineMatches[index] = nodePath;
923
+ if (((_b = node.loc) === null || _b === void 0 ? void 0 : _b.start.column) === recastTargetColumns[index]) {
924
+ exactMatches[index] = nodePath;
925
+ exactMatchCounts[index]++;
926
+ }
927
+ }
928
+ }
929
+ return this.traverse(p);
930
+ },
931
+ });
932
+ return targets.map((_, index) => exactMatchCounts[index] === 1 ? exactMatches[index] : lineMatches[index]);
933
+ };
934
+ exports.lineColumnsToNodePaths = lineColumnsToNodePaths;
935
+ const resolveSequencePropsNodePathsFromFilename = ({ fileName, targets, remotionRoot, }) => {
936
+ const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
937
+ remotionRoot,
938
+ fileName,
939
+ action: 'read',
940
+ });
941
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
942
+ const { ast } = getCachedSequencePropsStatusAst(fileContents);
943
+ return (0, exports.lineColumnsToNodePaths)({ ast, targets, fileContents });
944
+ };
945
+ exports.resolveSequencePropsNodePathsFromFilename = resolveSequencePropsNodePathsFromFilename;
697
946
  const PIXEL_VALUE_REGEX = /^-?\d+(\.\d+)?px$/;
698
947
  const isSupportedTranslateValue = (value) => {
699
948
  const parts = value.split(/\s+/);
@@ -924,32 +1173,8 @@ const computeSequenceOnlyPropsRecord = ({ jsxElement, jsxElementNode, ast, keys,
924
1173
  }
925
1174
  return filteredProps;
926
1175
  };
927
- const computeSequencePropsStatusFromContent = ({ fileContents, nodePath, componentIdentity, keys, assetKeys = [], effects, videoConfigValues, }) => {
1176
+ const computeSequencePropsStatusFromAstAndIdentifiers = ({ ast, nodePath, componentIdentity, keys, assetKeys, effects, videoConfigIdentifierValues, }) => {
928
1177
  var _a;
929
- if ((cachedSequencePropsStatusAst === null || cachedSequencePropsStatusAst === void 0 ? void 0 : cachedSequencePropsStatusAst.fileContents) !== fileContents) {
930
- cachedSequencePropsStatusAst = null;
931
- const snapshot = {
932
- fileContents,
933
- ast: (0, parse_ast_1.parseAst)(fileContents),
934
- videoConfigIdentifierValues: new Map(),
935
- };
936
- cachedSequencePropsStatusAst = snapshot;
937
- queueMicrotask(() => {
938
- if (cachedSequencePropsStatusAst === snapshot) {
939
- cachedSequencePropsStatusAst = null;
940
- }
941
- });
942
- }
943
- const { ast } = cachedSequencePropsStatusAst;
944
- const videoConfigCacheKey = JSON.stringify(videoConfigValues);
945
- let videoConfigIdentifierValues = cachedSequencePropsStatusAst.videoConfigIdentifierValues.get(videoConfigCacheKey);
946
- if (videoConfigIdentifierValues === undefined) {
947
- videoConfigIdentifierValues = (0, video_config_values_1.getVideoConfigIdentifierValues)({
948
- ast,
949
- videoConfigValues,
950
- });
951
- cachedSequencePropsStatusAst.videoConfigIdentifierValues.set(videoConfigCacheKey, videoConfigIdentifierValues);
952
- }
953
1178
  const jsxElementNode = (0, exports.findJsxElementNodeAtNodePath)(ast, nodePath);
954
1179
  const jsxElement = (_a = jsxElementNode === null || jsxElementNode === void 0 ? void 0 : jsxElementNode.openingElement) !== null && _a !== void 0 ? _a : null;
955
1180
  if (!jsxElement || !jsxElementNode) {
@@ -975,12 +1200,80 @@ const computeSequencePropsStatusFromContent = ({ fileContents, nodePath, compone
975
1200
  effects,
976
1201
  videoConfigValues: videoConfigIdentifierValues,
977
1202
  });
1203
+ const defaultKeyframeDisplayOffsetAdjustment = getDefaultFrameDisplayOffsetAdjustment({
1204
+ jsxElement,
1205
+ ast,
1206
+ videoConfigValues: videoConfigIdentifierValues,
1207
+ });
1208
+ const addDefaultKeyframeDisplayOffsetAdjustment = (status) => {
1209
+ if (status.status !== 'static') {
1210
+ return status;
1211
+ }
1212
+ if (defaultKeyframeDisplayOffsetAdjustment === null) {
1213
+ return computedStatus();
1214
+ }
1215
+ if (defaultKeyframeDisplayOffsetAdjustment === 0) {
1216
+ return status;
1217
+ }
1218
+ return {
1219
+ ...status,
1220
+ keyframeDisplayOffsetAdjustment: defaultKeyframeDisplayOffsetAdjustment,
1221
+ };
1222
+ };
978
1223
  return {
979
1224
  canUpdate: true,
980
- props: filteredProps,
981
- effects: effectsStatuses,
1225
+ props: Object.fromEntries(Object.entries(filteredProps).map(([key, status]) => [
1226
+ key,
1227
+ addDefaultKeyframeDisplayOffsetAdjustment(status),
1228
+ ])),
1229
+ effects: effectsStatuses.map((effectStatus) => effectStatus.canUpdate
1230
+ ? {
1231
+ ...effectStatus,
1232
+ props: Object.fromEntries(Object.entries(effectStatus.props).map(([key, status]) => [
1233
+ key,
1234
+ addDefaultKeyframeDisplayOffsetAdjustment(status),
1235
+ ])),
1236
+ }
1237
+ : effectStatus),
982
1238
  };
983
1239
  };
1240
+ const computeSequencePropsStatusFromAst = ({ ast, nodePath, componentIdentity, keys, assetKeys = [], effects, videoConfigValues, }) => {
1241
+ return computeSequencePropsStatusFromAstAndIdentifiers({
1242
+ ast,
1243
+ nodePath,
1244
+ componentIdentity,
1245
+ keys,
1246
+ assetKeys,
1247
+ effects,
1248
+ videoConfigIdentifierValues: (0, video_config_values_1.getVideoConfigIdentifierValues)({
1249
+ ast,
1250
+ videoConfigValues,
1251
+ }),
1252
+ });
1253
+ };
1254
+ exports.computeSequencePropsStatusFromAst = computeSequencePropsStatusFromAst;
1255
+ const computeSequencePropsStatusFromContent = ({ fileContents, nodePath, componentIdentity, keys, assetKeys = [], effects, videoConfigValues, }) => {
1256
+ const cachedAst = getCachedSequencePropsStatusAst(fileContents);
1257
+ const { ast } = cachedAst;
1258
+ const videoConfigCacheKey = JSON.stringify(videoConfigValues);
1259
+ let videoConfigIdentifierValues = cachedAst.videoConfigIdentifierValues.get(videoConfigCacheKey);
1260
+ if (videoConfigIdentifierValues === undefined) {
1261
+ videoConfigIdentifierValues = (0, video_config_values_1.getVideoConfigIdentifierValues)({
1262
+ ast,
1263
+ videoConfigValues,
1264
+ });
1265
+ cachedAst.videoConfigIdentifierValues.set(videoConfigCacheKey, videoConfigIdentifierValues);
1266
+ }
1267
+ return computeSequencePropsStatusFromAstAndIdentifiers({
1268
+ ast,
1269
+ nodePath,
1270
+ componentIdentity,
1271
+ keys,
1272
+ assetKeys,
1273
+ effects,
1274
+ videoConfigIdentifierValues,
1275
+ });
1276
+ };
984
1277
  exports.computeSequencePropsStatusFromContent = computeSequencePropsStatusFromContent;
985
1278
  const computeSequencePropsStatus = ({ fileName, nodePath, componentIdentity, keys, assetKeys = [], effects, remotionRoot, videoConfigValues, }) => {
986
1279
  const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
@@ -1008,7 +1301,7 @@ const computeSequencePropsStatusFromFilenameByLocation = ({ fileName, line, colu
1008
1301
  action: 'read',
1009
1302
  });
1010
1303
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
1011
- const ast = (0, parse_ast_1.parseAst)(fileContents);
1304
+ const { ast } = getCachedSequencePropsStatusAst(fileContents);
1012
1305
  const resolvedNodePath = (0, exports.lineColumnToNodePath)(ast, line, column, fileContents);
1013
1306
  if (!resolvedNodePath) {
1014
1307
  return {
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getDefaultEditorInfoHandler = void 0;
4
+ const custom_editor_1 = require("../../helpers/custom-editor");
4
5
  const editor_registry_1 = require("../../helpers/editor-registry");
5
6
  const getDefaultEditorInfoHandler = async ({ getDefaultEditor }) => {
6
7
  const installedEditors = await (0, editor_registry_1.getAvailableEditors)();
7
8
  const configuredEditor = getDefaultEditor();
8
- const customEditor = configuredEditor && typeof configuredEditor === 'object'
9
+ const customEditor = configuredEditor &&
10
+ typeof configuredEditor === 'object' &&
11
+ (0, custom_editor_1.resolveCustomEditorExecutable)(configuredEditor)
9
12
  ? configuredEditor
10
13
  : null;
11
14
  return {
@@ -190,6 +190,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
190
190
  }
191
191
  const snapshots = [];
192
192
  const outputByPath = new Map();
193
+ const statusAstByPath = new Map();
193
194
  const resultByIndex = new Map();
194
195
  const updatedNodePaths = new Map();
195
196
  const sequenceKeyframeLogs = [];
@@ -198,14 +199,20 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
198
199
  for (const [absolutePath, group] of editGroups) {
199
200
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
200
201
  let output = fileContents;
202
+ const cachedStatusAst = group.edits.length > 0
203
+ ? (0, can_update_sequence_props_1.takeCachedSequencePropsStatusAst)(fileContents)
204
+ : null;
205
+ let sequencePropsAst = null;
201
206
  let firstLogLine = Number.POSITIVE_INFINITY;
202
207
  if (group.edits.length > 0) {
203
- const { output: sequencePropsOutput, formatted, results: updateResults, } = await (0, update_sequence_props_1.updateMultipleSequenceProps)({
208
+ const { output: sequencePropsOutput, formatted, results: updateResults, ast, } = await (0, update_sequence_props_1.updateMultipleSequenceProps)({
204
209
  input: output,
205
210
  changes: group.edits.map(exports.convertSequencePropEditToCodemodChange),
206
211
  prettierConfigOverride: null,
212
+ ast: cachedStatusAst !== null && cachedStatusAst !== void 0 ? cachedStatusAst : undefined,
207
213
  });
208
214
  output = sequencePropsOutput;
215
+ sequencePropsAst = ast;
209
216
  const firstUpdate = updateResults[0];
210
217
  if (firstUpdate) {
211
218
  firstLogLine = Math.min(firstLogLine, firstUpdate.logLine);
@@ -365,6 +372,13 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
365
372
  });
366
373
  }
367
374
  }
375
+ if (sequencePropsAst &&
376
+ group.captionPatches.length === 0 &&
377
+ group.addedKeyframes.length === 0 &&
378
+ group.movedSequenceKeyframes.length === 0 &&
379
+ group.effectKeyframes.length === 0) {
380
+ statusAstByPath.set(absolutePath, sequencePropsAst);
381
+ }
368
382
  outputByPath.set(absolutePath, output);
369
383
  snapshots.push({
370
384
  filePath: absolutePath,
@@ -481,15 +495,24 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
481
495
  if (!output) {
482
496
  throw new Error('Could not compute sequence prop edit status');
483
497
  }
484
- const newStatus = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
485
- fileContents: output,
498
+ const statusInput = {
486
499
  keys: (0, studio_shared_1.getAllSchemaKeys)(target.schema),
487
500
  assetKeys: (0, studio_shared_1.getAssetSchemaKeys)(target.schema),
488
501
  nodePath: (_a = updatedNodePaths.get(`${absolutePath}:${JSON.stringify(target.nodePath.nodePath)}`)) !== null && _a !== void 0 ? _a : target.nodePath.nodePath,
489
502
  componentIdentity: null,
490
503
  effects: [],
491
504
  videoConfigValues: target.nodePath.videoConfigValues,
492
- });
505
+ };
506
+ const statusAst = statusAstByPath.get(absolutePath);
507
+ const newStatus = statusAst
508
+ ? (0, can_update_sequence_props_1.computeSequencePropsStatusFromAst)({
509
+ ...statusInput,
510
+ ast: statusAst,
511
+ })
512
+ : (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
513
+ ...statusInput,
514
+ fileContents: output,
515
+ });
493
516
  return {
494
517
  fileName: target.fileName,
495
518
  nodePath: target.nodePath,
@@ -1,3 +1,3 @@
1
- import type { SubscribeToSequencePropsRequest, SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
1
+ import type { SubscribeToSequencePropsBatchRequest, SubscribeToSequencePropsBatchResponse } from '@remotion/studio-shared';
2
2
  import type { ApiHandler } from '../api-types';
3
- export declare const subscribeToSequenceProps: ApiHandler<SubscribeToSequencePropsRequest, SubscribeToSequencePropsResponse>;
3
+ export declare const subscribeToSequenceProps: ApiHandler<SubscribeToSequencePropsBatchRequest, SubscribeToSequencePropsBatchResponse>;
@@ -2,12 +2,43 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.subscribeToSequenceProps = void 0;
4
4
  const sequence_props_watchers_1 = require("../sequence-props-watchers");
5
- const subscribeToSequenceProps = ({ input: { fileName, line, column, nodePath, componentIdentity, keys, assetKeys = [], effects, clientId, videoConfigValues, }, remotionRoot, logLevel, }) => {
6
- const result = (0, sequence_props_watchers_1.subscribeToSequencePropsWatchers)({
5
+ const can_update_sequence_props_1 = require("./can-update-sequence-props");
6
+ const subscribeToSequenceProps = ({ input, remotionRoot, logLevel }) => {
7
+ var _a, _b;
8
+ const requests = (_a = input.requests) !== null && _a !== void 0 ? _a : [input];
9
+ const unresolvedByFile = new Map();
10
+ for (const [index, request] of requests.entries()) {
11
+ if (request.nodePath !== null) {
12
+ continue;
13
+ }
14
+ const unresolved = (_b = unresolvedByFile.get(request.fileName)) !== null && _b !== void 0 ? _b : [];
15
+ unresolved.push({ index, line: request.line, column: request.column });
16
+ unresolvedByFile.set(request.fileName, unresolved);
17
+ }
18
+ const resolvedNodePaths = new Map();
19
+ for (const [fileName, unresolved] of unresolvedByFile) {
20
+ try {
21
+ const nodePaths = (0, can_update_sequence_props_1.resolveSequencePropsNodePathsFromFilename)({
22
+ fileName,
23
+ targets: unresolved,
24
+ remotionRoot,
25
+ });
26
+ for (const [index, nodePath] of nodePaths.entries()) {
27
+ if (nodePath) {
28
+ resolvedNodePaths.set(unresolved[index].index, nodePath);
29
+ }
30
+ }
31
+ }
32
+ catch (_c) {
33
+ // Let each subscription produce its usual not-found response.
34
+ }
35
+ }
36
+ const results = requests.map(({ fileName, line, column, nodePath, componentIdentity, keys, assetKeys = [], effects, clientId, videoConfigValues, }, index) => (0, sequence_props_watchers_1.subscribeToSequencePropsWatchers)({
7
37
  fileName,
8
38
  line,
9
39
  column,
10
40
  nodePath,
41
+ resolvedNodePath: resolvedNodePaths.get(index),
11
42
  componentIdentity,
12
43
  keys,
13
44
  assetKeys,
@@ -16,7 +47,10 @@ const subscribeToSequenceProps = ({ input: { fileName, line, column, nodePath, c
16
47
  clientId,
17
48
  videoConfigValues,
18
49
  logLevel,
50
+ }));
51
+ return Promise.resolve({
52
+ ...results[0],
53
+ results,
19
54
  });
20
- return Promise.resolve(result);
21
55
  };
22
56
  exports.subscribeToSequenceProps = subscribeToSequenceProps;
@@ -1,10 +1,11 @@
1
1
  import { type SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
2
2
  import type { SequenceNodePath, VideoConfigValues } from 'remotion';
3
- export declare const subscribeToSequencePropsWatchers: ({ fileName, line, column, nodePath: preferredNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, videoConfigValues, }: {
3
+ export declare const subscribeToSequencePropsWatchers: ({ fileName, line, column, nodePath: preferredNodePath, resolvedNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, videoConfigValues, }: {
4
4
  fileName: string;
5
5
  line: number;
6
6
  column: number;
7
7
  nodePath: SequenceNodePath | null;
8
+ resolvedNodePath?: SequenceNodePath | null | undefined;
8
9
  componentIdentity: string | null;
9
10
  keys: string[];
10
11
  assetKeys: string[];
@@ -15,7 +15,7 @@ const node_path_cache_1 = require("./node-path-cache");
15
15
  const can_update_sequence_props_1 = require("./routes/can-update-sequence-props");
16
16
  const sequencePropsWatchers = {};
17
17
  const getWatcherKey = (nodePath, assetKeys) => `${(0, studio_shared_1.stringifySequenceSubscriptionKey)(nodePath)}:${assetKeys.join('\0')}:${JSON.stringify(nodePath.videoConfigValues)}`;
18
- const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, logLevel, videoConfigValues, }) => {
18
+ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, resolvedNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, logLevel, videoConfigValues, }) => {
19
19
  if (preferredNodePath) {
20
20
  try {
21
21
  const fromNodePath = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
@@ -85,6 +85,36 @@ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, com
85
85
  };
86
86
  }
87
87
  }
88
+ if (resolvedNodePath) {
89
+ try {
90
+ return {
91
+ status: (0, can_update_sequence_props_1.computeSequencePropsStatus)({
92
+ fileName,
93
+ nodePath: resolvedNodePath,
94
+ componentIdentity,
95
+ keys,
96
+ assetKeys,
97
+ effects,
98
+ remotionRoot,
99
+ videoConfigValues,
100
+ }),
101
+ nodePath: {
102
+ absolutePath: node_path_1.default.resolve(remotionRoot, fileName),
103
+ nodePath: resolvedNodePath,
104
+ sequenceKeys: keys,
105
+ effectKeys: effects,
106
+ videoConfigValues,
107
+ },
108
+ success: true,
109
+ };
110
+ }
111
+ catch (error) {
112
+ if (!(error instanceof jsx_component_identity_1.JsxElementIdentityMismatchError ||
113
+ error instanceof jsx_element_not_found_at_location_error_1.JsxElementNotFoundAtLocationError)) {
114
+ throw error;
115
+ }
116
+ }
117
+ }
88
118
  const status = (0, can_update_sequence_props_1.computeSequencePropsStatusFromFilenameByLocation)({
89
119
  fileName,
90
120
  line,
@@ -99,13 +129,14 @@ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, com
99
129
  });
100
130
  return status;
101
131
  };
102
- const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: preferredNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, videoConfigValues, }) => {
132
+ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: preferredNodePath, resolvedNodePath = null, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, videoConfigValues, }) => {
103
133
  var _a;
104
134
  const initialResult = getSequencePropsStatus({
105
135
  fileName,
106
136
  line,
107
137
  column,
108
138
  preferredNodePath,
139
+ resolvedNodePath,
109
140
  componentIdentity,
110
141
  keys,
111
142
  assetKeys,
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.511",
6
+ "version": "4.0.513",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@remotion/studio-protocol": "4.0.511",
26
+ "@remotion/studio-protocol": "4.0.513",
27
27
  "@babel/types": "7.24.0",
28
28
  "@babel/parser": "7.24.1",
29
29
  "@svgr/core": "8.1.0",
@@ -32,12 +32,12 @@
32
32
  "semver": "7.5.3",
33
33
  "zod": "4.4.3",
34
34
  "prettier": "3.8.1",
35
- "remotion": "4.0.511",
35
+ "remotion": "4.0.513",
36
36
  "recast": "0.23.11",
37
- "@remotion/bundler": "4.0.511",
38
- "@remotion/renderer": "4.0.511",
39
- "@remotion/studio-codemods": "4.0.511",
40
- "@remotion/studio-shared": "4.0.511",
37
+ "@remotion/bundler": "4.0.513",
38
+ "@remotion/renderer": "4.0.513",
39
+ "@remotion/studio-codemods": "4.0.513",
40
+ "@remotion/studio-shared": "4.0.513",
41
41
  "memfs": "3.4.3",
42
42
  "open": "8.4.2"
43
43
  },
@@ -45,7 +45,7 @@
45
45
  "ast-types": "0.16.1",
46
46
  "react": "19.2.3",
47
47
  "@types/semver": "7.5.3",
48
- "@remotion/eslint-config-internal": "4.0.511",
48
+ "@remotion/eslint-config-internal": "4.0.513",
49
49
  "eslint": "9.19.0",
50
50
  "@types/node": "20.12.14",
51
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"