@remotion/studio-server 4.0.475 → 4.0.477

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.
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.parseAndApplyCodemod = exports.formatOutput = void 0;
37
+ const imports_1 = require("../helpers/imports");
37
38
  const parse_ast_1 = require("./parse-ast");
38
39
  const recast_mods_1 = require("./recast-mods");
39
40
  const getPrettier = async () => {
@@ -71,6 +72,14 @@ const parseAndApplyCodemod = ({ input, codeMod, }) => {
71
72
  if (changesMade.length === 0) {
72
73
  throw new Error('Unable to calculate the changes needed for this file. Edit the file manually.');
73
74
  }
75
+ if (codeMod.type === 'duplicate-composition' && codeMod.tag) {
76
+ (0, imports_1.ensureNamedImport)({
77
+ ast: newAst,
78
+ importedName: codeMod.tag,
79
+ sourcePath: 'remotion',
80
+ localName: codeMod.tag,
81
+ });
82
+ }
74
83
  const output = (0, parse_ast_1.serializeAst)(newAst);
75
84
  return { changesMade, newContents: output };
76
85
  };
@@ -391,6 +391,36 @@ const normalizeEasingAfterRemovingKeyframe = ({ extraArgs, previousSegmentCount,
391
391
  needsEasingImport: setEasingOption({ options, easing }),
392
392
  };
393
393
  };
394
+ const normalizeEasingAfterRemovingKeyframes = ({ extraArgs, previousSegmentCount, nextSegmentCount, removedKeyframeIndexes, }) => {
395
+ const options = getInlineOptionsFromExtraArgs(extraArgs);
396
+ if (!options) {
397
+ return { extraArgs, needsEasingImport: false };
398
+ }
399
+ const easing = getExistingEasingArrayOrNull({
400
+ options,
401
+ segmentCount: previousSegmentCount,
402
+ });
403
+ if (easing === null) {
404
+ return { extraArgs, needsEasingImport: false };
405
+ }
406
+ for (const removedKeyframeIndex of [...removedKeyframeIndexes].sort((first, second) => second - first)) {
407
+ if (easing.length === 0) {
408
+ break;
409
+ }
410
+ const easingIndexToRemove = removedKeyframeIndex === 0 ? 0 : removedKeyframeIndex - 1;
411
+ easing.splice(easingIndexToRemove, 1);
412
+ }
413
+ while (easing.length > nextSegmentCount) {
414
+ easing.pop();
415
+ }
416
+ while (easing.length < nextSegmentCount) {
417
+ easing.push('linear');
418
+ }
419
+ return {
420
+ extraArgs: getExtraArgsWithOptions({ extraArgs, options }),
421
+ needsEasingImport: setEasingOption({ options, easing }),
422
+ };
423
+ };
394
424
  const validatePosterize = (posterize) => {
395
425
  if (posterize === undefined) {
396
426
  return;
@@ -614,7 +644,6 @@ const removeKeyframe = ({ expression, frame, }) => {
614
644
  };
615
645
  };
616
646
  const moveKeyframes = ({ expression, moves, }) => {
617
- var _a;
618
647
  const existing = getInterpolationExpression(expression);
619
648
  if (!existing) {
620
649
  throw new Error('Cannot move keyframe in non-interpolated expression');
@@ -639,28 +668,38 @@ const moveKeyframes = ({ expression, moves, }) => {
639
668
  }
640
669
  }
641
670
  const movedFromFrames = new Set(moveMap.keys());
642
- const nextFrames = new Set();
643
- for (const keyframe of existing.keyframes) {
644
- const nextFrame = (_a = moveMap.get(keyframe.frame)) !== null && _a !== void 0 ? _a : keyframe.frame;
645
- if (nextFrames.has(nextFrame)) {
646
- throw new Error(`Cannot move keyframe to frame ${nextFrame}: frame already exists`);
671
+ const movedToFrames = new Set(moveMap.values());
672
+ const removedKeyframeIndexes = [];
673
+ const nextKeyframes = existing.keyframes.flatMap((keyframe, index) => {
674
+ const movedFrame = moveMap.get(keyframe.frame);
675
+ if (movedFrame !== undefined) {
676
+ return [{ ...keyframe, frame: movedFrame }];
647
677
  }
648
- if (!movedFromFrames.has(keyframe.frame) && moveMap.has(nextFrame)) {
649
- throw new Error(`Cannot move keyframe to frame ${nextFrame}: frame already exists`);
678
+ if (movedToFrames.has(keyframe.frame) &&
679
+ !movedFromFrames.has(keyframe.frame)) {
680
+ removedKeyframeIndexes.push(index);
681
+ return [];
650
682
  }
651
- nextFrames.add(nextFrame);
683
+ return [keyframe];
684
+ });
685
+ const nextFrames = new Set();
686
+ for (const keyframe of nextKeyframes) {
687
+ if (nextFrames.has(keyframe.frame)) {
688
+ throw new Error(`Cannot move keyframe to frame ${keyframe.frame}: frame already exists`);
689
+ }
690
+ nextFrames.add(keyframe.frame);
652
691
  }
692
+ const normalizedEasing = normalizeEasingAfterRemovingKeyframes({
693
+ extraArgs: existing.extraArgs,
694
+ previousSegmentCount: Math.max(existing.keyframes.length - 1, 0),
695
+ nextSegmentCount: Math.max(nextKeyframes.length - 1, 0),
696
+ removedKeyframeIndexes,
697
+ });
653
698
  return createInterpolateExpression({
654
699
  callee: existing.callee,
655
700
  input: existing.input,
656
- extraArgs: existing.extraArgs,
657
- keyframes: existing.keyframes.map((keyframe) => {
658
- var _a;
659
- return ({
660
- ...keyframe,
661
- frame: (_a = moveMap.get(keyframe.frame)) !== null && _a !== void 0 ? _a : keyframe.frame,
662
- });
663
- }),
701
+ extraArgs: normalizedEasing.extraArgs,
702
+ keyframes: nextKeyframes,
664
703
  });
665
704
  };
666
705
  const applyKeyframeOperation = ({ expression, key, operation, schema, }) => {
@@ -515,10 +515,15 @@ const createStringAttribute = (name, value) => {
515
515
  const createBooleanAttribute = (name, value) => {
516
516
  return recast.types.builders.jsxAttribute(recast.types.builders.jsxIdentifier(name), recast.types.builders.jsxExpressionContainer(recast.types.builders.booleanLiteral(value)));
517
517
  };
518
- const createPositionAbsoluteStyleAttribute = () => {
519
- return recast.types.builders.jsxAttribute(recast.types.builders.jsxIdentifier('style'), recast.types.builders.jsxExpressionContainer(recast.types.builders.objectExpression([
518
+ const formatTranslateValue = ({ x, y }) => `${x}px ${y}px`;
519
+ const createPositionAbsoluteStyleAttribute = (position) => {
520
+ const properties = [
520
521
  recast.types.builders.objectProperty(recast.types.builders.identifier('position'), recast.types.builders.stringLiteral('absolute')),
521
- ])));
522
+ ];
523
+ if (position) {
524
+ properties.push(recast.types.builders.objectProperty(recast.types.builders.identifier('translate'), recast.types.builders.stringLiteral(formatTranslateValue(position))));
525
+ }
526
+ return recast.types.builders.jsxAttribute(recast.types.builders.jsxIdentifier('style'), recast.types.builders.jsxExpressionContainer(recast.types.builders.objectExpression(properties)));
522
527
  };
523
528
  const createStaticFileSrcAttribute = ({ staticFileLocalName, src, }) => {
524
529
  return recast.types.builders.jsxAttribute(recast.types.builders.jsxIdentifier('src'), recast.types.builders.jsxExpressionContainer(recast.types.builders.callExpression(recast.types.builders.identifier(staticFileLocalName), [recast.types.builders.stringLiteral(src)])));
@@ -535,25 +540,27 @@ const createComponentProp = ({ name, value, }) => {
535
540
  const createStringSrcAttribute = (src) => {
536
541
  return recast.types.builders.jsxAttribute(recast.types.builders.jsxIdentifier('src'), recast.types.builders.stringLiteral(src));
537
542
  };
538
- const createSolidElement = ({ localName, width, height, }) => {
543
+ const createSolidElement = ({ localName, width, height, position, }) => {
539
544
  return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier(localName), [
540
545
  createNumberAttribute('width', width),
541
546
  createNumberAttribute('height', height),
542
- createPositionAbsoluteStyleAttribute(),
547
+ createPositionAbsoluteStyleAttribute(position),
543
548
  ], true), null, []);
544
549
  };
545
- const createComponentElement = ({ localName, props, }) => {
550
+ const createComponentElement = ({ localName, props, position, }) => {
546
551
  return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier(localName), [
547
552
  ...props.map(createComponentProp),
548
- createPositionAbsoluteStyleAttribute(),
553
+ createPositionAbsoluteStyleAttribute(position),
549
554
  ], true), null, []);
550
555
  };
551
- const createAssetElement = ({ addPositionStyle, localName, staticFileLocalName, src, dimensions, }) => {
556
+ const createAssetElement = ({ addPositionStyle, localName, staticFileLocalName, src, dimensions, position, }) => {
552
557
  return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier(localName), [
553
558
  staticFileLocalName === null
554
559
  ? createStringSrcAttribute(src)
555
560
  : createStaticFileSrcAttribute({ staticFileLocalName, src }),
556
- ...(addPositionStyle ? [createPositionAbsoluteStyleAttribute()] : []),
561
+ ...(addPositionStyle
562
+ ? [createPositionAbsoluteStyleAttribute(position)]
563
+ : []),
557
564
  ...(dimensions
558
565
  ? [
559
566
  createNumberAttribute('width', dimensions.width),
@@ -1007,6 +1014,7 @@ const createInsertableJsxElement = ({ ast, element, }) => {
1007
1014
  localName: solidLocalName,
1008
1015
  width: element.width,
1009
1016
  height: element.height,
1017
+ position: element.position,
1010
1018
  });
1011
1019
  }
1012
1020
  if (element.type === 'component') {
@@ -1019,6 +1027,7 @@ const createInsertableJsxElement = ({ ast, element, }) => {
1019
1027
  return createComponentElement({
1020
1028
  localName: componentLocalName,
1021
1029
  props: element.props,
1030
+ position: element.position,
1022
1031
  });
1023
1032
  }
1024
1033
  if (element.type === 'asset') {
@@ -1048,6 +1057,7 @@ const createInsertableJsxElement = ({ ast, element, }) => {
1048
1057
  staticFileLocalName,
1049
1058
  src: element.src,
1050
1059
  dimensions: element.dimensions,
1060
+ position: element.position,
1051
1061
  });
1052
1062
  }
1053
1063
  throw new Error('Unsupported element type');
@@ -0,0 +1,12 @@
1
+ import type { File, JSXOpeningElement } from '@babel/types';
2
+ export declare class JsxElementIdentityMismatchError extends Error {
3
+ constructor();
4
+ }
5
+ export declare const getJsxComponentIdentity: ({ ast, jsxElement, }: {
6
+ ast: File;
7
+ jsxElement: JSXOpeningElement;
8
+ }) => string | null;
9
+ export declare const jsxComponentIdentitiesMatch: ({ expected, actual, }: {
10
+ expected: string | null;
11
+ actual: string | null;
12
+ }) => boolean;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsxComponentIdentitiesMatch = exports.getJsxComponentIdentity = exports.JsxElementIdentityMismatchError = void 0;
4
+ const imports_1 = require("../helpers/imports");
5
+ class JsxElementIdentityMismatchError extends Error {
6
+ constructor() {
7
+ super('JSX element identity changed');
8
+ }
9
+ }
10
+ exports.JsxElementIdentityMismatchError = JsxElementIdentityMismatchError;
11
+ const jsxMemberToString = (expr) => {
12
+ if (expr.type === 'JSXIdentifier') {
13
+ return expr.name;
14
+ }
15
+ return `${jsxMemberToString(expr.object)}.${expr.property.name}`;
16
+ };
17
+ const getJsxRootIdentifier = (expr) => {
18
+ if (expr.type === 'JSXIdentifier') {
19
+ return expr;
20
+ }
21
+ return getJsxRootIdentifier(expr.object);
22
+ };
23
+ const getTagName = (node) => {
24
+ if (node.name.type === 'JSXIdentifier') {
25
+ return node.name.name;
26
+ }
27
+ if (node.name.type === 'JSXMemberExpression') {
28
+ return jsxMemberToString(node.name);
29
+ }
30
+ return null;
31
+ };
32
+ const getMemberPath = (tagName) => {
33
+ const dotIndex = tagName.indexOf('.');
34
+ return dotIndex === -1 ? '' : tagName.slice(dotIndex + 1);
35
+ };
36
+ const getMemberPathAfterImportName = ({ tagName, importName, }) => {
37
+ const parts = tagName.split('.');
38
+ const importNameIndex = parts.indexOf(importName);
39
+ if (importNameIndex === -1) {
40
+ return getMemberPath(tagName);
41
+ }
42
+ return parts.slice(importNameIndex + 1).join('.');
43
+ };
44
+ const sourcePathToIdentityPrefix = (sourcePath) => {
45
+ if (sourcePath === 'remotion') {
46
+ return 'dev.remotion.remotion';
47
+ }
48
+ if (sourcePath.startsWith('@remotion/')) {
49
+ const packageName = sourcePath
50
+ .slice('@remotion/'.length)
51
+ .replace(/-([a-z])/g, (_, char) => char.toUpperCase());
52
+ return `dev.remotion.${packageName}`;
53
+ }
54
+ return null;
55
+ };
56
+ const makeImportedIdentity = ({ sourcePath, importName, memberPath, }) => {
57
+ const prefix = sourcePathToIdentityPrefix(sourcePath);
58
+ if (prefix === null) {
59
+ return null;
60
+ }
61
+ return [prefix, importName, memberPath].filter(Boolean).join('.');
62
+ };
63
+ const getJsxComponentIdentity = ({ ast, jsxElement, }) => {
64
+ var _a;
65
+ const tagName = getTagName(jsxElement);
66
+ if (tagName === null) {
67
+ return null;
68
+ }
69
+ const rootIdentifier = jsxElement.name.type === 'JSXIdentifier'
70
+ ? jsxElement.name
71
+ : jsxElement.name.type === 'JSXMemberExpression'
72
+ ? getJsxRootIdentifier(jsxElement.name)
73
+ : null;
74
+ if (rootIdentifier === null) {
75
+ return tagName;
76
+ }
77
+ for (const node of ast.program.body) {
78
+ if (node.type !== 'ImportDeclaration') {
79
+ continue;
80
+ }
81
+ const sourcePath = String(node.source.value);
82
+ for (const specifier of node.specifiers) {
83
+ if (specifier.type === 'ImportSpecifier' &&
84
+ ((_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) === rootIdentifier.name) {
85
+ const importName = (0, imports_1.getImportedName)(specifier);
86
+ return makeImportedIdentity({
87
+ sourcePath,
88
+ importName,
89
+ memberPath: getMemberPath(tagName),
90
+ });
91
+ }
92
+ if (specifier.type === 'ImportNamespaceSpecifier' &&
93
+ specifier.local.name === rootIdentifier.name) {
94
+ const [importName, ...members] = getMemberPath(tagName).split('.');
95
+ if (!importName) {
96
+ return null;
97
+ }
98
+ return makeImportedIdentity({
99
+ sourcePath,
100
+ importName,
101
+ memberPath: members.join('.'),
102
+ });
103
+ }
104
+ if (specifier.type === 'ImportDefaultSpecifier' &&
105
+ specifier.local.name === rootIdentifier.name) {
106
+ return makeImportedIdentity({
107
+ sourcePath,
108
+ importName: 'default',
109
+ memberPath: getMemberPathAfterImportName({
110
+ tagName,
111
+ importName: rootIdentifier.name,
112
+ }),
113
+ });
114
+ }
115
+ }
116
+ }
117
+ return tagName;
118
+ };
119
+ exports.getJsxComponentIdentity = getJsxComponentIdentity;
120
+ const jsxComponentIdentitiesMatch = ({ expected, actual, }) => {
121
+ if (expected === null) {
122
+ return true;
123
+ }
124
+ return expected === actual;
125
+ };
126
+ exports.jsxComponentIdentitiesMatch = jsxComponentIdentitiesMatch;
@@ -74,6 +74,7 @@ const addSequenceKeyframeHandler = ({ input: { fileName, nodePath, key, frame, v
74
74
  fileContents: output,
75
75
  keys: (0, studio_shared_1.getAllSchemaKeys)(schema),
76
76
  nodePath: updatedNodePath,
77
+ componentIdentity: null,
77
78
  effects: [],
78
79
  });
79
80
  const updatedSubscriptionKey = { ...nodePath, nodePath: updatedNodePath };
@@ -106,8 +106,12 @@ const extractDefaultPropsFromSource = (input, compositionId) => {
106
106
  this.traverse(path);
107
107
  return;
108
108
  }
109
- if ((0, can_update_sequence_props_1.isStaticValue)(expression)) {
110
- const value = (0, can_update_sequence_props_1.extractStaticValue)(expression);
109
+ if ((0, can_update_sequence_props_1.isStaticValue)(expression, {
110
+ allowSpecialValues: true,
111
+ })) {
112
+ const value = (0, can_update_sequence_props_1.extractStaticValue)(expression, {
113
+ allowSpecialValues: true,
114
+ });
111
115
  if (value !== null &&
112
116
  typeof value === 'object' &&
113
117
  !Array.isArray(value)) {
@@ -1,30 +1,37 @@
1
1
  import type { Expression, File, JSXOpeningElement } from '@babel/types';
2
2
  import type { SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
3
3
  import type { CanUpdateSequencePropsResponseTrue, CanUpdateSequencePropStatus, SequenceNodePath } from 'remotion';
4
- export declare const isStaticValue: (node: Expression) => boolean;
5
- export declare const extractStaticValue: (node: Expression) => unknown;
4
+ type StaticValueOptions = {
5
+ allowSpecialValues: boolean;
6
+ };
7
+ export declare const isStaticValue: (node: Expression, options?: StaticValueOptions) => boolean;
8
+ export declare const extractStaticValue: (node: Expression, options?: StaticValueOptions) => unknown;
6
9
  export declare const getComputedStatus: (node: Expression, ast: File) => CanUpdateSequencePropStatus;
7
10
  export declare const findJsxElementAtNodePath: (ast: File, nodePath: SequenceNodePath) => JSXOpeningElement | null;
8
11
  export declare const findNodePathForJsxElement: (ast: File, target: JSXOpeningElement) => SequenceNodePath | null;
9
12
  export declare const lineColumnToNodePath: (ast: File, targetLine: number) => SequenceNodePath | null;
10
- export declare const computeSequencePropsStatusFromContent: ({ fileContents, nodePath, keys, effects, }: {
13
+ export declare const computeSequencePropsStatusFromContent: ({ fileContents, nodePath, componentIdentity, keys, effects, }: {
11
14
  fileContents: string;
12
15
  nodePath: SequenceNodePath;
16
+ componentIdentity: string | null;
13
17
  keys: string[];
14
18
  effects: string[][];
15
19
  }) => CanUpdateSequencePropsResponseTrue;
16
- export declare const computeSequencePropsStatus: ({ fileName, nodePath, keys, effects, remotionRoot, }: {
20
+ export declare const computeSequencePropsStatus: ({ fileName, nodePath, componentIdentity, keys, effects, remotionRoot, }: {
17
21
  fileName: string;
18
22
  nodePath: SequenceNodePath;
23
+ componentIdentity: string | null;
19
24
  keys: string[];
20
25
  effects: string[][];
21
26
  remotionRoot: string;
22
27
  }) => CanUpdateSequencePropsResponseTrue;
23
- export declare const computeSequencePropsStatusFromFilenameByLine: ({ fileName, line, keys, effects, remotionRoot, logLevel, }: {
28
+ export declare const computeSequencePropsStatusFromFilenameByLine: ({ fileName, line, componentIdentity, keys, effects, remotionRoot, logLevel, }: {
24
29
  fileName: string;
25
30
  line: number;
31
+ componentIdentity: string | null;
26
32
  keys: string[];
27
33
  effects: string[][];
28
34
  remotionRoot: string;
29
35
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
30
36
  }) => SubscribeToSequencePropsResponse;
37
+ export {};
@@ -38,10 +38,12 @@ 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");
40
40
  const recast = __importStar(require("recast"));
41
+ const no_react_1 = require("remotion/no-react");
41
42
  const parse_ast_1 = require("../../codemods/parse-ast");
42
43
  const get_ast_node_path_1 = require("../../helpers/get-ast-node-path");
43
44
  const import_agnostic_node_path_1 = require("../../helpers/import-agnostic-node-path");
44
45
  const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
46
+ const jsx_component_identity_1 = require("../jsx-component-identity");
45
47
  const jsx_element_not_found_at_location_error_1 = require("../jsx-element-not-found-at-location-error");
46
48
  const can_update_effect_props_1 = require("./can-update-effect-props");
47
49
  const staticStatus = (codeValue) => ({
@@ -51,7 +53,42 @@ const staticStatus = (codeValue) => ({
51
53
  const computedStatus = () => ({
52
54
  status: 'computed',
53
55
  });
54
- const isStaticValue = (node) => {
56
+ // Mirrors the encoding that staticFile() from "remotion" applies at runtime
57
+ const encodeStaticFilePath = (path) => {
58
+ return path.split('/').map(encodeURIComponent).join('/');
59
+ };
60
+ // Detects calls that the Studio knows how to serialize back to source:
61
+ // staticFile("...") and new Date("...")
62
+ const getSpecialValueCall = (node) => {
63
+ if (node.type === 'CallExpression') {
64
+ const call = node;
65
+ if (call.callee.type === 'Identifier' &&
66
+ call.callee.name === 'staticFile' &&
67
+ call.arguments.length === 1 &&
68
+ call.arguments[0].type === 'StringLiteral') {
69
+ return { type: 'static-file', value: call.arguments[0].value };
70
+ }
71
+ return null;
72
+ }
73
+ if (node.type === 'NewExpression') {
74
+ const newExpr = node;
75
+ if (newExpr.callee.type === 'Identifier' &&
76
+ newExpr.callee.name === 'Date' &&
77
+ newExpr.arguments.length === 1 &&
78
+ newExpr.arguments[0].type === 'StringLiteral') {
79
+ return { type: 'date', value: newExpr.arguments[0].value };
80
+ }
81
+ return null;
82
+ }
83
+ return null;
84
+ };
85
+ const defaultStaticValueOptions = {
86
+ allowSpecialValues: false,
87
+ };
88
+ const isStaticValue = (node, options = defaultStaticValueOptions) => {
89
+ if (options.allowSpecialValues && getSpecialValueCall(node) !== null) {
90
+ return true;
91
+ }
55
92
  switch (node.type) {
56
93
  case 'NumericLiteral':
57
94
  case 'StringLiteral':
@@ -63,18 +100,29 @@ const isStaticValue = (node) => {
63
100
  node.operator === '+') &&
64
101
  node.argument.type === 'NumericLiteral');
65
102
  case 'TSAsExpression':
66
- return (0, exports.isStaticValue)(node.expression);
103
+ return (0, exports.isStaticValue)(node.expression, options);
67
104
  case 'ArrayExpression':
68
- return node.elements.every((el) => el !== null && el.type !== 'SpreadElement' && (0, exports.isStaticValue)(el));
105
+ return node.elements.every((el) => el !== null &&
106
+ el.type !== 'SpreadElement' &&
107
+ (0, exports.isStaticValue)(el, options));
69
108
  case 'ObjectExpression':
70
109
  return node.properties.every((prop) => prop.type === 'ObjectProperty' &&
71
- (0, exports.isStaticValue)(prop.value));
110
+ (0, exports.isStaticValue)(prop.value, options));
72
111
  default:
73
112
  return false;
74
113
  }
75
114
  };
76
115
  exports.isStaticValue = isStaticValue;
77
- const extractStaticValue = (node) => {
116
+ const extractStaticValue = (node, options = defaultStaticValueOptions) => {
117
+ if (options.allowSpecialValues) {
118
+ const specialValue = getSpecialValueCall(node);
119
+ if (specialValue !== null) {
120
+ if (specialValue.type === 'static-file') {
121
+ return `${no_react_1.NoReactInternals.FILE_TOKEN}${encodeStaticFilePath(specialValue.value)}`;
122
+ }
123
+ return `${no_react_1.NoReactInternals.DATE_TOKEN}${specialValue.value}`;
124
+ }
125
+ }
78
126
  switch (node.type) {
79
127
  case 'NumericLiteral':
80
128
  case 'StringLiteral':
@@ -91,13 +139,13 @@ const extractStaticValue = (node) => {
91
139
  return undefined;
92
140
  }
93
141
  case 'TSAsExpression':
94
- return (0, exports.extractStaticValue)(node.expression);
142
+ return (0, exports.extractStaticValue)(node.expression, options);
95
143
  case 'ArrayExpression':
96
144
  return node.elements.map((el) => {
97
145
  if (el === null || el.type === 'SpreadElement') {
98
146
  return undefined;
99
147
  }
100
- return (0, exports.extractStaticValue)(el);
148
+ return (0, exports.extractStaticValue)(el, options);
101
149
  });
102
150
  case 'ObjectExpression': {
103
151
  const obj = node;
@@ -112,7 +160,7 @@ const extractStaticValue = (node) => {
112
160
  ? String(p.key.value)
113
161
  : undefined;
114
162
  if (key !== undefined) {
115
- result[key] = (0, exports.extractStaticValue)(p.value);
163
+ result[key] = (0, exports.extractStaticValue)(p.value, options);
116
164
  }
117
165
  }
118
166
  }
@@ -306,6 +354,25 @@ const getInterpolationKeyframes = (node, ast) => {
306
354
  if (node.type === 'TSAsExpression') {
307
355
  return getInterpolationKeyframes(node.expression, ast);
308
356
  }
357
+ if (node.type === 'CallExpression' &&
358
+ node.callee.type === 'Identifier' &&
359
+ node.callee.name === 'String' &&
360
+ node.arguments.length === 1 &&
361
+ node.arguments[0].type !== 'ArgumentPlaceholder' &&
362
+ node.arguments[0].type !== 'JSXNamespacedName' &&
363
+ node.arguments[0].type !== 'SpreadElement') {
364
+ const interpolation = getInterpolationKeyframes(node.arguments[0], ast);
365
+ if (!interpolation) {
366
+ return undefined;
367
+ }
368
+ return {
369
+ ...interpolation,
370
+ keyframes: interpolation.keyframes.map((keyframe) => ({
371
+ ...keyframe,
372
+ value: String(keyframe.value),
373
+ })),
374
+ };
375
+ }
309
376
  if (node.type !== 'CallExpression') {
310
377
  return undefined;
311
378
  }
@@ -580,12 +647,18 @@ const computeSequenceOnlyPropsRecord = ({ jsxElement, ast, keys, }) => {
580
647
  }
581
648
  return filteredProps;
582
649
  };
583
- const computeSequencePropsStatusFromContent = ({ fileContents, nodePath, keys, effects, }) => {
650
+ const computeSequencePropsStatusFromContent = ({ fileContents, nodePath, componentIdentity, keys, effects, }) => {
584
651
  const ast = (0, parse_ast_1.parseAst)(fileContents);
585
652
  const jsxElement = (0, exports.findJsxElementAtNodePath)(ast, nodePath);
586
653
  if (!jsxElement) {
587
654
  throw new jsx_element_not_found_at_location_error_1.JsxElementNotFoundAtLocationError();
588
655
  }
656
+ if (!(0, jsx_component_identity_1.jsxComponentIdentitiesMatch)({
657
+ expected: componentIdentity,
658
+ actual: (0, jsx_component_identity_1.getJsxComponentIdentity)({ ast, jsxElement }),
659
+ })) {
660
+ throw new jsx_component_identity_1.JsxElementIdentityMismatchError();
661
+ }
589
662
  const filteredProps = computeSequenceOnlyPropsRecord({ jsxElement, ast, keys });
590
663
  const effectsStatuses = computeEffectsForJsx({ ast, jsxElement, effects });
591
664
  return {
@@ -595,7 +668,7 @@ const computeSequencePropsStatusFromContent = ({ fileContents, nodePath, keys, e
595
668
  };
596
669
  };
597
670
  exports.computeSequencePropsStatusFromContent = computeSequencePropsStatusFromContent;
598
- const computeSequencePropsStatus = ({ fileName, nodePath, keys, effects, remotionRoot, }) => {
671
+ const computeSequencePropsStatus = ({ fileName, nodePath, componentIdentity, keys, effects, remotionRoot, }) => {
599
672
  const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
600
673
  remotionRoot,
601
674
  fileName,
@@ -605,12 +678,13 @@ const computeSequencePropsStatus = ({ fileName, nodePath, keys, effects, remotio
605
678
  return (0, exports.computeSequencePropsStatusFromContent)({
606
679
  fileContents,
607
680
  nodePath,
681
+ componentIdentity,
608
682
  keys,
609
683
  effects,
610
684
  });
611
685
  };
612
686
  exports.computeSequencePropsStatus = computeSequencePropsStatus;
613
- const computeSequencePropsStatusFromFilenameByLine = ({ fileName, line, keys, effects, remotionRoot, logLevel, }) => {
687
+ const computeSequencePropsStatusFromFilenameByLine = ({ fileName, line, componentIdentity, keys, effects, remotionRoot, logLevel, }) => {
614
688
  try {
615
689
  const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
616
690
  remotionRoot,
@@ -633,6 +707,7 @@ const computeSequencePropsStatusFromFilenameByLine = ({ fileName, line, keys, ef
633
707
  status: (0, exports.computeSequencePropsStatus)({
634
708
  fileName,
635
709
  nodePath: resolvedNodePath,
710
+ componentIdentity,
636
711
  keys,
637
712
  effects,
638
713
  remotionRoot,
@@ -215,6 +215,7 @@ const deleteKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, r
215
215
  fileContents: output,
216
216
  keys: (0, studio_shared_1.getAllSchemaKeys)(keyframe.schema),
217
217
  nodePath: keyframe.nodePath.nodePath,
218
+ componentIdentity: null,
218
219
  effects: [],
219
220
  });
220
221
  return {
@@ -257,6 +257,7 @@ const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
257
257
  src: assetPath,
258
258
  srcType: 'static',
259
259
  dimensions: fileType.dimensions,
260
+ position: null,
260
261
  };
261
262
  return {
262
263
  assetPath,
@@ -14,7 +14,16 @@ const validateDimension = (name, value) => {
14
14
  throw new Error(`${name} must be a positive number`);
15
15
  }
16
16
  };
17
+ const validatePosition = (position) => {
18
+ if (position === null) {
19
+ return;
20
+ }
21
+ if (!Number.isFinite(position.x) || !Number.isFinite(position.y)) {
22
+ throw new Error('Position must be finite');
23
+ }
24
+ };
17
25
  const validateElement = (element) => {
26
+ validatePosition(element.position);
18
27
  if (element.type === 'solid') {
19
28
  validateDimension('width', element.width);
20
29
  validateDimension('height', element.height);
@@ -1,3 +1,20 @@
1
- import type { SaveSequencePropsRequest, SaveSequencePropsResponse } from '@remotion/studio-shared';
1
+ import type { SaveSequencePropEdit, SaveSequencePropsRequest, SaveSequencePropsResponse } from '@remotion/studio-shared';
2
+ import { type SequencePropsNodeUpdate } from '../../codemods/update-sequence-props/update-sequence-props';
2
3
  import type { ApiHandler } from '../api-types';
4
+ type ResolvedSequencePropEdit = {
5
+ index: number;
6
+ fileName: SaveSequencePropEdit['fileName'];
7
+ nodePath: SaveSequencePropEdit['nodePath'];
8
+ key: SaveSequencePropEdit['key'];
9
+ value: unknown;
10
+ valueString: string;
11
+ defaultValue: unknown | null;
12
+ defaultValueString: string | null;
13
+ schema: SaveSequencePropEdit['schema'];
14
+ };
15
+ export declare const convertSequencePropEditToCodemodChange: (edit: Pick<ResolvedSequencePropEdit, "defaultValue" | "key" | "nodePath" | "schema" | "value">) => SequencePropsNodeUpdate;
16
+ export declare const shouldSuppressHmrForSequencePropEdits: (edits: readonly {
17
+ key: string;
18
+ }[]) => boolean;
3
19
  export declare const saveSequencePropsHandler: ApiHandler<SaveSequencePropsRequest, SaveSequencePropsResponse>;
20
+ export {};
@@ -1,10 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.saveSequencePropsHandler = void 0;
3
+ exports.saveSequencePropsHandler = exports.shouldSuppressHmrForSequencePropEdits = exports.convertSequencePropEditToCodemodChange = void 0;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const renderer_1 = require("@remotion/renderer");
6
6
  const studio_shared_1 = require("@remotion/studio-shared");
7
- const no_react_1 = require("remotion/no-react");
8
7
  const update_sequence_props_1 = require("../../codemods/update-sequence-props/update-sequence-props");
9
8
  const file_watcher_1 = require("../../file-watcher");
10
9
  const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
@@ -13,6 +12,24 @@ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
13
12
  const can_update_sequence_props_1 = require("./can-update-sequence-props");
14
13
  const log_update_1 = require("./log-updates/log-update");
15
14
  const save_props_mutex_1 = require("./save-props-mutex");
15
+ const convertSequencePropEditToCodemodChange = (edit) => {
16
+ return {
17
+ nodePath: edit.nodePath.nodePath,
18
+ updates: [
19
+ {
20
+ key: edit.key,
21
+ value: edit.value,
22
+ defaultValue: edit.defaultValue,
23
+ },
24
+ ],
25
+ schema: edit.schema,
26
+ };
27
+ };
28
+ exports.convertSequencePropEditToCodemodChange = convertSequencePropEditToCodemodChange;
29
+ const shouldSuppressHmrForSequencePropEdits = (edits) => {
30
+ return edits.every((edit) => edit.key !== 'showInTimeline');
31
+ };
32
+ exports.shouldSuppressHmrForSequencePropEdits = shouldSuppressHmrForSequencePropEdits;
16
33
  const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLabel }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
17
34
  var _a;
18
35
  if (edits.length === 0) {
@@ -54,19 +71,7 @@ const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLab
54
71
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
55
72
  const { output, formatted, results: updateResults, } = await (0, update_sequence_props_1.updateMultipleSequenceProps)({
56
73
  input: fileContents,
57
- changes: group.edits.map((edit) => {
58
- return {
59
- nodePath: edit.nodePath.nodePath,
60
- updates: [
61
- {
62
- key: edit.key,
63
- value: edit.value,
64
- defaultValue: edit.defaultValue,
65
- },
66
- ],
67
- schema: no_react_1.NoReactInternals.sequenceSchema,
68
- };
69
- }),
74
+ changes: group.edits.map(exports.convertSequencePropEditToCodemodChange),
70
75
  prettierConfigOverride: null,
71
76
  });
72
77
  const [{ logLine: firstLogLine }] = updateResults;
@@ -89,17 +94,20 @@ const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLab
89
94
  }
90
95
  const undoMessage = `↩️ ${undoLabel}`;
91
96
  const redoMessage = `↪️ ${redoLabel}`;
97
+ const suppressHmr = (0, exports.shouldSuppressHmrForSequencePropEdits)(edits);
92
98
  (0, undo_stack_1.pushTransactionToUndoStack)({
93
99
  snapshots,
94
100
  logLevel,
95
101
  remotionRoot,
96
102
  description: { undoMessage, redoMessage },
97
103
  entryType: 'sequence-props',
98
- suppressHmrOnFileRestore: true,
104
+ suppressHmrOnFileRestore: suppressHmr,
99
105
  });
100
106
  for (const [absolutePath, output] of outputByPath) {
101
107
  (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
102
- (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
108
+ if (suppressHmr) {
109
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
110
+ }
103
111
  (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output, clientId);
104
112
  }
105
113
  for (const { edits: groupEdits, fileRelativeToRoot } of editGroups.values()) {
@@ -137,6 +145,7 @@ const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLab
137
145
  fileContents: output,
138
146
  keys: (0, studio_shared_1.getAllSchemaKeys)(edit.schema),
139
147
  nodePath: edit.nodePath.nodePath,
148
+ componentIdentity: null,
140
149
  effects: [],
141
150
  });
142
151
  return {
@@ -2,12 +2,13 @@
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, keys, effects, clientId }, remotionRoot, logLevel, }) => {
5
+ const subscribeToSequenceProps = ({ input: { fileName, line, column, nodePath, componentIdentity, keys, effects, clientId, }, remotionRoot, logLevel, }) => {
6
6
  const result = (0, sequence_props_watchers_1.subscribeToSequencePropsWatchers)({
7
7
  fileName,
8
8
  line,
9
9
  column,
10
10
  nodePath,
11
+ componentIdentity,
11
12
  keys,
12
13
  effects,
13
14
  remotionRoot,
@@ -77,6 +77,7 @@ const updateSequenceKeyframeSettingsHandler = ({ input: { fileName, nodePath, ke
77
77
  fileContents: output,
78
78
  keys: (0, studio_shared_1.getAllSchemaKeys)(schema),
79
79
  nodePath: updatedNodePath,
80
+ componentIdentity: null,
80
81
  effects: [],
81
82
  });
82
83
  const updatedSubscriptionKey = { ...nodePath, nodePath: updatedNodePath };
@@ -1,10 +1,11 @@
1
1
  import { type SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
2
2
  import type { SequenceNodePath } from 'remotion';
3
- export declare const subscribeToSequencePropsWatchers: ({ fileName, line, column, nodePath: preferredNodePath, keys, effects, remotionRoot, clientId, logLevel, }: {
3
+ export declare const subscribeToSequencePropsWatchers: ({ fileName, line, column, nodePath: preferredNodePath, componentIdentity, keys, effects, remotionRoot, clientId, logLevel, }: {
4
4
  fileName: string;
5
5
  line: number;
6
6
  column: number;
7
7
  nodePath: SequenceNodePath | null;
8
+ componentIdentity: string | null;
8
9
  keys: string[];
9
10
  effects: string[][];
10
11
  remotionRoot: string;
@@ -8,42 +8,64 @@ const node_path_1 = __importDefault(require("node:path"));
8
8
  const renderer_1 = require("@remotion/renderer");
9
9
  const studio_shared_1 = require("@remotion/studio-shared");
10
10
  const file_watcher_1 = require("../file-watcher");
11
+ const jsx_component_identity_1 = require("./jsx-component-identity");
11
12
  const jsx_element_not_found_at_location_error_1 = require("./jsx-element-not-found-at-location-error");
12
13
  const live_events_1 = require("./live-events");
13
14
  const node_path_cache_1 = require("./node-path-cache");
14
15
  const can_update_sequence_props_1 = require("./routes/can-update-sequence-props");
15
16
  const sequencePropsWatchers = {};
16
- const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, keys, effects, remotionRoot, logLevel, }) => {
17
+ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, componentIdentity, keys, effects, remotionRoot, logLevel, }) => {
17
18
  if (preferredNodePath) {
18
- const fromNodePath = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
19
- fileName,
20
- nodePath: preferredNodePath,
21
- keys,
22
- effects,
23
- remotionRoot,
24
- });
25
- return {
26
- status: fromNodePath,
27
- nodePath: {
28
- absolutePath: node_path_1.default.resolve(remotionRoot, fileName),
19
+ try {
20
+ const fromNodePath = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
21
+ fileName,
29
22
  nodePath: preferredNodePath,
30
- sequenceKeys: keys,
31
- effectKeys: effects,
32
- },
33
- success: true,
34
- };
23
+ componentIdentity,
24
+ keys,
25
+ effects,
26
+ remotionRoot,
27
+ });
28
+ return {
29
+ status: fromNodePath,
30
+ nodePath: {
31
+ absolutePath: node_path_1.default.resolve(remotionRoot, fileName),
32
+ nodePath: preferredNodePath,
33
+ sequenceKeys: keys,
34
+ effectKeys: effects,
35
+ },
36
+ success: true,
37
+ };
38
+ }
39
+ catch (error) {
40
+ if (!(error instanceof jsx_component_identity_1.JsxElementIdentityMismatchError ||
41
+ error instanceof jsx_element_not_found_at_location_error_1.JsxElementNotFoundAtLocationError)) {
42
+ throw error;
43
+ }
44
+ }
35
45
  }
36
46
  // Try cached nodePath first (handles stale source maps after suppressed rebuilds)
37
47
  const cachedNodePath = (0, node_path_cache_1.getCachedNodePath)(fileName, line, column);
38
48
  if (cachedNodePath) {
39
- const cachedResult = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
40
- fileName,
41
- nodePath: cachedNodePath,
42
- keys,
43
- effects,
44
- remotionRoot,
45
- });
46
- if (cachedResult.canUpdate) {
49
+ const cachedResult = (() => {
50
+ try {
51
+ return (0, can_update_sequence_props_1.computeSequencePropsStatus)({
52
+ fileName,
53
+ nodePath: cachedNodePath,
54
+ componentIdentity,
55
+ keys,
56
+ effects,
57
+ remotionRoot,
58
+ });
59
+ }
60
+ catch (error) {
61
+ if (error instanceof jsx_component_identity_1.JsxElementIdentityMismatchError ||
62
+ error instanceof jsx_element_not_found_at_location_error_1.JsxElementNotFoundAtLocationError) {
63
+ return null;
64
+ }
65
+ throw error;
66
+ }
67
+ })();
68
+ if (cachedResult === null || cachedResult === void 0 ? void 0 : cachedResult.canUpdate) {
47
69
  return {
48
70
  status: cachedResult,
49
71
  nodePath: {
@@ -59,6 +81,7 @@ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, key
59
81
  const status = (0, can_update_sequence_props_1.computeSequencePropsStatusFromFilenameByLine)({
60
82
  fileName,
61
83
  line,
84
+ componentIdentity,
62
85
  keys,
63
86
  effects,
64
87
  remotionRoot,
@@ -66,13 +89,14 @@ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, key
66
89
  });
67
90
  return status;
68
91
  };
69
- const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: preferredNodePath, keys, effects, remotionRoot, clientId, logLevel, }) => {
92
+ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: preferredNodePath, componentIdentity, keys, effects, remotionRoot, clientId, logLevel, }) => {
70
93
  var _a;
71
94
  const initialResult = getSequencePropsStatus({
72
95
  fileName,
73
96
  line,
74
97
  column,
75
98
  preferredNodePath,
99
+ componentIdentity,
76
100
  keys,
77
101
  effects,
78
102
  remotionRoot,
@@ -105,6 +129,7 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: pr
105
129
  const result = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
106
130
  fileContents: event.content,
107
131
  nodePath: nodePath.nodePath,
132
+ componentIdentity,
108
133
  keys,
109
134
  effects,
110
135
  });
@@ -125,7 +150,8 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: pr
125
150
  });
126
151
  }
127
152
  catch (error) {
128
- if (error instanceof jsx_element_not_found_at_location_error_1.JsxElementNotFoundAtLocationError) {
153
+ if (error instanceof jsx_element_not_found_at_location_error_1.JsxElementNotFoundAtLocationError ||
154
+ error instanceof jsx_component_identity_1.JsxElementIdentityMismatchError) {
129
155
  (0, live_events_1.waitForLiveEventsListener)().then((listener) => {
130
156
  listener.sendEventToClientId(clientId, {
131
157
  type: 'lost-node-path',
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.475",
6
+ "version": "4.0.477",
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.475",
30
+ "remotion": "4.0.477",
31
31
  "recast": "0.23.11",
32
- "@remotion/bundler": "4.0.475",
33
- "@remotion/renderer": "4.0.475",
34
- "@remotion/studio-shared": "4.0.475",
32
+ "@remotion/bundler": "4.0.477",
33
+ "@remotion/renderer": "4.0.477",
34
+ "@remotion/studio-shared": "4.0.477",
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.475",
42
+ "@remotion/eslint-config-internal": "4.0.477",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14",
45
45
  "@typescript/native-preview": "7.0.0-dev.20260217.1"