@remotion/studio-server 4.0.471 → 4.0.472

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.
Files changed (31) hide show
  1. package/dist/codemods/add-effect.d.ts +11 -0
  2. package/dist/codemods/add-effect.js +14 -52
  3. package/dist/codemods/format-file-content.js +1 -1
  4. package/dist/codemods/parse-ast.js +4 -1
  5. package/dist/codemods/paste-effects.d.ts +15 -0
  6. package/dist/codemods/paste-effects.js +234 -0
  7. package/dist/codemods/recast-mods.js +178 -31
  8. package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.d.ts +1 -1
  9. package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.js +7 -55
  10. package/dist/codemods/update-keyframes/update-keyframes.d.ts +11 -6
  11. package/dist/codemods/update-keyframes/update-keyframes.js +127 -16
  12. package/dist/helpers/get-ast-node-path.js +6 -1
  13. package/dist/helpers/import-agnostic-node-path.d.ts +10 -0
  14. package/dist/helpers/import-agnostic-node-path.js +154 -0
  15. package/dist/helpers/imports.d.ts +16 -0
  16. package/dist/helpers/imports.js +145 -0
  17. package/dist/helpers/resolve-composition-component.js +114 -51
  18. package/dist/preview-server/api-routes.js +2 -0
  19. package/dist/preview-server/routes/add-effect-keyframe.js +2 -0
  20. package/dist/preview-server/routes/add-sequence-keyframe.js +1 -0
  21. package/dist/preview-server/routes/apply-codemod.js +18 -0
  22. package/dist/preview-server/routes/can-update-effect-props.d.ts +3 -2
  23. package/dist/preview-server/routes/can-update-effect-props.js +75 -6
  24. package/dist/preview-server/routes/can-update-sequence-props.js +33 -30
  25. package/dist/preview-server/routes/delete-keyframes.js +1 -0
  26. package/dist/preview-server/routes/insert-jsx-element.js +23 -0
  27. package/dist/preview-server/routes/paste-effects.d.ts +3 -0
  28. package/dist/preview-server/routes/paste-effects.js +78 -0
  29. package/dist/preview-server/routes/save-effect-props.js +1 -0
  30. package/dist/preview-server/undo-stack.d.ts +7 -1
  31. package/package.json +6 -6
@@ -1,4 +1,15 @@
1
+ import type { File, ObjectExpression } from '@babel/types';
1
2
  import type { SequenceNodePath } from 'remotion';
3
+ export declare const assertValidEffect: ({ effectName, effectImportPath, }: {
4
+ effectName: string;
5
+ effectImportPath: string;
6
+ }) => void;
7
+ export declare const ensureEffectImport: ({ ast, effectName, effectImportPath, }: {
8
+ ast: File;
9
+ effectName: string;
10
+ effectImportPath: string;
11
+ }) => string;
12
+ export declare const makeConfigObjectExpression: (config: Record<string, unknown>) => ObjectExpression;
2
13
  export declare const addEffect: ({ input, sequenceNodePath, effectName, effectImportPath, effectConfig, prettierConfigOverride, }: {
3
14
  input: string;
4
15
  sequenceNodePath: SequenceNodePath;
@@ -33,9 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.addEffect = void 0;
36
+ exports.addEffect = exports.makeConfigObjectExpression = exports.ensureEffectImport = exports.assertValidEffect = void 0;
37
37
  const studio_shared_1 = require("@remotion/studio-shared");
38
38
  const recast = __importStar(require("recast"));
39
+ const imports_1 = require("../helpers/imports");
39
40
  const can_update_sequence_props_1 = require("../preview-server/routes/can-update-sequence-props");
40
41
  const format_file_content_1 = require("./format-file-content");
41
42
  const parse_ast_1 = require("./parse-ast");
@@ -53,6 +54,7 @@ const assertValidEffect = ({ effectName, effectImportPath, }) => {
53
54
  throw new Error(`Unsupported effect import "${effectImportPath}"`);
54
55
  }
55
56
  };
57
+ exports.assertValidEffect = assertValidEffect;
56
58
  const parseValueExpression = (value) => {
57
59
  const code = `a = ${(0, studio_shared_1.stringifyDefaultProps)({ props: value, enumPaths: [] })}`;
58
60
  const ast = (0, parse_ast_1.parseAst)(code);
@@ -63,12 +65,6 @@ const parseValueExpression = (value) => {
63
65
  }
64
66
  return stmt.expression.right;
65
67
  };
66
- const getImportedName = (specifier) => {
67
- if (specifier.imported.type === 'Identifier') {
68
- return specifier.imported.name;
69
- }
70
- return specifier.imported.value;
71
- };
72
68
  const declarationBindsName = (declaration, name) => {
73
69
  var _a, _b;
74
70
  if (declaration.type === 'ClassDeclaration') {
@@ -106,16 +102,6 @@ const hasTopLevelBinding = ({ ast, name }) => {
106
102
  });
107
103
  });
108
104
  };
109
- const insertImportDeclaration = (ast, importDeclaration) => {
110
- const { body } = ast.program;
111
- let lastImportIndex = -1;
112
- for (let i = 0; i < body.length; i++) {
113
- if (body[i].type === 'ImportDeclaration') {
114
- lastImportIndex = i;
115
- }
116
- }
117
- body.splice(lastImportIndex + 1, 0, importDeclaration);
118
- };
119
105
  const getAvailableLocalName = ({ ast, effectName, }) => {
120
106
  if (!hasTopLevelBinding({ ast, name: effectName })) {
121
107
  return effectName;
@@ -133,40 +119,15 @@ const getAvailableLocalName = ({ ast, effectName, }) => {
133
119
  throw new Error(`Cannot find a local name for ${effectName}`);
134
120
  };
135
121
  const ensureEffectImport = ({ ast, effectName, effectImportPath, }) => {
136
- var _a, _b, _c;
137
- var _d;
138
- let importDeclaration = null;
139
- for (const node of ast.program.body) {
140
- if (node.type !== 'ImportDeclaration' ||
141
- node.source.value !== effectImportPath) {
142
- continue;
143
- }
144
- importDeclaration = node;
145
- const matchingSpecifier = (_a = node.specifiers) === null || _a === void 0 ? void 0 : _a.find((importSpecifier) => {
146
- return (importSpecifier.type === 'ImportSpecifier' &&
147
- getImportedName(importSpecifier) === effectName);
148
- });
149
- if ((_b = matchingSpecifier === null || matchingSpecifier === void 0 ? void 0 : matchingSpecifier.local) === null || _b === void 0 ? void 0 : _b.name) {
150
- return matchingSpecifier.local.name;
151
- }
152
- }
153
122
  const localName = getAvailableLocalName({ ast, effectName });
154
- const imported = b.identifier(effectName);
155
- const local = localName === effectName ? null : b.identifier(localName);
156
- const specifier = b.importSpecifier(imported, local);
157
- if (importDeclaration &&
158
- !((_c = importDeclaration.specifiers) === null || _c === void 0 ? void 0 : _c.some((importSpecifier) => importSpecifier.type === 'ImportNamespaceSpecifier'))) {
159
- importDeclaration.specifiers = [
160
- ...((_d = importDeclaration.specifiers) !== null && _d !== void 0 ? _d : []),
161
- specifier,
162
- ];
163
- return localName;
164
- }
165
- const newImport = b.importDeclaration([], b.stringLiteral(effectImportPath));
166
- newImport.specifiers = [specifier];
167
- insertImportDeclaration(ast, newImport);
168
- return localName;
123
+ return (0, imports_1.ensureNamedImport)({
124
+ ast,
125
+ importedName: effectName,
126
+ sourcePath: effectImportPath,
127
+ localName,
128
+ });
169
129
  };
130
+ exports.ensureEffectImport = ensureEffectImport;
170
131
  const getEffectsArray = (attr) => {
171
132
  if (!attr.value || attr.value.type !== 'JSXExpressionContainer') {
172
133
  throw new Error('Cannot add effect: effects prop is not an array');
@@ -188,6 +149,7 @@ const makeConfigObjectExpression = (config) => {
188
149
  return b.objectProperty(keyNode, parseValueExpression(value));
189
150
  }));
190
151
  };
152
+ exports.makeConfigObjectExpression = makeConfigObjectExpression;
191
153
  const getJsxTagLabel = (name) => {
192
154
  if (name.type === 'JSXIdentifier') {
193
155
  return `<${name.name}>`;
@@ -197,15 +159,15 @@ const getJsxTagLabel = (name) => {
197
159
  const addEffect = async ({ input, sequenceNodePath, effectName, effectImportPath, effectConfig, prettierConfigOverride, }) => {
198
160
  var _a;
199
161
  var _b, _c;
200
- assertValidEffect({ effectName, effectImportPath });
162
+ (0, exports.assertValidEffect)({ effectName, effectImportPath });
201
163
  const ast = (0, parse_ast_1.parseAst)(input);
202
164
  const jsx = (0, can_update_sequence_props_1.findJsxElementAtNodePath)(ast, sequenceNodePath);
203
165
  if (!jsx) {
204
166
  throw new Error('Could not find a JSX element at the specified location to add effect');
205
167
  }
206
- const localName = ensureEffectImport({ ast, effectName, effectImportPath });
168
+ const localName = (0, exports.ensureEffectImport)({ ast, effectName, effectImportPath });
207
169
  const effectCall = b.callExpression(b.identifier(localName), [
208
- makeConfigObjectExpression(effectConfig),
170
+ (0, exports.makeConfigObjectExpression)(effectConfig),
209
171
  ]);
210
172
  const attr = (0, update_effect_props_1.findEffectsAttr)((_b = jsx.attributes) !== null && _b !== void 0 ? _b : []);
211
173
  if (attr) {
@@ -47,7 +47,7 @@ const formatFileContent = async ({ input, prettierConfigOverride, }) => {
47
47
  }
48
48
  const { format, resolveConfig, resolveConfigFile } = prettier;
49
49
  let prettierConfig;
50
- if (prettierConfigOverride !== undefined) {
50
+ if (prettierConfigOverride !== undefined && prettierConfigOverride !== null) {
51
51
  prettierConfig = prettierConfigOverride;
52
52
  }
53
53
  else {
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.serializeAst = exports.parseAst = void 0;
37
37
  const recast = __importStar(require("recast"));
38
38
  const tsParser = __importStar(require("recast/parsers/babel-ts"));
39
+ const imports_1 = require("../helpers/imports");
39
40
  const parseAst = (input) => {
40
41
  return recast.parse(input, {
41
42
  parser: tsParser,
@@ -43,8 +44,10 @@ const parseAst = (input) => {
43
44
  };
44
45
  exports.parseAst = parseAst;
45
46
  const serializeAst = (ast) => {
46
- return recast.print(ast, {
47
+ const raw = recast.print(ast, {
47
48
  parser: tsParser,
48
49
  }).code;
50
+ // Normalize Windows line endings so normalizeImportSpacing regex works
51
+ return (0, imports_1.normalizeImportSpacing)(raw.replace(/\r\n/g, '\n'));
49
52
  };
50
53
  exports.serializeAst = serializeAst;
@@ -0,0 +1,15 @@
1
+ import type { EffectClipboardPasteType, EffectClipboardSnapshot } from '@remotion/studio-shared';
2
+ import type { SequenceNodePath } from 'remotion';
3
+ export declare const pasteEffects: ({ input, targetSequenceNodePath, type, effects, prettierConfigOverride, }: {
4
+ readonly input: string;
5
+ readonly targetFileName: string;
6
+ readonly targetSequenceNodePath: SequenceNodePath;
7
+ readonly type: EffectClipboardPasteType;
8
+ readonly effects: EffectClipboardSnapshot[];
9
+ readonly prettierConfigOverride?: Record<string, unknown> | null | undefined;
10
+ }) => Promise<{
11
+ readonly output: string;
12
+ readonly formatted: boolean;
13
+ readonly effectLabels: string[];
14
+ readonly logLine: number;
15
+ }>;
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.pasteEffects = void 0;
37
+ const recast = __importStar(require("recast"));
38
+ const get_ast_node_path_1 = require("../helpers/get-ast-node-path");
39
+ const imports_1 = require("../helpers/imports");
40
+ const can_update_sequence_props_1 = require("../preview-server/routes/can-update-sequence-props");
41
+ const add_effect_1 = require("./add-effect");
42
+ const format_file_content_1 = require("./format-file-content");
43
+ const parse_ast_1 = require("./parse-ast");
44
+ const update_effect_props_1 = require("./update-effect-props/update-effect-props");
45
+ const ensure_imports_and_frame_hook_1 = require("./update-keyframes/ensure-imports-and-frame-hook");
46
+ const update_nested_prop_1 = require("./update-nested-prop");
47
+ const b = recast.types.builders;
48
+ const identifierRegex = /^[A-Za-z_$][0-9A-Za-z_$]*$/;
49
+ const getEffectsArray = (attr) => {
50
+ if (!attr.value || attr.value.type !== 'JSXExpressionContainer') {
51
+ throw new Error('Cannot append effects: effects prop is not an array');
52
+ }
53
+ const expr = attr.value.expression;
54
+ if (expr.type !== 'ArrayExpression') {
55
+ throw new Error('Cannot append effects: effects prop is not an array');
56
+ }
57
+ return expr;
58
+ };
59
+ const makeEffectsAttr = (array) => {
60
+ return b.jsxAttribute(b.jsxIdentifier('effects'), b.jsxExpressionContainer(array));
61
+ };
62
+ const makeEffectsArray = (calls) => {
63
+ return b.arrayExpression(calls);
64
+ };
65
+ const isNonLinearEasing = (easing) => easing !== 'linear';
66
+ const keyframedParamNeedsEasingImport = (param) => {
67
+ return (param.interpolationFunction !== 'interpolateColors' &&
68
+ param.easing.some(isNonLinearEasing));
69
+ };
70
+ const getRequiredRemotionImports = (effects) => {
71
+ const requiredImports = new Set();
72
+ for (const effect of effects) {
73
+ for (const param of Object.values(effect.params)) {
74
+ if (param.type !== 'keyframed') {
75
+ continue;
76
+ }
77
+ requiredImports.add('useCurrentFrame');
78
+ requiredImports.add(param.interpolationFunction);
79
+ if (keyframedParamNeedsEasingImport(param)) {
80
+ requiredImports.add('Easing');
81
+ }
82
+ }
83
+ }
84
+ return requiredImports;
85
+ };
86
+ const ensureRemotionImportLocalNames = ({ ast, requiredImports, }) => {
87
+ const localNames = {};
88
+ for (const importedName of requiredImports) {
89
+ localNames[importedName] = (0, imports_1.ensureNamedImport)({
90
+ ast,
91
+ importedName,
92
+ sourcePath: 'remotion',
93
+ localName: importedName,
94
+ });
95
+ }
96
+ return localNames;
97
+ };
98
+ const makeEasingExpression = ({ easing, easingLocalName, }) => {
99
+ if (easing === 'linear') {
100
+ return b.memberExpression(b.identifier(easingLocalName), b.identifier('linear'));
101
+ }
102
+ return b.callExpression(b.memberExpression(b.identifier(easingLocalName), b.identifier('bezier')), easing.map((value) => (0, update_nested_prop_1.parseValueExpression)(value)));
103
+ };
104
+ const makeKeyframedOptions = ({ param, remotionLocalNames, }) => {
105
+ var _a;
106
+ const properties = [];
107
+ if (param.interpolationFunction !== 'interpolateColors') {
108
+ if (param.clamping.left !== 'extend') {
109
+ properties.push(b.objectProperty(b.identifier('extrapolateLeft'), b.stringLiteral(param.clamping.left)));
110
+ }
111
+ if (param.clamping.right !== 'extend') {
112
+ properties.push(b.objectProperty(b.identifier('extrapolateRight'), b.stringLiteral(param.clamping.right)));
113
+ }
114
+ if (keyframedParamNeedsEasingImport(param)) {
115
+ const easingLocalName = (_a = remotionLocalNames.Easing) !== null && _a !== void 0 ? _a : 'Easing';
116
+ properties.push(b.objectProperty(b.identifier('easing'), b.arrayExpression(param.easing.map((easing) => makeEasingExpression({ easing, easingLocalName })))));
117
+ }
118
+ }
119
+ if (param.posterize !== undefined) {
120
+ properties.push(b.objectProperty(b.identifier('posterize'), (0, update_nested_prop_1.parseValueExpression)(param.posterize)));
121
+ }
122
+ if (properties.length === 0) {
123
+ return null;
124
+ }
125
+ return b.objectExpression(properties);
126
+ };
127
+ const makeKeyframedExpression = ({ param, remotionLocalNames, }) => {
128
+ var _a;
129
+ const expectedEasingCount = Math.max(0, param.keyframes.length - 1);
130
+ if (param.easing.length !== expectedEasingCount) {
131
+ throw new Error('Cannot paste keyframed effect: invalid easing metadata');
132
+ }
133
+ const calleeName = (_a = remotionLocalNames[param.interpolationFunction]) !== null && _a !== void 0 ? _a : param.interpolationFunction;
134
+ const args = [
135
+ b.identifier('frame'),
136
+ b.arrayExpression(param.keyframes.map((keyframe) => (0, update_nested_prop_1.parseValueExpression)(keyframe.frame))),
137
+ b.arrayExpression(param.keyframes.map((keyframe) => (0, update_nested_prop_1.parseValueExpression)(keyframe.value))),
138
+ ];
139
+ const options = makeKeyframedOptions({ param, remotionLocalNames });
140
+ if (options) {
141
+ args.push(options);
142
+ }
143
+ return b.callExpression(b.identifier(calleeName), args);
144
+ };
145
+ const makeParamExpression = ({ param, remotionLocalNames, }) => {
146
+ if (param.type === 'static') {
147
+ return (0, update_nested_prop_1.parseValueExpression)(param.value);
148
+ }
149
+ return makeKeyframedExpression({ param, remotionLocalNames });
150
+ };
151
+ const makeParamsObjectExpression = ({ params, remotionLocalNames, }) => {
152
+ return b.objectExpression(Object.entries(params).map(([key, param]) => {
153
+ const keyNode = identifierRegex.test(key)
154
+ ? b.identifier(key)
155
+ : b.stringLiteral(key);
156
+ return b.objectProperty(keyNode, makeParamExpression({ param, remotionLocalNames }));
157
+ }));
158
+ };
159
+ const makeEffectCall = ({ ast, effect, remotionLocalNames, }) => {
160
+ (0, add_effect_1.assertValidEffect)({
161
+ effectName: effect.callee,
162
+ effectImportPath: effect.importPath,
163
+ });
164
+ const localName = (0, add_effect_1.ensureEffectImport)({
165
+ ast,
166
+ effectName: effect.callee,
167
+ effectImportPath: effect.importPath,
168
+ });
169
+ return b.callExpression(b.identifier(localName), [
170
+ makeParamsObjectExpression({
171
+ params: effect.params,
172
+ remotionLocalNames,
173
+ }),
174
+ ]);
175
+ };
176
+ const removeEffectsAttr = (attributes, attr) => {
177
+ const index = attributes.indexOf(attr);
178
+ if (index !== -1) {
179
+ attributes.splice(index, 1);
180
+ }
181
+ };
182
+ const pasteEffects = async ({ input, targetSequenceNodePath, type, effects, prettierConfigOverride, }) => {
183
+ var _a;
184
+ var _b, _c, _d;
185
+ const ast = (0, parse_ast_1.parseAst)(input);
186
+ const targetJsxPath = (0, get_ast_node_path_1.getAstNodePath)(ast, targetSequenceNodePath);
187
+ const targetJsx = (0, can_update_sequence_props_1.findJsxElementAtNodePath)(ast, targetSequenceNodePath);
188
+ if (!targetJsx || !targetJsxPath) {
189
+ throw new Error('Could not find a JSX element at the specified location to paste effects');
190
+ }
191
+ const requiredRemotionImports = getRequiredRemotionImports(effects);
192
+ const remotionLocalNames = ensureRemotionImportLocalNames({
193
+ ast,
194
+ requiredImports: requiredRemotionImports,
195
+ });
196
+ if (requiredRemotionImports.has('useCurrentFrame')) {
197
+ const fnPath = (0, ensure_imports_and_frame_hook_1.findEnclosingFunctionPath)(targetJsxPath);
198
+ if (fnPath) {
199
+ (0, ensure_imports_and_frame_hook_1.ensureUseCurrentFrameHook)(fnPath, (_b = remotionLocalNames.useCurrentFrame) !== null && _b !== void 0 ? _b : 'useCurrentFrame');
200
+ }
201
+ }
202
+ const effectCalls = effects.map((effect) => makeEffectCall({ ast, effect, remotionLocalNames }));
203
+ const effectLabels = effects.map((effect) => `${effect.callee}()`);
204
+ const existingAttr = (0, update_effect_props_1.findEffectsAttr)((_c = targetJsx.attributes) !== null && _c !== void 0 ? _c : []);
205
+ if (type === 'effects-replacing') {
206
+ if (existingAttr) {
207
+ removeEffectsAttr(targetJsx.attributes, existingAttr);
208
+ }
209
+ if (effectCalls.length > 0) {
210
+ targetJsx.attributes.push(makeEffectsAttr(makeEffectsArray(effectCalls)));
211
+ }
212
+ }
213
+ else if (existingAttr) {
214
+ getEffectsArray(existingAttr).elements.push(...effectCalls);
215
+ }
216
+ else if (effectCalls.length > 0) {
217
+ targetJsx.attributes.push(makeEffectsAttr(makeEffectsArray(effectCalls)));
218
+ }
219
+ else {
220
+ throw new Error('Cannot paste effects: no effects were copied');
221
+ }
222
+ const finalFile = (0, parse_ast_1.serializeAst)(ast);
223
+ const { output, formatted } = await (0, format_file_content_1.formatFileContent)({
224
+ input: finalFile,
225
+ prettierConfigOverride,
226
+ });
227
+ return {
228
+ output,
229
+ formatted,
230
+ effectLabels,
231
+ logLine: (_d = (_a = targetJsx.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _d !== void 0 ? _d : 1,
232
+ };
233
+ };
234
+ exports.pasteEffects = pasteEffects;