@remotion/studio-server 4.0.432 → 4.0.433

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.
@@ -1,13 +1,11 @@
1
- import { type EnumPath } from '@remotion/studio-shared';
2
- import type { namedTypes } from 'ast-types';
1
+ import type { JSXOpeningElement } from '@babel/types';
3
2
  import type { ExpressionKind } from 'ast-types/lib/gen/kinds';
4
- export declare const parseValueExpression: (value: unknown, enumPaths: EnumPath[]) => ExpressionKind;
5
- export declare const updateNestedProp: ({ node, parentKey, childKey, value, enumPaths, defaultValue, isDefault, }: {
6
- node: namedTypes.JSXOpeningElement;
3
+ export declare const parseValueExpression: (value: unknown) => ExpressionKind;
4
+ export declare const updateNestedProp: ({ node, parentKey, childKey, value, defaultValue, isDefault, }: {
5
+ node: JSXOpeningElement;
7
6
  parentKey: string;
8
7
  childKey: string;
9
8
  value: unknown;
10
- enumPaths: EnumPath[];
11
9
  defaultValue: unknown;
12
10
  isDefault: boolean;
13
11
  }) => string;
@@ -38,8 +38,8 @@ const studio_shared_1 = require("@remotion/studio-shared");
38
38
  const recast = __importStar(require("recast"));
39
39
  const parse_ast_1 = require("./parse-ast");
40
40
  const b = recast.types.builders;
41
- const parseValueExpression = (value, enumPaths) => {
42
- return (0, parse_ast_1.parseAst)(`a = ${(0, studio_shared_1.stringifyDefaultProps)({ props: value, enumPaths })}`)
41
+ const parseValueExpression = (value) => {
42
+ return (0, parse_ast_1.parseAst)(`a = ${(0, studio_shared_1.stringifyDefaultProps)({ props: value, enumPaths: [] })}`)
43
43
  .program.body[0].expression.right;
44
44
  };
45
45
  exports.parseValueExpression = parseValueExpression;
@@ -64,11 +64,9 @@ const findJsxAttribute = (attributes, name) => {
64
64
  const findObjectProperty = (objExpr, propertyName) => {
65
65
  const propIndex = objExpr.properties.findIndex((p) => p.type === 'ObjectProperty' &&
66
66
  ((p.key.type === 'Identifier' &&
67
- p.key.name ===
68
- propertyName) ||
67
+ p.key.name === propertyName) ||
69
68
  (p.key.type === 'StringLiteral' &&
70
- p.key
71
- .value === propertyName)));
69
+ p.key.value === propertyName)));
72
70
  return {
73
71
  propIndex,
74
72
  prop: propIndex !== -1
@@ -118,8 +116,8 @@ const removeNestedProp = ({ attr, attrIndex, attributes, childKey, }) => {
118
116
  attributes.splice(attrIndex, 1);
119
117
  }
120
118
  };
121
- const setNestedProp = ({ attr, attributes, parentKey, childKey, value, enumPaths, }) => {
122
- const parsedValue = (0, exports.parseValueExpression)(value, enumPaths);
119
+ const setNestedProp = ({ attr, attributes, parentKey, childKey, value, }) => {
120
+ const parsedValue = (0, exports.parseValueExpression)(value);
123
121
  if (attr) {
124
122
  const objExpr = getObjectExpression(attr);
125
123
  if (objExpr) {
@@ -140,7 +138,7 @@ const setNestedProp = ({ attr, attributes, parentKey, childKey, value, enumPaths
140
138
  attributes.push(newAttr);
141
139
  }
142
140
  };
143
- const updateNestedProp = ({ node, parentKey, childKey, value, enumPaths, defaultValue, isDefault, }) => {
141
+ const updateNestedProp = ({ node, parentKey, childKey, value, defaultValue, isDefault, }) => {
144
142
  if (!node.attributes) {
145
143
  node.attributes = [];
146
144
  }
@@ -151,7 +149,7 @@ const updateNestedProp = ({ node, parentKey, childKey, value, enumPaths, default
151
149
  removeNestedProp({ attr, attrIndex, attributes, childKey });
152
150
  }
153
151
  else {
154
- setNestedProp({ attr, attributes, parentKey, childKey, value, enumPaths });
152
+ setNestedProp({ attr, attributes, parentKey, childKey, value });
155
153
  }
156
154
  return oldValueString;
157
155
  };
@@ -1,12 +1,13 @@
1
- import type { EnumPath } from '@remotion/studio-shared';
2
- export declare const updateSequenceProps: ({ input, targetLine, key, value, enumPaths, defaultValue, }: {
1
+ import type { SequenceNodePath } from '@remotion/studio-shared';
2
+ export declare const updateSequenceProps: ({ input, nodePath, key, value, defaultValue, prettierConfigOverride, }: {
3
3
  input: string;
4
- targetLine: number;
4
+ nodePath: SequenceNodePath;
5
5
  key: string;
6
6
  value: unknown;
7
- enumPaths: EnumPath[];
8
7
  defaultValue: unknown;
8
+ prettierConfigOverride?: Record<string, unknown> | null | undefined;
9
9
  }) => Promise<{
10
10
  output: string;
11
11
  oldValueString: string;
12
+ formatted: boolean;
12
13
  }>;
@@ -35,12 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.updateSequenceProps = void 0;
37
37
  const recast = __importStar(require("recast"));
38
+ const can_update_sequence_props_1 = require("../preview-server/routes/can-update-sequence-props");
38
39
  const parse_ast_1 = require("./parse-ast");
39
40
  const update_nested_prop_1 = require("./update-nested-prop");
40
41
  const b = recast.types.builders;
41
- const updateSequenceProps = async ({ input, targetLine, key, value, enumPaths, defaultValue, }) => {
42
+ const updateSequenceProps = async ({ input, nodePath, key, value, defaultValue, prettierConfigOverride, }) => {
43
+ var _a, _b;
42
44
  const ast = (0, parse_ast_1.parseAst)(input);
43
- let found = false;
44
45
  let oldValueString = '';
45
46
  const isDefault = defaultValue !== null &&
46
47
  JSON.stringify(value) === JSON.stringify(defaultValue);
@@ -48,61 +49,55 @@ const updateSequenceProps = async ({ input, targetLine, key, value, enumPaths, d
48
49
  const isNested = dotIndex !== -1;
49
50
  const parentKey = isNested ? key.slice(0, dotIndex) : key;
50
51
  const childKey = isNested ? key.slice(dotIndex + 1) : '';
51
- recast.types.visit(ast, {
52
- visitJSXOpeningElement(path) {
53
- var _a, _b;
54
- const { node } = path;
55
- if (!node.loc || node.loc.start.line !== targetLine) {
56
- return this.traverse(path);
57
- }
58
- if (isNested) {
59
- oldValueString = (0, update_nested_prop_1.updateNestedProp)({
60
- node,
61
- parentKey,
62
- childKey,
63
- value,
64
- enumPaths,
65
- defaultValue,
66
- isDefault,
67
- });
68
- found = true;
69
- return this.traverse(path);
70
- }
71
- const attrIndex = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.findIndex((a) => {
72
- if (a.type === 'JSXSpreadAttribute') {
73
- return false;
74
- }
75
- if (a.name.type === 'JSXNamespacedName') {
76
- return false;
77
- }
78
- return a.name.name === key;
79
- });
80
- const attr = attrIndex !== undefined && attrIndex !== -1
81
- ? (_b = node.attributes) === null || _b === void 0 ? void 0 : _b[attrIndex]
82
- : undefined;
83
- if (attr && attr.type !== 'JSXSpreadAttribute' && attr.value) {
84
- const printed = recast.print(attr.value).code;
85
- // Strip JSX expression container braces, e.g. "{30}" -> "30"
86
- oldValueString =
87
- printed.startsWith('{') && printed.endsWith('}')
88
- ? printed.slice(1, -1)
89
- : printed;
90
- }
91
- else if (attr && attr.type !== 'JSXSpreadAttribute' && !attr.value) {
92
- // JSX shorthand like `loop` (no value) is implicitly `true`
93
- oldValueString = 'true';
52
+ const node = (0, can_update_sequence_props_1.findJsxElementAtNodePath)(ast, nodePath);
53
+ if (!node) {
54
+ throw new Error('Could not find a JSX element at the specified line to update');
55
+ }
56
+ if (isNested) {
57
+ oldValueString = (0, update_nested_prop_1.updateNestedProp)({
58
+ node,
59
+ parentKey,
60
+ childKey,
61
+ value,
62
+ defaultValue,
63
+ isDefault,
64
+ });
65
+ }
66
+ else {
67
+ const attrIndex = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.findIndex((a) => {
68
+ if (a.type === 'JSXSpreadAttribute') {
69
+ return false;
94
70
  }
95
- else if (!attr && defaultValue !== null) {
96
- oldValueString = JSON.stringify(defaultValue);
71
+ if (a.name.type === 'JSXNamespacedName') {
72
+ return false;
97
73
  }
98
- if (isDefault) {
99
- if (attr && attr.type !== 'JSXSpreadAttribute' && node.attributes) {
100
- node.attributes.splice(attrIndex, 1);
101
- }
102
- found = true;
103
- return this.traverse(path);
74
+ return a.name.name === key;
75
+ });
76
+ const attr = attrIndex !== undefined && attrIndex !== -1
77
+ ? (_b = node.attributes) === null || _b === void 0 ? void 0 : _b[attrIndex]
78
+ : undefined;
79
+ if (attr && attr.type !== 'JSXSpreadAttribute' && attr.value) {
80
+ const printed = recast.print(attr.value).code;
81
+ // Strip JSX expression container braces, e.g. "{30}" -> "30"
82
+ oldValueString =
83
+ printed.startsWith('{') && printed.endsWith('}')
84
+ ? printed.slice(1, -1)
85
+ : printed;
86
+ }
87
+ else if (attr && attr.type !== 'JSXSpreadAttribute' && !attr.value) {
88
+ // JSX shorthand like `loop` (no value) is implicitly `true`
89
+ oldValueString = 'true';
90
+ }
91
+ else if (!attr && defaultValue !== null) {
92
+ oldValueString = JSON.stringify(defaultValue);
93
+ }
94
+ if (isDefault) {
95
+ if (attr && attr.type !== 'JSXSpreadAttribute' && node.attributes) {
96
+ node.attributes.splice(attrIndex, 1);
104
97
  }
105
- const parsed = (0, update_nested_prop_1.parseValueExpression)(value, enumPaths);
98
+ }
99
+ else {
100
+ const parsed = (0, update_nested_prop_1.parseValueExpression)(value);
106
101
  const newValue = value === true ? null : b.jsxExpressionContainer(parsed);
107
102
  if (!attr || attr.type === 'JSXSpreadAttribute') {
108
103
  const newAttr = b.jsxAttribute(b.jsxIdentifier(key), newValue);
@@ -114,36 +109,53 @@ const updateSequenceProps = async ({ input, targetLine, key, value, enumPaths, d
114
109
  else {
115
110
  attr.value = newValue;
116
111
  }
117
- found = true;
118
- return this.traverse(path);
119
- },
120
- });
121
- if (!found) {
122
- throw new Error('Could not find a JSX element at the specified line to update');
112
+ }
123
113
  }
114
+ const finalFile = (0, parse_ast_1.serializeAst)(ast);
124
115
  let prettier = null;
125
116
  try {
126
117
  prettier = await Promise.resolve().then(() => __importStar(require('prettier')));
127
118
  }
128
- catch (_a) {
129
- throw new Error('Prettier cannot be found in the current project.');
119
+ catch (_c) {
120
+ return {
121
+ output: finalFile,
122
+ oldValueString,
123
+ formatted: false,
124
+ };
130
125
  }
131
126
  const { format, resolveConfig, resolveConfigFile } = prettier;
132
- const configFilePath = await resolveConfigFile();
133
- if (!configFilePath) {
134
- throw new Error('The Prettier config file was not found');
127
+ let prettierConfig;
128
+ if (prettierConfigOverride !== undefined) {
129
+ prettierConfig = prettierConfigOverride;
130
+ }
131
+ else {
132
+ const configFilePath = await resolveConfigFile();
133
+ if (!configFilePath) {
134
+ return {
135
+ output: finalFile,
136
+ oldValueString,
137
+ formatted: false,
138
+ };
139
+ }
140
+ prettierConfig = await resolveConfig(configFilePath);
135
141
  }
136
- const prettierConfig = await resolveConfig(configFilePath);
137
142
  if (!prettierConfig) {
138
- throw new Error('The Prettier config file was not found. For this feature, the "prettier" package must be installed and a .prettierrc file must exist.');
143
+ return {
144
+ output: finalFile,
145
+ oldValueString,
146
+ formatted: false,
147
+ };
139
148
  }
140
- const finalFile = (0, parse_ast_1.serializeAst)(ast);
141
149
  const prettified = await format(finalFile, {
142
150
  ...prettierConfig,
143
151
  filepath: 'test.tsx',
144
152
  plugins: [],
145
- endOfLine: 'auto',
153
+ endOfLine: 'lf',
146
154
  });
147
- return { output: prettified, oldValueString };
155
+ return {
156
+ output: prettified,
157
+ oldValueString,
158
+ formatted: true,
159
+ };
148
160
  };
149
161
  exports.updateSequenceProps = updateSequenceProps;
@@ -1,8 +1,17 @@
1
- import type { Expression } from '@babel/types';
2
- import type { CanUpdateSequencePropsResponse } from '@remotion/studio-shared';
1
+ import type { Expression, File, JSXOpeningElement } from '@babel/types';
2
+ import type { CanUpdateSequencePropsResponse, SequenceNodePath } from '@remotion/studio-shared';
3
3
  export declare const isStaticValue: (node: Expression) => boolean;
4
4
  export declare const extractStaticValue: (node: Expression) => unknown;
5
- export declare const computeSequencePropsStatus: ({ fileName, line, keys, remotionRoot, }: {
5
+ export declare const findJsxElementAtNodePath: (ast: File, nodePath: SequenceNodePath) => JSXOpeningElement | null;
6
+ export declare const lineColumnToNodePath: (ast: File, targetLine: number) => SequenceNodePath | null;
7
+ export declare const computeSequencePropsStatusFromContent: (fileContents: string, nodePath: SequenceNodePath, keys: string[]) => CanUpdateSequencePropsResponse;
8
+ export declare const computeSequencePropsStatus: ({ fileName, nodePath, keys, remotionRoot, }: {
9
+ fileName: string;
10
+ nodePath: SequenceNodePath;
11
+ keys: string[];
12
+ remotionRoot: string;
13
+ }) => CanUpdateSequencePropsResponse;
14
+ export declare const computeSequencePropsStatusByLine: ({ fileName, line, keys, remotionRoot, }: {
6
15
  fileName: string;
7
16
  line: number;
8
17
  keys: string[];
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.computeSequencePropsStatus = exports.extractStaticValue = exports.isStaticValue = void 0;
39
+ exports.computeSequencePropsStatusByLine = exports.computeSequencePropsStatus = exports.computeSequencePropsStatusFromContent = exports.lineColumnToNodePath = exports.findJsxElementAtNodePath = exports.extractStaticValue = exports.isStaticValue = void 0;
40
40
  const node_fs_1 = require("node:fs");
41
41
  const node_path_1 = __importDefault(require("node:path"));
42
42
  const recast = __importStar(require("recast"));
@@ -151,19 +151,64 @@ const getPropsStatus = (jsxElement) => {
151
151
  }
152
152
  return props;
153
153
  };
154
- const findJsxElementAtLine = (ast, targetLine) => {
155
- let found = null;
154
+ const getNodePathForRecastPath = (
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ recastPath) => {
157
+ const segments = [];
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
+ let current = recastPath;
160
+ while (current && current.parentPath) {
161
+ segments.unshift(current.name);
162
+ current = current.parentPath;
163
+ }
164
+ // Recast paths start with "root" which doesn't correspond to a real AST property
165
+ if (segments.length > 0 && segments[0] === 'root') {
166
+ return segments.slice(1);
167
+ }
168
+ return segments;
169
+ };
170
+ const findJsxElementAtNodePath = (ast, nodePath) => {
171
+ let current = new recast.types.NodePath(ast);
172
+ for (const segment of nodePath) {
173
+ current = current.get(segment);
174
+ if (current.value === null || current.value === undefined) {
175
+ return null;
176
+ }
177
+ }
178
+ if (recast.types.namedTypes.JSXOpeningElement.check(current.value)) {
179
+ return current.value;
180
+ }
181
+ return null;
182
+ };
183
+ exports.findJsxElementAtNodePath = findJsxElementAtNodePath;
184
+ const lineColumnToNodePath = (ast, targetLine) => {
185
+ let foundPath = null;
156
186
  recast.types.visit(ast, {
157
- visitJSXOpeningElement(nodePath) {
158
- const { node } = nodePath;
187
+ visitJSXOpeningElement(p) {
188
+ const { node } = p;
159
189
  if (node.loc && node.loc.start.line === targetLine) {
160
- found = node;
190
+ foundPath = getNodePathForRecastPath(p);
161
191
  return false;
162
192
  }
163
- return this.traverse(nodePath);
193
+ return this.traverse(p);
164
194
  },
165
195
  });
166
- return found;
196
+ return foundPath;
197
+ };
198
+ exports.lineColumnToNodePath = lineColumnToNodePath;
199
+ const PIXEL_VALUE_REGEX = /^-?\d+(\.\d+)?px$/;
200
+ const isSupportedTranslateValue = (value) => {
201
+ const parts = value.split(/\s+/);
202
+ if (parts.length === 1 || parts.length === 2) {
203
+ return parts.every((part) => PIXEL_VALUE_REGEX.test(part));
204
+ }
205
+ return false;
206
+ };
207
+ const validateStyleValue = (childKey, value) => {
208
+ if (childKey === 'translate' && typeof value === 'string') {
209
+ return isSupportedTranslateValue(value);
210
+ }
211
+ return true;
167
212
  };
168
213
  const getNestedPropStatus = (jsxElement, parentKey, childKey) => {
169
214
  const attr = jsxElement.attributes.find((a) => a.type !== 'JSXSpreadAttribute' &&
@@ -194,9 +239,58 @@ const getNestedPropStatus = (jsxElement, parentKey, childKey) => {
194
239
  if (!(0, exports.isStaticValue)(propValue)) {
195
240
  return { canUpdate: false, reason: 'computed' };
196
241
  }
197
- return { canUpdate: true, codeValue: (0, exports.extractStaticValue)(propValue) };
242
+ const codeValue = (0, exports.extractStaticValue)(propValue);
243
+ if (!validateStyleValue(childKey, codeValue)) {
244
+ return { canUpdate: false, reason: 'computed' };
245
+ }
246
+ return { canUpdate: true, codeValue };
247
+ };
248
+ const computeSequencePropsStatusFromContent = (fileContents, nodePath, keys) => {
249
+ const ast = (0, parse_ast_1.parseAst)(fileContents);
250
+ const jsxElement = (0, exports.findJsxElementAtNodePath)(ast, nodePath);
251
+ if (!jsxElement) {
252
+ throw new Error('Could not find a JSX element at the specified location');
253
+ }
254
+ const allProps = getPropsStatus(jsxElement);
255
+ const filteredProps = {};
256
+ for (const key of keys) {
257
+ const dotIndex = key.indexOf('.');
258
+ if (dotIndex !== -1) {
259
+ filteredProps[key] = getNestedPropStatus(jsxElement, key.slice(0, dotIndex), key.slice(dotIndex + 1));
260
+ }
261
+ else if (key in allProps) {
262
+ filteredProps[key] = allProps[key];
263
+ }
264
+ else {
265
+ filteredProps[key] = { canUpdate: true, codeValue: undefined };
266
+ }
267
+ }
268
+ return {
269
+ canUpdate: true,
270
+ props: filteredProps,
271
+ nodePath,
272
+ };
273
+ };
274
+ exports.computeSequencePropsStatusFromContent = computeSequencePropsStatusFromContent;
275
+ const computeSequencePropsStatus = ({ fileName, nodePath, keys, remotionRoot, }) => {
276
+ try {
277
+ const absolutePath = node_path_1.default.resolve(remotionRoot, fileName);
278
+ const fileRelativeToRoot = node_path_1.default.relative(remotionRoot, absolutePath);
279
+ if (fileRelativeToRoot.startsWith('..')) {
280
+ throw new Error('Cannot read a file outside the project');
281
+ }
282
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
283
+ return (0, exports.computeSequencePropsStatusFromContent)(fileContents, nodePath, keys);
284
+ }
285
+ catch (err) {
286
+ return {
287
+ canUpdate: false,
288
+ reason: err.message,
289
+ };
290
+ }
198
291
  };
199
- const computeSequencePropsStatus = ({ fileName, line, keys, remotionRoot, }) => {
292
+ exports.computeSequencePropsStatus = computeSequencePropsStatus;
293
+ const computeSequencePropsStatusByLine = ({ fileName, line, keys, remotionRoot, }) => {
200
294
  try {
201
295
  const absolutePath = node_path_1.default.resolve(remotionRoot, fileName);
202
296
  const fileRelativeToRoot = node_path_1.default.relative(remotionRoot, absolutePath);
@@ -205,28 +299,16 @@ const computeSequencePropsStatus = ({ fileName, line, keys, remotionRoot, }) =>
205
299
  }
206
300
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
207
301
  const ast = (0, parse_ast_1.parseAst)(fileContents);
208
- const jsxElement = findJsxElementAtLine(ast, line);
209
- if (!jsxElement) {
302
+ const resolvedNodePath = (0, exports.lineColumnToNodePath)(ast, line);
303
+ if (!resolvedNodePath) {
210
304
  throw new Error('Could not find a JSX element at the specified location');
211
305
  }
212
- const allProps = getPropsStatus(jsxElement);
213
- const filteredProps = {};
214
- for (const key of keys) {
215
- const dotIndex = key.indexOf('.');
216
- if (dotIndex !== -1) {
217
- filteredProps[key] = getNestedPropStatus(jsxElement, key.slice(0, dotIndex), key.slice(dotIndex + 1));
218
- }
219
- else if (key in allProps) {
220
- filteredProps[key] = allProps[key];
221
- }
222
- else {
223
- filteredProps[key] = { canUpdate: true, codeValue: undefined };
224
- }
225
- }
226
- return {
227
- canUpdate: true,
228
- props: filteredProps,
229
- };
306
+ return (0, exports.computeSequencePropsStatus)({
307
+ fileName,
308
+ nodePath: resolvedNodePath,
309
+ keys,
310
+ remotionRoot,
311
+ });
230
312
  }
231
313
  catch (err) {
232
314
  return {
@@ -235,4 +317,4 @@ const computeSequencePropsStatus = ({ fileName, line, keys, remotionRoot, }) =>
235
317
  };
236
318
  }
237
319
  };
238
- exports.computeSequencePropsStatus = computeSequencePropsStatus;
320
+ exports.computeSequencePropsStatusByLine = computeSequencePropsStatusByLine;
@@ -0,0 +1,10 @@
1
+ export declare const logUpdate: ({ absolutePath, fileRelativeToRoot, key, oldValueString, newValueString, defaultValueString, formatted, logLevel, }: {
2
+ absolutePath: string;
3
+ fileRelativeToRoot: string;
4
+ key: string;
5
+ oldValueString: string;
6
+ newValueString: string;
7
+ defaultValueString: string | null;
8
+ formatted: boolean;
9
+ logLevel: "error" | "info" | "trace" | "verbose" | "warn";
10
+ }) => void;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logUpdate = void 0;
4
+ const renderer_1 = require("@remotion/renderer");
5
+ const make_link_1 = require("../../hyperlinks/make-link");
6
+ let warnedAboutPrettier = false;
7
+ const normalizeQuotes = (str) => {
8
+ if (str.length >= 2 &&
9
+ ((str.startsWith("'") && str.endsWith("'")) ||
10
+ (str.startsWith('"') && str.endsWith('"')))) {
11
+ return `'${str.slice(1, -1)}'`;
12
+ }
13
+ return str;
14
+ };
15
+ const formatValueChange = ({ oldValueString, newValueString, defaultValueString, }) => {
16
+ // Changed to default value (prop gets deleted) → show only old value in red
17
+ if (defaultValueString !== null && newValueString === defaultValueString) {
18
+ return renderer_1.RenderInternals.chalk.red(oldValueString);
19
+ }
20
+ // Changed from default value (prop gets added) → show only new value in green
21
+ if (defaultValueString !== null && oldValueString === defaultValueString) {
22
+ return renderer_1.RenderInternals.chalk.green(newValueString);
23
+ }
24
+ return `${renderer_1.RenderInternals.chalk.red(oldValueString)} \u2192 ${renderer_1.RenderInternals.chalk.green(newValueString)}`;
25
+ };
26
+ const formatPropChange = ({ key, oldValueString, newValueString, defaultValueString, }) => {
27
+ const isResetToDefault = defaultValueString !== null && newValueString === defaultValueString;
28
+ const isChangeFromDefault = defaultValueString !== null && oldValueString === defaultValueString;
29
+ const valueChange = formatValueChange({
30
+ oldValueString,
31
+ newValueString,
32
+ defaultValueString,
33
+ });
34
+ const dotIndex = key.indexOf('.');
35
+ if (dotIndex === -1) {
36
+ if (isResetToDefault) {
37
+ return renderer_1.RenderInternals.chalk.red(`${key}={${oldValueString}}`);
38
+ }
39
+ if (isChangeFromDefault) {
40
+ return renderer_1.RenderInternals.chalk.green(`${key}={${newValueString}}`);
41
+ }
42
+ return `${key}={${valueChange}}`;
43
+ }
44
+ const parentKey = key.slice(0, dotIndex);
45
+ const childKey = key.slice(dotIndex + 1);
46
+ if (isResetToDefault) {
47
+ return `${parentKey}={{${renderer_1.RenderInternals.chalk.red(`${childKey}: ${oldValueString}`)}}}`;
48
+ }
49
+ if (isChangeFromDefault) {
50
+ return `${parentKey}={{${renderer_1.RenderInternals.chalk.green(`${childKey}: ${newValueString}`)}}}`;
51
+ }
52
+ return `${parentKey}={{${childKey}: ${valueChange}}}`;
53
+ };
54
+ const logUpdate = ({ absolutePath, fileRelativeToRoot, key, oldValueString, newValueString, defaultValueString, formatted, logLevel, }) => {
55
+ const locationLabel = `${fileRelativeToRoot}`;
56
+ const fileLink = (0, make_link_1.makeHyperlink)({
57
+ url: `file://${absolutePath}`,
58
+ text: locationLabel,
59
+ fallback: locationLabel,
60
+ });
61
+ const propChange = formatPropChange({
62
+ key,
63
+ oldValueString: normalizeQuotes(oldValueString),
64
+ newValueString: normalizeQuotes(newValueString),
65
+ defaultValueString: defaultValueString !== null ? normalizeQuotes(defaultValueString) : null,
66
+ });
67
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(`${fileLink}:`)} ${propChange}`);
68
+ if (!formatted && !warnedAboutPrettier) {
69
+ warnedAboutPrettier = true;
70
+ renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.yellow('Could not format with Prettier. File will need to be formatted manually.'));
71
+ }
72
+ };
73
+ exports.logUpdate = logUpdate;
@@ -6,11 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.saveSequencePropsHandler = void 0;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
- const renderer_1 = require("@remotion/renderer");
10
9
  const update_sequence_props_1 = require("../../codemods/update-sequence-props");
11
- const make_link_1 = require("../../hyperlinks/make-link");
12
10
  const hmr_suppression_1 = require("../hmr-suppression");
13
- const saveSequencePropsHandler = async ({ input: { fileName, line, column, key, value, enumPaths, defaultValue }, remotionRoot, logLevel, }) => {
11
+ const log_update_1 = require("./log-update");
12
+ const saveSequencePropsHandler = async ({ input: { fileName, nodePath, key, value, defaultValue }, remotionRoot, logLevel, }) => {
14
13
  try {
15
14
  const absolutePath = node_path_1.default.resolve(remotionRoot, fileName);
16
15
  const fileRelativeToRoot = node_path_1.default.relative(remotionRoot, absolutePath);
@@ -18,24 +17,27 @@ const saveSequencePropsHandler = async ({ input: { fileName, line, column, key,
18
17
  throw new Error('Cannot modify a file outside the project');
19
18
  }
20
19
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
21
- const { output, oldValueString } = await (0, update_sequence_props_1.updateSequenceProps)({
20
+ const { output, oldValueString, formatted } = await (0, update_sequence_props_1.updateSequenceProps)({
22
21
  input: fileContents,
23
- targetLine: line,
22
+ nodePath,
24
23
  key,
25
24
  value: JSON.parse(value),
26
- enumPaths,
27
25
  defaultValue: defaultValue !== null ? JSON.parse(defaultValue) : null,
28
26
  });
29
27
  (0, hmr_suppression_1.suppressHmrForFile)(absolutePath);
30
28
  (0, node_fs_1.writeFileSync)(absolutePath, output);
31
29
  const newValueString = JSON.stringify(JSON.parse(value));
32
- const locationLabel = `${fileRelativeToRoot}:${line}:${column}`;
33
- const fileLink = (0, make_link_1.makeHyperlink)({
34
- url: `file://${absolutePath}`,
35
- text: locationLabel,
36
- fallback: locationLabel,
30
+ const parsedDefault = defaultValue !== null ? JSON.parse(defaultValue) : null;
31
+ (0, log_update_1.logUpdate)({
32
+ absolutePath,
33
+ fileRelativeToRoot,
34
+ key,
35
+ oldValueString,
36
+ newValueString,
37
+ defaultValueString: parsedDefault !== null ? JSON.stringify(parsedDefault) : null,
38
+ formatted,
39
+ logLevel,
37
40
  });
38
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.blueBright(`${fileLink} updated: ${key} ${oldValueString} \u2192 ${newValueString}`));
39
41
  return {
40
42
  success: true,
41
43
  };
@@ -2,11 +2,10 @@
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, keys, clientId }, remotionRoot }) => {
5
+ const subscribeToSequenceProps = ({ input: { fileName, line, keys, clientId }, remotionRoot }) => {
6
6
  const result = (0, sequence_props_watchers_1.subscribeToSequencePropsWatchers)({
7
7
  fileName,
8
8
  line,
9
- column,
10
9
  keys,
11
10
  remotionRoot,
12
11
  clientId,
@@ -2,11 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.unsubscribeFromSequenceProps = void 0;
4
4
  const sequence_props_watchers_1 = require("../sequence-props-watchers");
5
- const unsubscribeFromSequenceProps = ({ input: { fileName, line, column, clientId }, remotionRoot }) => {
5
+ const unsubscribeFromSequenceProps = ({ input: { fileName, nodePath, clientId }, remotionRoot }) => {
6
6
  (0, sequence_props_watchers_1.unsubscribeFromSequencePropsWatchers)({
7
7
  fileName,
8
- line,
9
- column,
8
+ nodePath,
10
9
  remotionRoot,
11
10
  clientId,
12
11
  });
@@ -1,16 +1,14 @@
1
- import type { CanUpdateSequencePropsResponse } from '@remotion/studio-shared';
2
- export declare const subscribeToSequencePropsWatchers: ({ fileName, line, column, keys, remotionRoot, clientId, }: {
1
+ import type { CanUpdateSequencePropsResponse, SequenceNodePath } from '@remotion/studio-shared';
2
+ export declare const subscribeToSequencePropsWatchers: ({ fileName, line, keys, remotionRoot, clientId, }: {
3
3
  fileName: string;
4
4
  line: number;
5
- column: number;
6
5
  keys: string[];
7
6
  remotionRoot: string;
8
7
  clientId: string;
9
8
  }) => CanUpdateSequencePropsResponse;
10
- export declare const unsubscribeFromSequencePropsWatchers: ({ fileName, line, column, remotionRoot, clientId, }: {
9
+ export declare const unsubscribeFromSequencePropsWatchers: ({ fileName, nodePath, remotionRoot, clientId, }: {
11
10
  fileName: string;
12
- line: number;
13
- column: number;
11
+ nodePath: SequenceNodePath;
14
12
  remotionRoot: string;
15
13
  clientId: string;
16
14
  }) => void;
@@ -9,23 +9,28 @@ const file_watcher_1 = require("../file-watcher");
9
9
  const live_events_1 = require("./live-events");
10
10
  const can_update_sequence_props_1 = require("./routes/can-update-sequence-props");
11
11
  const sequencePropsWatchers = {};
12
- const makeWatcherKey = ({ absolutePath, line, column, }) => {
13
- return `${absolutePath}:${line}:${column}`;
12
+ const makeWatcherKey = ({ absolutePath, nodePath, }) => {
13
+ return `${absolutePath}:${JSON.stringify(nodePath)}`;
14
14
  };
15
- const subscribeToSequencePropsWatchers = ({ fileName, line, column, keys, remotionRoot, clientId, }) => {
15
+ const subscribeToSequencePropsWatchers = ({ fileName, line, keys, remotionRoot, clientId, }) => {
16
16
  var _a;
17
17
  const absolutePath = node_path_1.default.resolve(remotionRoot, fileName);
18
- const watcherKey = makeWatcherKey({ absolutePath, line, column });
19
- // Unwatch any existing watcher for the same key
20
- if ((_a = sequencePropsWatchers[clientId]) === null || _a === void 0 ? void 0 : _a[watcherKey]) {
21
- sequencePropsWatchers[clientId][watcherKey].unwatch();
22
- }
23
- const initialResult = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
18
+ // Initial lookup by line+column to resolve the nodePath
19
+ const initialResult = (0, can_update_sequence_props_1.computeSequencePropsStatusByLine)({
24
20
  fileName,
25
21
  line,
26
22
  keys,
27
23
  remotionRoot,
28
24
  });
25
+ if (!initialResult.canUpdate) {
26
+ return initialResult;
27
+ }
28
+ const { nodePath } = initialResult;
29
+ const watcherKey = makeWatcherKey({ absolutePath, nodePath });
30
+ // Unwatch any existing watcher for the same key
31
+ if ((_a = sequencePropsWatchers[clientId]) === null || _a === void 0 ? void 0 : _a[watcherKey]) {
32
+ sequencePropsWatchers[clientId][watcherKey].unwatch();
33
+ }
29
34
  const { unwatch } = (0, file_watcher_1.installFileWatcher)({
30
35
  file: absolutePath,
31
36
  onChange: (type) => {
@@ -34,7 +39,7 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, keys, remoti
34
39
  }
35
40
  const result = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
36
41
  fileName,
37
- line,
42
+ nodePath,
38
43
  keys,
39
44
  remotionRoot,
40
45
  });
@@ -42,8 +47,7 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, keys, remoti
42
47
  listener.sendEventToClient({
43
48
  type: 'sequence-props-updated',
44
49
  fileName,
45
- line,
46
- column,
50
+ nodePath,
47
51
  result,
48
52
  });
49
53
  });
@@ -56,14 +60,16 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, keys, remoti
56
60
  return initialResult;
57
61
  };
58
62
  exports.subscribeToSequencePropsWatchers = subscribeToSequencePropsWatchers;
59
- const unsubscribeFromSequencePropsWatchers = ({ fileName, line, column, remotionRoot, clientId, }) => {
60
- var _a;
63
+ const unsubscribeFromSequencePropsWatchers = ({ fileName, nodePath, remotionRoot, clientId, }) => {
61
64
  const absolutePath = node_path_1.default.resolve(remotionRoot, fileName);
62
- const watcherKey = makeWatcherKey({ absolutePath, line, column });
63
- if (!sequencePropsWatchers[clientId]) {
65
+ const watcherKey = makeWatcherKey({ absolutePath, nodePath });
66
+ if (!sequencePropsWatchers[clientId] ||
67
+ !sequencePropsWatchers[clientId][watcherKey]) {
68
+ // eslint-disable-next-line no-console
69
+ console.warn(`Unexpected: unsubscribe for sequence props watcher that does not exist (clientId=${clientId}, key=${watcherKey})`);
64
70
  return;
65
71
  }
66
- (_a = sequencePropsWatchers[clientId][watcherKey]) === null || _a === void 0 ? void 0 : _a.unwatch();
72
+ sequencePropsWatchers[clientId][watcherKey].unwatch();
67
73
  delete sequencePropsWatchers[clientId][watcherKey];
68
74
  };
69
75
  exports.unsubscribeFromSequencePropsWatchers = unsubscribeFromSequencePropsWatchers;
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.432",
6
+ "version": "4.0.433",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "sideEffects": false,
@@ -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.432",
30
+ "remotion": "4.0.433",
31
31
  "recast": "0.23.11",
32
- "@remotion/bundler": "4.0.432",
33
- "@remotion/renderer": "4.0.432",
34
- "@remotion/studio-shared": "4.0.432",
32
+ "@remotion/bundler": "4.0.433",
33
+ "@remotion/renderer": "4.0.433",
34
+ "@remotion/studio-shared": "4.0.433",
35
35
  "memfs": "3.4.3",
36
36
  "source-map": "0.7.3",
37
37
  "open": "^8.4.2"
@@ -41,10 +41,10 @@
41
41
  "react": "19.2.3",
42
42
  "@babel/types": "7.24.0",
43
43
  "@types/semver": "^7.3.4",
44
- "@remotion/eslint-config-internal": "4.0.432",
44
+ "@remotion/eslint-config-internal": "4.0.433",
45
45
  "eslint": "9.19.0",
46
46
  "@types/node": "20.12.14",
47
- "@typescript/native-preview": "7.0.0-dev.20260301.1"
47
+ "@typescript/native-preview": "7.0.0-dev.20260217.1"
48
48
  },
49
49
  "exports": {
50
50
  ".": "./dist/index.js",