@remotion/studio-server 4.0.475 → 4.0.476

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, }) => {
@@ -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,8 +1,11 @@
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;
@@ -28,3 +31,4 @@ export declare const computeSequencePropsStatusFromFilenameByLine: ({ fileName,
28
31
  remotionRoot: string;
29
32
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
30
33
  }) => SubscribeToSequencePropsResponse;
34
+ export {};
@@ -38,6 +38,7 @@ 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");
@@ -51,7 +52,42 @@ const staticStatus = (codeValue) => ({
51
52
  const computedStatus = () => ({
52
53
  status: 'computed',
53
54
  });
54
- const isStaticValue = (node) => {
55
+ // Mirrors the encoding that staticFile() from "remotion" applies at runtime
56
+ const encodeStaticFilePath = (path) => {
57
+ return path.split('/').map(encodeURIComponent).join('/');
58
+ };
59
+ // Detects calls that the Studio knows how to serialize back to source:
60
+ // staticFile("...") and new Date("...")
61
+ const getSpecialValueCall = (node) => {
62
+ if (node.type === 'CallExpression') {
63
+ const call = node;
64
+ if (call.callee.type === 'Identifier' &&
65
+ call.callee.name === 'staticFile' &&
66
+ call.arguments.length === 1 &&
67
+ call.arguments[0].type === 'StringLiteral') {
68
+ return { type: 'static-file', value: call.arguments[0].value };
69
+ }
70
+ return null;
71
+ }
72
+ if (node.type === 'NewExpression') {
73
+ const newExpr = node;
74
+ if (newExpr.callee.type === 'Identifier' &&
75
+ newExpr.callee.name === 'Date' &&
76
+ newExpr.arguments.length === 1 &&
77
+ newExpr.arguments[0].type === 'StringLiteral') {
78
+ return { type: 'date', value: newExpr.arguments[0].value };
79
+ }
80
+ return null;
81
+ }
82
+ return null;
83
+ };
84
+ const defaultStaticValueOptions = {
85
+ allowSpecialValues: false,
86
+ };
87
+ const isStaticValue = (node, options = defaultStaticValueOptions) => {
88
+ if (options.allowSpecialValues && getSpecialValueCall(node) !== null) {
89
+ return true;
90
+ }
55
91
  switch (node.type) {
56
92
  case 'NumericLiteral':
57
93
  case 'StringLiteral':
@@ -63,18 +99,29 @@ const isStaticValue = (node) => {
63
99
  node.operator === '+') &&
64
100
  node.argument.type === 'NumericLiteral');
65
101
  case 'TSAsExpression':
66
- return (0, exports.isStaticValue)(node.expression);
102
+ return (0, exports.isStaticValue)(node.expression, options);
67
103
  case 'ArrayExpression':
68
- return node.elements.every((el) => el !== null && el.type !== 'SpreadElement' && (0, exports.isStaticValue)(el));
104
+ return node.elements.every((el) => el !== null &&
105
+ el.type !== 'SpreadElement' &&
106
+ (0, exports.isStaticValue)(el, options));
69
107
  case 'ObjectExpression':
70
108
  return node.properties.every((prop) => prop.type === 'ObjectProperty' &&
71
- (0, exports.isStaticValue)(prop.value));
109
+ (0, exports.isStaticValue)(prop.value, options));
72
110
  default:
73
111
  return false;
74
112
  }
75
113
  };
76
114
  exports.isStaticValue = isStaticValue;
77
- const extractStaticValue = (node) => {
115
+ const extractStaticValue = (node, options = defaultStaticValueOptions) => {
116
+ if (options.allowSpecialValues) {
117
+ const specialValue = getSpecialValueCall(node);
118
+ if (specialValue !== null) {
119
+ if (specialValue.type === 'static-file') {
120
+ return `${no_react_1.NoReactInternals.FILE_TOKEN}${encodeStaticFilePath(specialValue.value)}`;
121
+ }
122
+ return `${no_react_1.NoReactInternals.DATE_TOKEN}${specialValue.value}`;
123
+ }
124
+ }
78
125
  switch (node.type) {
79
126
  case 'NumericLiteral':
80
127
  case 'StringLiteral':
@@ -91,13 +138,13 @@ const extractStaticValue = (node) => {
91
138
  return undefined;
92
139
  }
93
140
  case 'TSAsExpression':
94
- return (0, exports.extractStaticValue)(node.expression);
141
+ return (0, exports.extractStaticValue)(node.expression, options);
95
142
  case 'ArrayExpression':
96
143
  return node.elements.map((el) => {
97
144
  if (el === null || el.type === 'SpreadElement') {
98
145
  return undefined;
99
146
  }
100
- return (0, exports.extractStaticValue)(el);
147
+ return (0, exports.extractStaticValue)(el, options);
101
148
  });
102
149
  case 'ObjectExpression': {
103
150
  const obj = node;
@@ -112,7 +159,7 @@ const extractStaticValue = (node) => {
112
159
  ? String(p.key.value)
113
160
  : undefined;
114
161
  if (key !== undefined) {
115
- result[key] = (0, exports.extractStaticValue)(p.value);
162
+ result[key] = (0, exports.extractStaticValue)(p.value, options);
116
163
  }
117
164
  }
118
165
  }
@@ -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()) {
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.476",
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.476",
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.476",
33
+ "@remotion/renderer": "4.0.476",
34
+ "@remotion/studio-shared": "4.0.476",
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.476",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14",
45
45
  "@typescript/native-preview": "7.0.0-dev.20260217.1"