@remotion/studio-server 4.0.485 → 4.0.486

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.
@@ -94,6 +94,14 @@ const parseAndApplyCodemod = ({ input, codeMod, }) => {
94
94
  localName: codeMod.componentName,
95
95
  });
96
96
  }
97
+ if (codeMod.type === 'new-folder') {
98
+ (0, imports_1.ensureNamedImport)({
99
+ ast: newAst,
100
+ importedName: 'Folder',
101
+ sourcePath: 'remotion',
102
+ localName: 'Folder',
103
+ });
104
+ }
97
105
  const output = (0, parse_ast_1.serializeAst)(newAst);
98
106
  return { changesMade, newContents: output };
99
107
  };
@@ -36,12 +36,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.applyCodemod = void 0;
37
37
  const recast = __importStar(require("recast"));
38
38
  const apply_visual_control_1 = require("./apply-visual-control");
39
+ const delete_jsx_node_1 = require("./delete-jsx-node");
39
40
  const b = recast.types.builders;
40
41
  const applyCodemod = ({ file, codeMod, }) => {
41
42
  const changesMade = [];
42
43
  if (codeMod.type === 'apply-visual-control') {
43
44
  return (0, apply_visual_control_1.applyVisualControl)({ file, transformation: codeMod, changesMade });
44
45
  }
46
+ if (codeMod.type === 'move-composition-to-folder') {
47
+ return moveCompositionToFolder({
48
+ file,
49
+ transformation: codeMod,
50
+ changesMade,
51
+ });
52
+ }
45
53
  const body = file.program.body.map((node) => {
46
54
  return mapAll(node, codeMod, changesMade, null);
47
55
  });
@@ -104,6 +112,15 @@ const mapReturnStatement = (statement, transformation, changesMade, parentFolder
104
112
  argument: addNewCompositionToRootJsx(statement.argument, transformation, changesMade),
105
113
  };
106
114
  }
115
+ if (transformation.type === 'new-folder' &&
116
+ transformation.parentName === null &&
117
+ (statement.argument.type === 'JSXElement' ||
118
+ statement.argument.type === 'JSXFragment')) {
119
+ return {
120
+ ...statement,
121
+ argument: addNewFolderToRootJsx(statement.argument, transformation, changesMade),
122
+ };
123
+ }
107
124
  const replacement = transformLoneJsxElement(statement.argument, transformation, changesMade, parentFolderName);
108
125
  if (replacement !== null) {
109
126
  return { ...statement, argument: replacement };
@@ -177,6 +194,19 @@ const newCompositionElement = (transformation) => {
177
194
  children: [],
178
195
  };
179
196
  };
197
+ const newFolderElement = (transformation) => {
198
+ return {
199
+ type: 'JSXElement',
200
+ openingElement: {
201
+ type: 'JSXOpeningElement',
202
+ name: jsxId('Folder'),
203
+ attributes: [jsxAttributeWithString('name', transformation.folderName)],
204
+ selfClosing: true,
205
+ },
206
+ closingElement: null,
207
+ children: [],
208
+ };
209
+ };
180
210
  const addNewCompositionToRootJsx = (root, transformation, changesMade) => {
181
211
  if (changesMade.length > 0) {
182
212
  return root;
@@ -192,6 +222,21 @@ const addNewCompositionToRootJsx = (root, transformation, changesMade) => {
192
222
  }
193
223
  return wrapInJsxFragment([root, newCompositionElement(transformation)]);
194
224
  };
225
+ const addNewFolderToRootJsx = (root, transformation, changesMade) => {
226
+ if (changesMade.length > 0) {
227
+ return root;
228
+ }
229
+ changesMade.push({
230
+ description: 'Added new folder',
231
+ });
232
+ if (root.type === 'JSXFragment') {
233
+ return {
234
+ ...root,
235
+ children: [...root.children, newFolderElement(transformation)],
236
+ };
237
+ }
238
+ return wrapInJsxFragment([root, newFolderElement(transformation)]);
239
+ };
195
240
  const addNewCompositionToFolder = (folderElement, transformation, changesMade) => {
196
241
  var _a;
197
242
  if (changesMade.length > 0) {
@@ -216,6 +261,146 @@ const addNewCompositionToFolder = (folderElement, transformation, changesMade) =
216
261
  ],
217
262
  };
218
263
  };
264
+ const addNewFolderToFolder = (folderElement, transformation, changesMade) => {
265
+ var _a;
266
+ if (changesMade.length > 0) {
267
+ return folderElement;
268
+ }
269
+ changesMade.push({
270
+ description: 'Added new folder',
271
+ });
272
+ return {
273
+ ...folderElement,
274
+ openingElement: {
275
+ ...folderElement.openingElement,
276
+ selfClosing: false,
277
+ },
278
+ closingElement: (_a = folderElement.closingElement) !== null && _a !== void 0 ? _a : {
279
+ type: 'JSXClosingElement',
280
+ name: folderElement.openingElement.name,
281
+ },
282
+ children: [...folderElement.children, newFolderElement(transformation)],
283
+ };
284
+ };
285
+ const getChildFolderParentName = ({ folderName, parentFolderName, }) => {
286
+ return [parentFolderName, folderName].filter(Boolean).join('/');
287
+ };
288
+ const appendCompositionToFolder = ({ compositionElement, folderElement, }) => {
289
+ var _a;
290
+ folderElement.openingElement.selfClosing = false;
291
+ folderElement.closingElement = (_a = folderElement.closingElement) !== null && _a !== void 0 ? _a : {
292
+ type: 'JSXClosingElement',
293
+ name: folderElement.openingElement.name,
294
+ };
295
+ folderElement.children.push(stripParenthesizedExtra(compositionElement));
296
+ };
297
+ const appendCompositionToRoot = ({ compositionElement, file, }) => {
298
+ let appended = false;
299
+ recast.types.visit(file, {
300
+ visitReturnStatement(astPath) {
301
+ if (appended) {
302
+ return false;
303
+ }
304
+ const { argument } = astPath.node;
305
+ if ((argument === null || argument === void 0 ? void 0 : argument.type) !== 'JSXFragment' && (argument === null || argument === void 0 ? void 0 : argument.type) !== 'JSXElement') {
306
+ this.traverse(astPath);
307
+ return undefined;
308
+ }
309
+ if (argument.type === 'JSXFragment') {
310
+ argument.children.push(stripParenthesizedExtra(compositionElement));
311
+ }
312
+ else {
313
+ astPath.node.argument = wrapInJsxFragment([
314
+ argument,
315
+ compositionElement,
316
+ ]);
317
+ }
318
+ appended = true;
319
+ return false;
320
+ },
321
+ });
322
+ if (!appended) {
323
+ throw new Error('Could not find a root JSX element');
324
+ }
325
+ };
326
+ const moveCompositionToFolder = ({ file, transformation, changesMade, }) => {
327
+ let sourcePath = null;
328
+ let sourceParentFolderName = null;
329
+ let sourceIsDirectJsxChild = false;
330
+ let targetFolder = null;
331
+ const folderStack = [];
332
+ const isDirectJsxChild = (astPath) => {
333
+ var _a;
334
+ const parent = (_a = astPath.parentPath) === null || _a === void 0 ? void 0 : _a.node;
335
+ return (((parent === null || parent === void 0 ? void 0 : parent.type) === 'JSXElement' || (parent === null || parent === void 0 ? void 0 : parent.type) === 'JSXFragment') &&
336
+ parent.children.includes(astPath.node));
337
+ };
338
+ const visitJsxElementWithFolderContext = (astPath) => {
339
+ const node = astPath.node;
340
+ const compositionId = getCompositionIdFromJSXElement(node);
341
+ if (compositionId === transformation.idToMove) {
342
+ sourcePath = astPath;
343
+ sourceParentFolderName = folderStack.join('/') || null;
344
+ sourceIsDirectJsxChild = isDirectJsxChild(astPath);
345
+ }
346
+ const folderName = getFolderNameFromJSXElement(node);
347
+ const parentName = folderStack.join('/') || null;
348
+ if (transformation.folderName !== null &&
349
+ folderName === transformation.folderName &&
350
+ parentName === transformation.parentName) {
351
+ targetFolder = node;
352
+ }
353
+ if (folderName) {
354
+ folderStack.push(folderName);
355
+ }
356
+ for (let i = 0; i < node.children.length; i++) {
357
+ if (node.children[i].type !== 'JSXElement') {
358
+ continue;
359
+ }
360
+ visitJsxElementWithFolderContext(astPath.get('children', i));
361
+ }
362
+ if (folderName) {
363
+ folderStack.pop();
364
+ }
365
+ };
366
+ recast.types.visit(file, {
367
+ visitJSXElement(astPath) {
368
+ visitJsxElementWithFolderContext(astPath);
369
+ return false;
370
+ },
371
+ });
372
+ if (!sourcePath) {
373
+ throw new Error(`Could not find composition "${transformation.idToMove}"`);
374
+ }
375
+ if (!sourceIsDirectJsxChild) {
376
+ throw new Error(`Cannot move composition "${transformation.idToMove}" because it is not a direct JSX child`);
377
+ }
378
+ if (transformation.folderName === null && sourceParentFolderName === null) {
379
+ return { newAst: file, changesMade };
380
+ }
381
+ if (transformation.folderName !== null && !targetFolder) {
382
+ const folderLabel = `${transformation.parentName ? `${transformation.parentName}/` : ''}${transformation.folderName}`;
383
+ throw new Error(`Could not find folder "${folderLabel}"`);
384
+ }
385
+ const compositionElement = sourcePath
386
+ .node;
387
+ (0, delete_jsx_node_1.deleteJsxElementAtPath)(sourcePath);
388
+ if (transformation.folderName === null) {
389
+ appendCompositionToRoot({ compositionElement, file });
390
+ changesMade.push({ description: 'Moved composition to root' });
391
+ }
392
+ else {
393
+ if (targetFolder === null) {
394
+ throw new Error('Could not find target folder');
395
+ }
396
+ appendCompositionToFolder({
397
+ compositionElement,
398
+ folderElement: targetFolder,
399
+ });
400
+ changesMade.push({ description: 'Moved composition into folder' });
401
+ }
402
+ return { newAst: file, changesMade };
403
+ };
219
404
  // When a <Composition> JSX element appears in a position where it cannot
220
405
  // simply be removed from a parent's children list (e.g. as the sole return
221
406
  // value of a wrapper component or as the concise body of an arrow function),
@@ -237,7 +422,10 @@ const transformLoneJsxElement = (expression, transformation, changesMade, parent
237
422
  parentFolderName === transformation.parentName) ||
238
423
  (transformation.type === 'new-composition' &&
239
424
  folderName === transformation.folderName &&
240
- parentFolderName === transformation.parentName));
425
+ parentFolderName === transformation.parentName) ||
426
+ (transformation.type === 'new-folder' &&
427
+ getChildFolderParentName({ folderName, parentFolderName }) ===
428
+ transformation.parentName));
241
429
  if (!isFolderMatch) {
242
430
  return null;
243
431
  }
@@ -256,7 +444,11 @@ const transformLoneJsxElement = (expression, transformation, changesMade, parent
256
444
  parentFolderName === transformation.parentName) ||
257
445
  (transformation.type === 'new-composition' &&
258
446
  folderName === transformation.folderName &&
259
- parentFolderName === transformation.parentName);
447
+ parentFolderName === transformation.parentName) ||
448
+ (transformation.type === 'new-folder' &&
449
+ folderName !== null &&
450
+ getChildFolderParentName({ folderName, parentFolderName }) ===
451
+ transformation.parentName);
260
452
  if (!isMatch) {
261
453
  return null;
262
454
  }
@@ -283,9 +475,6 @@ const mapJsxElementOrFragment = (jsxFragment, transformation, changesMade, paren
283
475
  .flat(1),
284
476
  };
285
477
  };
286
- const getChildFolderParentName = ({ folderName, parentFolderName, }) => {
287
- return [parentFolderName, folderName].filter(Boolean).join('/');
288
- };
289
478
  const mapJsxChild = (c, transformation, changesMade, parentFolderName) => {
290
479
  const compId = getCompositionIdFromJSXElement(c);
291
480
  const folderName = getFolderNameFromJSXElement(c);
@@ -354,6 +543,12 @@ const mapJsxChild = (c, transformation, changesMade, parentFolderName) => {
354
543
  parentFolderName === transformation.parentName) {
355
544
  return [addNewCompositionToFolder(c, transformation, changesMade)];
356
545
  }
546
+ if (transformation.type === 'new-folder' &&
547
+ folderName !== null &&
548
+ getChildFolderParentName({ folderName, parentFolderName }) ===
549
+ transformation.parentName) {
550
+ return [addNewFolderToFolder(c, transformation, changesMade)];
551
+ }
357
552
  const childParentFolderName = folderName
358
553
  ? getChildFolderParentName({ folderName, parentFolderName })
359
554
  : parentFolderName;
@@ -374,6 +569,15 @@ const mapRecognizedType = (expression, transformation, changesMade, parentFolder
374
569
  body: addNewCompositionToRootJsx(expression.body, transformation, changesMade),
375
570
  };
376
571
  }
572
+ if (transformation.type === 'new-folder' &&
573
+ transformation.parentName === null &&
574
+ (expression.body.type === 'JSXElement' ||
575
+ expression.body.type === 'JSXFragment')) {
576
+ return {
577
+ ...expression,
578
+ body: addNewFolderToRootJsx(expression.body, transformation, changesMade),
579
+ };
580
+ }
377
581
  if (expression.type === 'ArrowFunctionExpression' &&
378
582
  expression.body.type === 'JSXElement') {
379
583
  const replacement = transformLoneJsxElement(expression.body, transformation, changesMade, parentFolderName);
@@ -1,8 +1,10 @@
1
+ import type { GoogleFontSourceEdit } from '@remotion/studio-shared';
1
2
  import type { InteractivitySchema, SequenceNodePath } from 'remotion';
2
3
  export type SequencePropUpdate = {
3
4
  key: string;
4
5
  value: unknown;
5
6
  defaultValue: unknown | null;
7
+ googleFont?: GoogleFontSourceEdit | null;
6
8
  };
7
9
  export type RemovedProp = {
8
10
  key: string;
@@ -134,6 +134,343 @@ const updateJsxTextContent = ({ jsxElement, value, }) => {
134
134
  }
135
135
  return staticTextContent.value;
136
136
  };
137
+ const collectTopLevelIdentifierNames = (ast) => {
138
+ const names = new Set();
139
+ recast.types.visit(ast, {
140
+ visitIdentifier(path) {
141
+ names.add(path.node.name);
142
+ this.traverse(path);
143
+ },
144
+ });
145
+ return names;
146
+ };
147
+ const toSafeIdentifier = (value) => {
148
+ const sanitized = value.replace(/[^a-zA-Z0-9_$]/g, '');
149
+ if (!sanitized) {
150
+ return 'loadGoogleFont';
151
+ }
152
+ return /^[a-zA-Z_$]/.test(sanitized) ? sanitized : `_${sanitized}`;
153
+ };
154
+ const getSafeLoadFontIdentifier = ({ font, usedNames, }) => {
155
+ const baseName = toSafeIdentifier(`load${font.importName}`);
156
+ if (!usedNames.has(baseName)) {
157
+ usedNames.add(baseName);
158
+ return baseName;
159
+ }
160
+ let index = 2;
161
+ while (usedNames.has(`${baseName}${index}`)) {
162
+ index++;
163
+ }
164
+ const name = `${baseName}${index}`;
165
+ usedNames.add(name);
166
+ return name;
167
+ };
168
+ const getImportedLoadFontIdentifier = ({ ast, font, }) => {
169
+ var _a;
170
+ var _b, _c;
171
+ const moduleName = `@remotion/google-fonts/${font.importName}`;
172
+ for (const statement of ast.program.body) {
173
+ if (statement.type !== 'ImportDeclaration' ||
174
+ statement.source.value !== moduleName) {
175
+ continue;
176
+ }
177
+ for (const specifier of (_b = statement.specifiers) !== null && _b !== void 0 ? _b : []) {
178
+ if (specifier.type === 'ImportSpecifier' &&
179
+ specifier.imported.type === 'Identifier' &&
180
+ specifier.imported.name === 'loadFont') {
181
+ return (_c = (_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) !== null && _c !== void 0 ? _c : 'loadFont';
182
+ }
183
+ }
184
+ }
185
+ return null;
186
+ };
187
+ const getInsertIndexAfterImportsOrDirectives = (ast) => {
188
+ const lastImportIndex = ast.program.body.reduce((lastIndex, statement, index) => {
189
+ return statement.type === 'ImportDeclaration' ? index : lastIndex;
190
+ }, -1);
191
+ if (lastImportIndex !== -1) {
192
+ return lastImportIndex + 1;
193
+ }
194
+ return ast.program.body.findIndex((statement) => {
195
+ return !(statement.type === 'ExpressionStatement' &&
196
+ 'directive' in statement &&
197
+ statement.directive);
198
+ });
199
+ };
200
+ const insertAfterImportsOrDirectives = (ast, statement) => {
201
+ const insertIndex = getInsertIndexAfterImportsOrDirectives(ast);
202
+ ast.program.body.splice(insertIndex === -1 ? ast.program.body.length : insertIndex, 0, statement);
203
+ };
204
+ const ensureLoadFontImport = ({ ast, font, usedNames, }) => {
205
+ var _a;
206
+ var _b;
207
+ const existing = getImportedLoadFontIdentifier({ ast, font });
208
+ if (existing) {
209
+ return existing;
210
+ }
211
+ const moduleName = `@remotion/google-fonts/${font.importName}`;
212
+ const localName = getSafeLoadFontIdentifier({ font, usedNames });
213
+ const specifier = b.importSpecifier(b.identifier('loadFont'), b.identifier(localName));
214
+ for (const statement of ast.program.body) {
215
+ if (statement.type === 'ImportDeclaration' &&
216
+ statement.source.value === moduleName &&
217
+ !((_a = statement.specifiers) === null || _a === void 0 ? void 0 : _a.some((existingSpecifier) => existingSpecifier.type === 'ImportNamespaceSpecifier'))) {
218
+ statement.specifiers = [
219
+ ...((_b = statement.specifiers) !== null && _b !== void 0 ? _b : []),
220
+ specifier,
221
+ ];
222
+ return localName;
223
+ }
224
+ }
225
+ const declaration = b.importDeclaration([specifier], b.stringLiteral(moduleName));
226
+ insertAfterImportsOrDirectives(ast, declaration);
227
+ return localName;
228
+ };
229
+ const getStringArrayObjectProperty = ({ node, key, }) => {
230
+ if (node.type !== 'ObjectExpression') {
231
+ return null;
232
+ }
233
+ const property = node.properties.find((prop) => {
234
+ return (prop.type === 'ObjectProperty' &&
235
+ prop.key.type === 'Identifier' &&
236
+ prop.key.name === key);
237
+ });
238
+ if (!property || property.type !== 'ObjectProperty') {
239
+ return null;
240
+ }
241
+ if (property.value.type !== 'ArrayExpression') {
242
+ return null;
243
+ }
244
+ const values = [];
245
+ for (const element of property.value.elements) {
246
+ if (!element || element.type !== 'StringLiteral') {
247
+ return null;
248
+ }
249
+ values.push(element.value);
250
+ }
251
+ return values;
252
+ };
253
+ const arraysEqual = (a, bValues) => {
254
+ return (a.length === bValues.length &&
255
+ a.every((value, index) => value === bValues[index]));
256
+ };
257
+ const isMatchingLoadFontCall = ({ statement, localName, font, }) => {
258
+ if (statement.type !== 'ExpressionStatement' ||
259
+ statement.expression.type !== 'CallExpression' ||
260
+ statement.expression.callee.type !== 'Identifier' ||
261
+ statement.expression.callee.name !== localName) {
262
+ return false;
263
+ }
264
+ const [style, options] = statement.expression.arguments;
265
+ if ((style === null || style === void 0 ? void 0 : style.type) !== 'StringLiteral' || style.value !== font.style) {
266
+ return false;
267
+ }
268
+ if (!options || options.type !== 'ObjectExpression') {
269
+ return false;
270
+ }
271
+ const weights = getStringArrayObjectProperty({ node: options, key: 'weights' });
272
+ const subsets = getStringArrayObjectProperty({ node: options, key: 'subsets' });
273
+ return (weights !== null &&
274
+ subsets !== null &&
275
+ arraysEqual(weights, font.weights) &&
276
+ arraysEqual(subsets, font.subsets));
277
+ };
278
+ const hasTopLevelLoadFontCall = ({ ast, localName, font, }) => {
279
+ return ast.program.body.some((statement) => {
280
+ return isMatchingLoadFontCall({ statement, localName, font });
281
+ });
282
+ };
283
+ const getGoogleFontImportName = (moduleName) => {
284
+ const prefix = '@remotion/google-fonts/';
285
+ return moduleName.startsWith(prefix) ? moduleName.slice(prefix.length) : null;
286
+ };
287
+ const normalizeFontFamilyName = (fontFamily) => {
288
+ return fontFamily.toLowerCase().replace(/[^a-z0-9]/g, '');
289
+ };
290
+ const getPrimaryFontFamilyName = (fontFamily) => {
291
+ const trimmed = fontFamily.trim();
292
+ if (!trimmed) {
293
+ return null;
294
+ }
295
+ const quote = trimmed[0];
296
+ if (quote === '"' || quote === "'") {
297
+ const closingQuote = trimmed.indexOf(quote, 1);
298
+ return closingQuote === -1
299
+ ? trimmed.slice(1)
300
+ : trimmed.slice(1, closingQuote);
301
+ }
302
+ return trimmed.split(',')[0].trim();
303
+ };
304
+ const collectUsedFontFamilyNames = (ast) => {
305
+ const used = new Set();
306
+ const add = (fontFamily) => {
307
+ const primary = getPrimaryFontFamilyName(fontFamily);
308
+ if (primary) {
309
+ used.add(normalizeFontFamilyName(primary));
310
+ }
311
+ };
312
+ recast.types.visit(ast, {
313
+ visitObjectProperty(path) {
314
+ var _a;
315
+ const { node } = path;
316
+ if (node.key.type === 'Identifier' && node.key.name === 'fontFamily') {
317
+ if (node.value.type === 'StringLiteral') {
318
+ add(node.value.value);
319
+ }
320
+ if (node.value.type === 'TemplateLiteral' &&
321
+ node.value.expressions.length === 0 &&
322
+ node.value.quasis.length === 1) {
323
+ add((_a = node.value.quasis[0].value.cooked) !== null && _a !== void 0 ? _a : node.value.quasis[0].value.raw);
324
+ }
325
+ }
326
+ this.traverse(path);
327
+ },
328
+ visitJSXAttribute(path) {
329
+ var _a;
330
+ const { node } = path;
331
+ if (node.name.type === 'JSXIdentifier' &&
332
+ node.name.name === 'fontFamily' &&
333
+ ((_a = node.value) === null || _a === void 0 ? void 0 : _a.type) === 'StringLiteral') {
334
+ add(node.value.value);
335
+ }
336
+ this.traverse(path);
337
+ },
338
+ });
339
+ return used;
340
+ };
341
+ const isIdentifierImportedBySpecifier = ({ parent, name, }) => {
342
+ return (parent !== null &&
343
+ typeof parent === 'object' &&
344
+ 'type' in parent &&
345
+ parent.type === 'ImportSpecifier' &&
346
+ 'local' in parent &&
347
+ parent.local !== null &&
348
+ typeof parent.local === 'object' &&
349
+ 'type' in parent.local &&
350
+ parent.local.type === 'Identifier' &&
351
+ 'name' in parent.local &&
352
+ parent.local.name === name);
353
+ };
354
+ const hasNonImportIdentifierReference = ({ ast, name, }) => {
355
+ let hasReference = false;
356
+ recast.types.visit(ast, {
357
+ visitIdentifier(path) {
358
+ if (path.node.name !== name) {
359
+ this.traverse(path);
360
+ return;
361
+ }
362
+ if (isIdentifierImportedBySpecifier({ parent: path.parent.node, name })) {
363
+ this.traverse(path);
364
+ return;
365
+ }
366
+ hasReference = true;
367
+ return false;
368
+ },
369
+ });
370
+ return hasReference;
371
+ };
372
+ const googleFontLoadingComment = 'Remotion Studio generated Google Font loading';
373
+ const hasStudioGeneratedGoogleFontLoadingComment = (statement) => {
374
+ var _a;
375
+ return ((_a = statement.leadingComments) !== null && _a !== void 0 ? _a : []).some((comment) => comment.value.includes(googleFontLoadingComment));
376
+ };
377
+ const isTopLevelCallToIdentifier = ({ statement, localName, }) => {
378
+ return (statement.type === 'ExpressionStatement' &&
379
+ statement.expression.type === 'CallExpression' &&
380
+ statement.expression.callee.type === 'Identifier' &&
381
+ statement.expression.callee.name === localName);
382
+ };
383
+ const removeUnusedGoogleFontSourceEdits = (ast) => {
384
+ var _a;
385
+ var _b, _c;
386
+ const usedFonts = collectUsedFontFamilyNames(ast);
387
+ const loadFontLocalNamesToRemove = new Set();
388
+ for (const statement of ast.program.body) {
389
+ if (statement.type !== 'ImportDeclaration' ||
390
+ typeof statement.source.value !== 'string') {
391
+ continue;
392
+ }
393
+ const importName = getGoogleFontImportName(statement.source.value);
394
+ if (!importName) {
395
+ continue;
396
+ }
397
+ if (usedFonts.has(normalizeFontFamilyName(importName))) {
398
+ continue;
399
+ }
400
+ for (const specifier of (_b = statement.specifiers) !== null && _b !== void 0 ? _b : []) {
401
+ if (specifier.type === 'ImportSpecifier' &&
402
+ specifier.imported.type === 'Identifier' &&
403
+ specifier.imported.name === 'loadFont') {
404
+ loadFontLocalNamesToRemove.add((_c = (_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) !== null && _c !== void 0 ? _c : 'loadFont');
405
+ }
406
+ }
407
+ }
408
+ if (loadFontLocalNamesToRemove.size === 0) {
409
+ return;
410
+ }
411
+ ast.program.body = ast.program.body.filter((statement) => {
412
+ for (const localName of loadFontLocalNamesToRemove) {
413
+ if (isTopLevelCallToIdentifier({ statement, localName }) &&
414
+ hasStudioGeneratedGoogleFontLoadingComment(statement)) {
415
+ return false;
416
+ }
417
+ }
418
+ return true;
419
+ });
420
+ ast.program.body = ast.program.body.filter((statement) => {
421
+ var _a;
422
+ if (statement.type !== 'ImportDeclaration') {
423
+ return true;
424
+ }
425
+ statement.specifiers = ((_a = statement.specifiers) !== null && _a !== void 0 ? _a : []).filter((specifier) => {
426
+ var _a, _b;
427
+ var _c, _d;
428
+ return !(specifier.type === 'ImportSpecifier' &&
429
+ specifier.imported.type === 'Identifier' &&
430
+ specifier.imported.name === 'loadFont' &&
431
+ loadFontLocalNamesToRemove.has((_c = (_a = specifier.local) === null || _a === void 0 ? void 0 : _a.name) !== null && _c !== void 0 ? _c : 'loadFont') &&
432
+ !hasNonImportIdentifierReference({
433
+ ast,
434
+ name: (_d = (_b = specifier.local) === null || _b === void 0 ? void 0 : _b.name) !== null && _d !== void 0 ? _d : 'loadFont',
435
+ }));
436
+ });
437
+ return statement.specifiers.length > 0;
438
+ });
439
+ };
440
+ const insertLoadFontCall = ({ ast, font, localName, }) => {
441
+ if (hasTopLevelLoadFontCall({ ast, localName, font })) {
442
+ return;
443
+ }
444
+ const call = b.expressionStatement(b.callExpression(b.identifier(localName), [
445
+ b.stringLiteral(font.style),
446
+ b.objectExpression([
447
+ b.objectProperty(b.identifier('weights'), b.arrayExpression(font.weights.map((weight) => b.stringLiteral(weight)))),
448
+ b.objectProperty(b.identifier('subsets'), b.arrayExpression(font.subsets.map((subset) => b.stringLiteral(subset)))),
449
+ ]),
450
+ ]));
451
+ call.comments = [b.commentLine(` ${googleFontLoadingComment}`)];
452
+ insertAfterImportsOrDirectives(ast, call);
453
+ };
454
+ const applyGoogleFontSourceEdits = ({ ast, updates, }) => {
455
+ const fonts = new Map();
456
+ let hasFontFamilyUpdate = false;
457
+ for (const update of updates) {
458
+ if (update.key === 'style.fontFamily') {
459
+ hasFontFamilyUpdate = true;
460
+ }
461
+ if (update.googleFont) {
462
+ fonts.set(update.googleFont.importName, update.googleFont);
463
+ }
464
+ }
465
+ const usedNames = collectTopLevelIdentifierNames(ast);
466
+ for (const font of fonts.values()) {
467
+ const localName = ensureLoadFontImport({ ast, font, usedNames });
468
+ insertLoadFontCall({ ast, font, localName });
469
+ }
470
+ if (hasFontFamilyUpdate) {
471
+ removeUnusedGoogleFontSourceEdits(ast);
472
+ }
473
+ };
137
474
  const updateSequencePropsNode = ({ jsxElement, updates, schema, }) => {
138
475
  var _a, _b, _c;
139
476
  var _d, _e;
@@ -152,8 +489,9 @@ const updateSequencePropsNode = ({ jsxElement, updates, schema, }) => {
152
489
  oldValueStrings.push(oldValueString);
153
490
  continue;
154
491
  }
155
- const isDefault = defaultValue !== null &&
156
- JSON.stringify(value) === JSON.stringify(defaultValue);
492
+ const isDefault = (defaultValue === null && value === undefined) ||
493
+ (defaultValue !== null &&
494
+ JSON.stringify(value) === JSON.stringify(defaultValue));
157
495
  const dotIndex = key.indexOf('.');
158
496
  const isNested = dotIndex !== -1;
159
497
  const parentKey = isNested ? key.slice(0, dotIndex) : key;
@@ -261,6 +599,7 @@ const updateSequencePropsAst = ({ input, nodePath, updates, schema, }) => {
261
599
  updates,
262
600
  schema,
263
601
  });
602
+ applyGoogleFontSourceEdits({ ast, updates });
264
603
  return {
265
604
  serialized: (0, parse_ast_1.serializeAst)(ast),
266
605
  oldValueStrings,
@@ -271,13 +610,16 @@ const updateSequencePropsAst = ({ input, nodePath, updates, schema, }) => {
271
610
  exports.updateSequencePropsAst = updateSequencePropsAst;
272
611
  const updateMultipleSequenceProps = async ({ input, changes, prettierConfigOverride, }) => {
273
612
  const ast = (0, parse_ast_1.parseAst)(input);
613
+ const allUpdates = [];
274
614
  const results = changes.map(({ nodePath, updates, schema }) => {
275
615
  const jsxElement = (0, can_update_sequence_props_1.findJsxElementNodeAtNodePath)(ast, nodePath);
276
616
  if (!jsxElement) {
277
617
  throw new Error('Could not find a JSX element at the specified line to update');
278
618
  }
619
+ allUpdates.push(...updates);
279
620
  return updateSequencePropsNode({ jsxElement, updates, schema });
280
621
  });
622
+ applyGoogleFontSourceEdits({ ast, updates: allUpdates });
281
623
  const { output, formatted } = await (0, format_file_content_1.formatFileContent)({
282
624
  input: (0, parse_ast_1.serializeAst)(ast),
283
625
  prettierConfigOverride,