@remotion/studio-server 4.0.508 → 4.0.510

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 (35) hide show
  1. package/dist/canvas-capture/generate-canvas-capture-composition.d.ts +14 -0
  2. package/dist/canvas-capture/generate-canvas-capture-composition.js +174 -0
  3. package/dist/codemods/delete-jsx-node.js +1 -1
  4. package/dist/codemods/duplicate-composition.js +8 -6
  5. package/dist/codemods/duplicate-jsx-node.js +1 -1
  6. package/dist/codemods/get-node-path-remappings.d.ts +4 -1
  7. package/dist/codemods/get-node-path-remappings.js +2 -1
  8. package/dist/codemods/recast-mods.js +13 -0
  9. package/dist/codemods/reorder-sequence.js +1 -1
  10. package/dist/codemods/split-jsx-sequence.d.ts +3 -4
  11. package/dist/codemods/split-jsx-sequence.js +6 -60
  12. package/dist/codemods/update-keyframes/update-keyframes.js +17 -10
  13. package/dist/helpers/git-client-registry.d.ts +30 -0
  14. package/dist/helpers/git-client-registry.js +130 -0
  15. package/dist/helpers/github-desktop-artwork.d.ts +1 -0
  16. package/dist/helpers/github-desktop-artwork.js +6 -0
  17. package/dist/helpers/resolve-composition-component.d.ts +2 -0
  18. package/dist/helpers/resolve-composition-component.js +4 -2
  19. package/dist/index.d.ts +2 -1
  20. package/dist/preview-server/api-routes.js +2 -0
  21. package/dist/preview-server/routes/app-icon.js +12 -7
  22. package/dist/preview-server/routes/apply-codemod.js +19 -3
  23. package/dist/preview-server/routes/default-coding-agent.js +3 -0
  24. package/dist/preview-server/routes/insert-jsx-element.js +7 -1
  25. package/dist/preview-server/routes/open-in-git-client.d.ts +3 -0
  26. package/dist/preview-server/routes/open-in-git-client.js +13 -0
  27. package/dist/preview-server/routes/save-sequence-props.js +19 -3
  28. package/dist/preview-server/routes/split-jsx-sequence.js +7 -2
  29. package/dist/preview-server/start-server.d.ts +1 -0
  30. package/dist/preview-server/start-server.js +1 -0
  31. package/dist/routes.d.ts +2 -1
  32. package/dist/routes.js +4 -2
  33. package/dist/start-studio.d.ts +2 -1
  34. package/dist/start-studio.js +2 -1
  35. package/package.json +8 -8
@@ -0,0 +1,14 @@
1
+ import type { CanvasCaptureData } from '@remotion/studio-shared';
2
+ export declare const generateCanvasCaptureComposition: ({ componentName, compositionId, data, durationInFrames, fps, height, keyframeFps, videoFileName, videoHeight, videoWidth, width, }: {
3
+ readonly componentName: string;
4
+ readonly compositionId: string;
5
+ readonly data: CanvasCaptureData;
6
+ readonly durationInFrames: number;
7
+ readonly fps: number;
8
+ readonly height: number;
9
+ readonly keyframeFps: number;
10
+ readonly videoFileName: string;
11
+ readonly videoHeight: number;
12
+ readonly videoWidth: number;
13
+ readonly width: number;
14
+ }) => Promise<string>;
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateCanvasCaptureComposition = void 0;
4
+ const duplicate_composition_1 = require("../codemods/duplicate-composition");
5
+ const customCursorRegex = /^\s*url\(/;
6
+ const collapseMouseMovementsByFrame = ({ data, keyframeFps, }) => {
7
+ const movements = [];
8
+ for (const movement of data.mouseMovements) {
9
+ if (movement.canvasX === null || movement.canvasY === null) {
10
+ continue;
11
+ }
12
+ const framedMovement = {
13
+ ...movement,
14
+ frame: Math.ceil(movement.timeInSeconds * keyframeFps),
15
+ };
16
+ const previous = movements.at(-1);
17
+ if ((previous === null || previous === void 0 ? void 0 : previous.frame) === framedMovement.frame) {
18
+ movements[movements.length - 1] = framedMovement;
19
+ }
20
+ else {
21
+ movements.push(framedMovement);
22
+ }
23
+ }
24
+ return movements;
25
+ };
26
+ const getCursorName = (cursor) => {
27
+ var _a;
28
+ if (customCursorRegex.test(cursor)) {
29
+ return 'custom';
30
+ }
31
+ return ((_a = cursor.split(',').at(-1)) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase()) || 'default';
32
+ };
33
+ const serialize = (value) => JSON.stringify(value);
34
+ const generateCanvasCaptureComposition = ({ componentName, compositionId, data, durationInFrames, fps, height, keyframeFps, videoFileName, videoHeight, videoWidth, width, }) => {
35
+ var _a;
36
+ const movements = collapseMouseMovementsByFrame({ data, keyframeFps });
37
+ if (movements.length === 0) {
38
+ throw new Error('The Canvas Capture does not contain cursor movements');
39
+ }
40
+ const cursorKeyframes = movements.reduce((keyframes, movement) => {
41
+ var _a;
42
+ const value = getCursorName(movement.cursor);
43
+ if (((_a = keyframes.at(-1)) === null || _a === void 0 ? void 0 : _a.value) !== value) {
44
+ keyframes.push({ frame: movement.frame, value });
45
+ }
46
+ return keyframes;
47
+ }, []);
48
+ const positionKeyframes = [];
49
+ for (const movement of movements) {
50
+ const previous = positionKeyframes.at(-1);
51
+ if (previous !== undefined && movement.frame - previous.frame > 1) {
52
+ positionKeyframes.push({
53
+ frame: movement.frame - 1,
54
+ value: previous.value,
55
+ });
56
+ }
57
+ positionKeyframes.push({
58
+ frame: movement.frame,
59
+ value: `${movement.canvasX}px ${movement.canvasY}px`,
60
+ });
61
+ }
62
+ const scaleKeyframes = [
63
+ { frame: 0, value: data.captureMetadata.density },
64
+ ...data.pointerClicks.map((click) => ({
65
+ frame: Math.ceil(click.timeInSeconds * keyframeFps),
66
+ value: click.type === 'pointer-down'
67
+ ? data.captureMetadata.density * 0.9
68
+ : data.captureMetadata.density,
69
+ })),
70
+ ].reduce((keyframes, keyframe) => {
71
+ var _a, _b;
72
+ if (((_a = keyframes.at(-1)) === null || _a === void 0 ? void 0 : _a.frame) === keyframe.frame) {
73
+ keyframes[keyframes.length - 1] = keyframe;
74
+ }
75
+ else if (((_b = keyframes.at(-1)) === null || _b === void 0 ? void 0 : _b.value) !== keyframe.value) {
76
+ keyframes.push(keyframe);
77
+ }
78
+ return keyframes;
79
+ }, []);
80
+ const customCursor = (_a = movements.find((movement) => customCursorRegex.test(movement.cursor))) === null || _a === void 0 ? void 0 : _a.cursor;
81
+ const cursorProp = cursorKeyframes.length === 1
82
+ ? `cursor=${serialize(cursorKeyframes[0].value)}`
83
+ : `cursor={interpolate(
84
+ frame,
85
+ ${serialize(cursorKeyframes.map((keyframe) => keyframe.frame))},
86
+ ${serialize(cursorKeyframes.map((keyframe) => keyframe.value))},
87
+ {
88
+ easing: Easing.step1,
89
+ extrapolateLeft: 'clamp',
90
+ extrapolateRight: 'clamp',
91
+ },
92
+ )}`;
93
+ const scale = scaleKeyframes.length === 1
94
+ ? serialize(scaleKeyframes[0].value)
95
+ : `interpolate(
96
+ frame,
97
+ ${serialize(scaleKeyframes.map((keyframe) => keyframe.frame))},
98
+ ${serialize(scaleKeyframes.map((keyframe) => keyframe.value))},
99
+ {
100
+ easing: Easing.step1,
101
+ extrapolateLeft: 'clamp',
102
+ extrapolateRight: 'clamp',
103
+ },
104
+ )`;
105
+ const translate = positionKeyframes.length === 1
106
+ ? serialize(positionKeyframes[0].value)
107
+ : `interpolate(
108
+ frame,
109
+ ${serialize(positionKeyframes.map((keyframe) => keyframe.frame))},
110
+ ${serialize(positionKeyframes.map((keyframe) => keyframe.value))},
111
+ {
112
+ extrapolateLeft: 'clamp',
113
+ extrapolateRight: 'clamp',
114
+ },
115
+ )`;
116
+ const previewComponentName = componentName.endsWith('Composition')
117
+ ? `${componentName.slice(0, -'Composition'.length)}Preview`
118
+ : `${componentName}Preview`;
119
+ return (0, duplicate_composition_1.formatOutput)(`import {MacOSCursor} from '@remotion/mac-cursors';
120
+ import {Video} from '@remotion/media';
121
+ import {
122
+ AbsoluteFill,
123
+ Composition,
124
+ Easing,
125
+ interpolate,
126
+ staticFile,
127
+ useCurrentFrame,
128
+ } from 'remotion';
129
+
130
+ export const ${previewComponentName} = () => {
131
+ const frame = useCurrentFrame();
132
+
133
+ return (
134
+ <AbsoluteFill
135
+ style={{
136
+ width: ${videoWidth},
137
+ height: ${videoHeight},
138
+ }}
139
+ >
140
+ <Video
141
+ src={staticFile(${serialize(videoFileName)})}
142
+ style={{
143
+ position: 'absolute',
144
+ }}
145
+ />
146
+ <MacOSCursor
147
+ ${cursorProp}
148
+ ${customCursor === undefined ? '' : `\t\t\t\tcustomCursor={${serialize(customCursor)}}\n`} style={{
149
+ position: 'absolute',
150
+ left: 0,
151
+ top: 0,
152
+ scale: ${scale},
153
+ translate: ${translate},
154
+ }}
155
+ />
156
+ </AbsoluteFill>
157
+ );
158
+ };
159
+
160
+ export const ${componentName} = () => {
161
+ return (
162
+ <Composition
163
+ id=${serialize(compositionId)}
164
+ component={${previewComponentName}}
165
+ width={${width}}
166
+ height={${height}}
167
+ fps={${fps}}
168
+ durationInFrames={${durationInFrames}}
169
+ />
170
+ );
171
+ };
172
+ `);
173
+ };
174
+ exports.generateCanvasCaptureComposition = generateCanvasCaptureComposition;
@@ -316,7 +316,7 @@ const deleteJsxNodes = async ({ input, nodePaths, prettierConfigOverride, }) =>
316
316
  input: finalFile,
317
317
  prettierConfigOverride,
318
318
  });
319
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
319
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
320
320
  ast,
321
321
  captured: capturedNodePaths,
322
322
  output,
@@ -81,12 +81,14 @@ const parseAndApplyCodemod = ({ input, codeMod, }) => {
81
81
  });
82
82
  }
83
83
  if (codeMod.type === 'new-composition') {
84
- (0, imports_1.ensureNamedImport)({
85
- ast: newAst,
86
- importedName: 'Composition',
87
- sourcePath: 'remotion',
88
- localName: 'Composition',
89
- });
84
+ if (codeMod.canvasCapture === null) {
85
+ (0, imports_1.ensureNamedImport)({
86
+ ast: newAst,
87
+ importedName: 'Composition',
88
+ sourcePath: 'remotion',
89
+ localName: 'Composition',
90
+ });
91
+ }
90
92
  (0, imports_1.ensureNamedImport)({
91
93
  ast: newAst,
92
94
  importedName: codeMod.componentName,
@@ -303,7 +303,7 @@ const duplicateJsxNode = async ({ input, nodePath, prettierConfigOverride, }) =>
303
303
  input: finalFile,
304
304
  prettierConfigOverride,
305
305
  });
306
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
306
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
307
307
  ast,
308
308
  captured: capturedNodePaths,
309
309
  output,
@@ -10,4 +10,7 @@ export declare const getNodePathRemappings: ({ ast, captured, output, }: {
10
10
  ast: File;
11
11
  captured: CapturedJsxNodePath[];
12
12
  output: string;
13
- }) => SequenceNodePathRemapping[];
13
+ }) => {
14
+ nodePathRemappings: SequenceNodePathRemapping[];
15
+ finalNodePathByNode: Map<JSXOpeningElement, SequenceNodePath>;
16
+ };
@@ -74,7 +74,7 @@ const getNodePathRemappings = ({ ast, captured, output, }) => {
74
74
  for (let i = 0; i < nodesAfterMutation.length; i++) {
75
75
  finalNodePathByNode.set(nodesAfterMutation[i], finalNodePaths[i]);
76
76
  }
77
- return captured.flatMap(({ node, nodePath }) => {
77
+ const nodePathRemappings = captured.flatMap(({ node, nodePath }) => {
78
78
  var _a;
79
79
  const newNodePath = (_a = finalNodePathByNode.get(node)) !== null && _a !== void 0 ? _a : null;
80
80
  if (newNodePath !== null &&
@@ -83,5 +83,6 @@ const getNodePathRemappings = ({ ast, captured, output, }) => {
83
83
  }
84
84
  return [{ oldNodePath: nodePath, newNodePath }];
85
85
  });
86
+ return { finalNodePathByNode, nodePathRemappings };
86
87
  };
87
88
  exports.getNodePathRemappings = getNodePathRemappings;
@@ -164,6 +164,19 @@ const jsxAttributeWithExpression = (name, expression) => ({
164
164
  },
165
165
  });
166
166
  const newCompositionElement = (transformation) => {
167
+ if (transformation.canvasCapture !== null) {
168
+ return {
169
+ type: 'JSXElement',
170
+ openingElement: {
171
+ type: 'JSXOpeningElement',
172
+ name: jsxId(transformation.componentName),
173
+ attributes: [],
174
+ selfClosing: true,
175
+ },
176
+ closingElement: null,
177
+ children: [],
178
+ };
179
+ }
167
180
  return {
168
181
  type: 'JSXElement',
169
182
  openingElement: {
@@ -101,7 +101,7 @@ const reorderSequence = async ({ input, sourceNodePath, targetNodePath, position
101
101
  input: finalFile,
102
102
  prettierConfigOverride,
103
103
  });
104
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
104
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
105
105
  ast,
106
106
  captured: capturedNodePaths,
107
107
  output,
@@ -1,10 +1,9 @@
1
- import type { SequenceNodePathRemapping } from '@remotion/studio-shared';
1
+ import { type SequenceNodePathRemapping } from '@remotion/studio-shared';
2
2
  import type { SequenceNodePath } from 'remotion';
3
- export declare const getSplitUnsupportedSequenceTagReason: (tagName: string) => string | null;
4
- export declare const getIsSplittableSequenceTag: (tagName: string) => boolean;
5
- export declare const splitJsxSequence: ({ input, nodePath, splitFrame, prettierConfigOverride, }: {
3
+ export declare const splitJsxSequence: ({ input, nodePath, sequenceKeys, splitFrame, prettierConfigOverride, }: {
6
4
  input: string;
7
5
  nodePath: SequenceNodePath;
6
+ sequenceKeys: string[];
8
7
  splitFrame: number;
9
8
  prettierConfigOverride?: Record<string, unknown> | null | undefined;
10
9
  }) => Promise<{
@@ -33,8 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.splitJsxSequence = exports.getIsSplittableSequenceTag = exports.getSplitUnsupportedSequenceTagReason = void 0;
36
+ exports.splitJsxSequence = void 0;
37
37
  const types_1 = require("@babel/types");
38
+ const studio_shared_1 = require("@remotion/studio-shared");
38
39
  const recast = __importStar(require("recast"));
39
40
  const delete_jsx_node_1 = require("./delete-jsx-node");
40
41
  const format_file_content_1 = require("./format-file-content");
@@ -172,38 +173,6 @@ const orderTimingAttributes = (element) => {
172
173
  const insertionIndex = Math.min(firstTimingIndex, otherAttributes.length);
173
174
  attributes.splice(0, attributes.length, ...otherAttributes.slice(0, insertionIndex), ...orderedTimingAttributes, ...otherAttributes.slice(insertionIndex));
174
175
  };
175
- const splittableSequenceTags = new Set([
176
- 'AnimatedImage',
177
- 'Arrow',
178
- 'Audio',
179
- 'Callout',
180
- 'CanvasImage',
181
- 'Circle',
182
- 'Ellipse',
183
- 'Gif',
184
- 'Heart',
185
- 'Html5Audio',
186
- 'Html5Video',
187
- 'HtmlInCanvas',
188
- 'Img',
189
- 'OffthreadVideo',
190
- 'Pie',
191
- 'Polygon',
192
- 'Rect',
193
- 'RemotionRiveCanvas',
194
- 'Sequence',
195
- 'Solid',
196
- 'Spark',
197
- 'Star',
198
- 'Starburst',
199
- 'Triangle',
200
- 'Video',
201
- ]);
202
- const unsupportedSequenceTags = new Set([
203
- 'Series.Sequence',
204
- 'TransitionSeries.Overlay',
205
- 'TransitionSeries.Sequence',
206
- ]);
207
176
  const jsxMemberNameToString = (name) => {
208
177
  if (name.type === 'JSXIdentifier') {
209
178
  return name.name;
@@ -216,25 +185,6 @@ const jsxNameToString = (name) => {
216
185
  }
217
186
  return jsxMemberNameToString(name);
218
187
  };
219
- const getSplitUnsupportedSequenceTagReason = (tagName) => {
220
- if (tagName === 'Series.Sequence' ||
221
- tagName === 'TransitionSeries.Sequence' ||
222
- tagName === 'TransitionSeries.Overlay') {
223
- return `<${tagName}> cannot be split from source`;
224
- }
225
- return null;
226
- };
227
- exports.getSplitUnsupportedSequenceTagReason = getSplitUnsupportedSequenceTagReason;
228
- const getIsSplittableSequenceTag = (tagName) => {
229
- if (unsupportedSequenceTags.has(tagName)) {
230
- return false;
231
- }
232
- if (tagName.startsWith('Interactive.')) {
233
- return true;
234
- }
235
- return splittableSequenceTags.has(tagName);
236
- };
237
- exports.getIsSplittableSequenceTag = getIsSplittableSequenceTag;
238
188
  const getSplittableSequenceTagName = (element) => {
239
189
  return jsxNameToString(element.openingElement.name);
240
190
  };
@@ -269,7 +219,7 @@ const insertAfter = (parentNode, node, clone) => {
269
219
  }
270
220
  return false;
271
221
  };
272
- const splitJsxSequence = async ({ input, nodePath, splitFrame, prettierConfigOverride, }) => {
222
+ const splitJsxSequence = async ({ input, nodePath, sequenceKeys, splitFrame, prettierConfigOverride, }) => {
273
223
  var _a, _b;
274
224
  var _c, _d;
275
225
  if (!Number.isInteger(splitFrame)) {
@@ -283,12 +233,8 @@ const splitJsxSequence = async ({ input, nodePath, splitFrame, prettierConfigOve
283
233
  }
284
234
  const jsxElement = jsxPath.node;
285
235
  const tagName = getSplittableSequenceTagName(jsxElement);
286
- const unsupportedReason = (0, exports.getSplitUnsupportedSequenceTagReason)(tagName);
287
- if (unsupportedReason) {
288
- throw new Error(unsupportedReason);
289
- }
290
- if (!(0, exports.getIsSplittableSequenceTag)(tagName)) {
291
- throw new Error(`<${tagName}> does not support sequence timing props and cannot be split`);
236
+ if (!(0, studio_shared_1.hasSequenceTimingTraits)(sequenceKeys)) {
237
+ throw new Error(`<${tagName}> cannot be split`);
292
238
  }
293
239
  const timing = readSequenceTiming(jsxElement);
294
240
  const finiteEnd = timing.durationInFrames === Infinity
@@ -342,7 +288,7 @@ const splitJsxSequence = async ({ input, nodePath, splitFrame, prettierConfigOve
342
288
  input: finalFile,
343
289
  prettierConfigOverride,
344
290
  });
345
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
291
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
346
292
  ast,
347
293
  captured: capturedNodePaths,
348
294
  output,
@@ -334,18 +334,16 @@ const getInlineOptionsFromExtraArgs = (extraArgs) => {
334
334
  }
335
335
  return existingOptions;
336
336
  };
337
- const normalizeEasingAfterAddingKeyframe = ({ extraArgs, previousSegmentCount, nextSegmentCount, insertedKeyframeIndex, nextKeyframeCount, }) => {
338
- const options = getInlineOptionsFromExtraArgs(extraArgs);
337
+ const normalizeEasingAfterAddingKeyframe = ({ extraArgs, previousSegmentCount, nextSegmentCount, insertedKeyframeIndex, nextKeyframeCount, defaultEasing, }) => {
338
+ var _a, _b;
339
+ const options = (_a = getInlineOptionsFromExtraArgs(extraArgs)) !== null && _a !== void 0 ? _a : (extraArgs.length === 0 ? createEmptyOptionsExpression() : null);
339
340
  if (!options) {
340
341
  return { extraArgs, needsEasingImport: false };
341
342
  }
342
- const easing = getExistingEasingArrayOrNull({
343
+ const easing = (_b = getExistingEasingArrayOrNull({
343
344
  options,
344
345
  segmentCount: previousSegmentCount,
345
- });
346
- if (easing === null) {
347
- return { extraArgs, needsEasingImport: false };
348
- }
346
+ })) !== null && _b !== void 0 ? _b : Array.from({ length: previousSegmentCount }, () => defaultEasing);
349
347
  if (easing.length < nextSegmentCount) {
350
348
  const isSplittingExistingSegment = insertedKeyframeIndex > 0 &&
351
349
  insertedKeyframeIndex < nextKeyframeCount - 1;
@@ -353,15 +351,16 @@ const normalizeEasingAfterAddingKeyframe = ({ extraArgs, previousSegmentCount, n
353
351
  ? Math.min(insertedKeyframeIndex - 1, easing.length - 1)
354
352
  : null;
355
353
  easing.splice(insertedKeyframeIndex, 0, easingIndexToDuplicate === null
356
- ? studio_shared_1.LINEAR_KEYFRAME_EASING
354
+ ? defaultEasing
357
355
  : easing[easingIndexToDuplicate]);
358
356
  }
359
357
  while (easing.length < nextSegmentCount) {
360
- easing.push(studio_shared_1.LINEAR_KEYFRAME_EASING);
358
+ easing.push(defaultEasing);
361
359
  }
360
+ const needsEasingImport = setEasingOption({ options, easing });
362
361
  return {
363
362
  extraArgs: getExtraArgsWithOptions({ extraArgs, options }),
364
- needsEasingImport: setEasingOption({ options, easing }),
363
+ needsEasingImport,
365
364
  };
366
365
  };
367
366
  const getEasingIndexToRemove = ({ removedKeyframeIndex, keyframeCountBeforeRemoval, }) => {
@@ -586,6 +585,9 @@ const addKeyframe = ({ expression, key, frame, value, schema, videoConfigValues,
586
585
  const existing = getInterpolationExpression(expression, videoConfigValues);
587
586
  const newOutput = (0, update_nested_prop_1.parseValueExpression)(value);
588
587
  if (existing) {
588
+ const defaultEasing = (0, studio_shared_1.isSchemaFieldHoldOnly)({ schema, key })
589
+ ? studio_shared_1.HOLD_KEYFRAME_EASING
590
+ : studio_shared_1.LINEAR_KEYFRAME_EASING;
589
591
  const existingCalleeName = existing.callee.type === 'Identifier'
590
592
  ? existing.callee.name
591
593
  : 'interpolate';
@@ -619,6 +621,7 @@ const addKeyframe = ({ expression, key, frame, value, schema, videoConfigValues,
619
621
  .sort((first, second) => first.frame - second.frame)
620
622
  .findIndex((keyframe) => keyframe.frame === frame),
621
623
  nextKeyframeCount: nextKeyframes.length,
624
+ defaultEasing,
622
625
  })
623
626
  : { extraArgs: existing.extraArgs, needsEasingImport: false };
624
627
  return {
@@ -804,6 +807,10 @@ const applyKeyframeOperation = ({ expression, key, operation, schema, videoConfi
804
807
  };
805
808
  }
806
809
  if (operation.type === 'easing') {
810
+ if ((0, studio_shared_1.isSchemaFieldHoldOnly)({ schema, key }) &&
811
+ operation.easing.type !== 'step1') {
812
+ throw new Error(`Cannot update easing: "${key}" only supports Easing.step1`);
813
+ }
807
814
  const updated = updateKeyframeEasing({
808
815
  expression,
809
816
  segmentIndex: operation.segmentIndex,
@@ -0,0 +1,30 @@
1
+ import type { GitClientId } from '@remotion/studio-shared';
2
+ type SupportedGitClientPlatform = 'darwin' | 'win32';
3
+ export type InstalledGitClient = {
4
+ id: GitClientId;
5
+ name: string;
6
+ applicationPath: string;
7
+ platform: SupportedGitClientPlatform;
8
+ };
9
+ export type GitClientDiscoveryContext = {
10
+ platform: NodeJS.Platform;
11
+ env: NodeJS.ProcessEnv;
12
+ homeDirectory: string;
13
+ pathExists: (filePath: string) => boolean;
14
+ findMacApplications: (bundleIdentifier: string) => Promise<readonly string[]>;
15
+ findWindowsApplications: (directory: string) => readonly string[];
16
+ };
17
+ export declare const discoverAvailableGitClients: (context: GitClientDiscoveryContext) => Promise<readonly InstalledGitClient[]>;
18
+ export declare const getAvailableGitClients: () => Promise<readonly InstalledGitClient[]>;
19
+ export declare const getGitClientLaunchInstruction: ({ gitClient, remotionRoot, }: {
20
+ gitClient: InstalledGitClient;
21
+ remotionRoot: string;
22
+ }) => {
23
+ command: string;
24
+ args: string[];
25
+ };
26
+ export declare const launchGitClient: ({ gitClient, remotionRoot, }: {
27
+ gitClient: InstalledGitClient;
28
+ remotionRoot: string;
29
+ }) => Promise<void>;
30
+ export {};
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.launchGitClient = exports.getGitClientLaunchInstruction = exports.getAvailableGitClients = exports.discoverAvailableGitClients = void 0;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const node_util_1 = require("node:util");
12
+ const bundler_1 = require("@remotion/bundler");
13
+ const execFilePromise = (0, node_util_1.promisify)(node_child_process_1.execFile);
14
+ const findMacApplications = async (bundleIdentifier) => {
15
+ try {
16
+ const { stdout } = await execFilePromise('mdfind', [
17
+ `kMDItemCFBundleIdentifier == '${bundleIdentifier}'`,
18
+ ]);
19
+ return stdout
20
+ .split('\n')
21
+ .map((line) => line.trim())
22
+ .filter(Boolean);
23
+ }
24
+ catch (_a) {
25
+ return [];
26
+ }
27
+ };
28
+ const defaultDiscoveryContext = {
29
+ platform: process.platform,
30
+ env: process.env,
31
+ homeDirectory: (0, node_os_1.homedir)(),
32
+ pathExists: node_fs_1.existsSync,
33
+ findMacApplications,
34
+ findWindowsApplications: (directory) => {
35
+ try {
36
+ return (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })
37
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith('app-'))
38
+ .map((entry) => node_path_1.default.win32.join(directory, entry.name, 'GitHubDesktop.exe'))
39
+ .sort()
40
+ .reverse();
41
+ }
42
+ catch (_a) {
43
+ return [];
44
+ }
45
+ },
46
+ };
47
+ const discoverAvailableGitClients = async (context) => {
48
+ if (context.platform === 'darwin') {
49
+ const discoveredApplications = await context.findMacApplications('com.github.GitHubClient');
50
+ const applicationPath = [
51
+ ...new Set([
52
+ '/Applications/GitHub Desktop.app',
53
+ node_path_1.default.posix.join(context.homeDirectory, 'Applications/GitHub Desktop.app'),
54
+ ...discoveredApplications,
55
+ ]),
56
+ ].find(context.pathExists);
57
+ return applicationPath
58
+ ? [
59
+ {
60
+ applicationPath,
61
+ id: 'github-desktop',
62
+ name: 'GitHub Desktop',
63
+ platform: 'darwin',
64
+ },
65
+ ]
66
+ : [];
67
+ }
68
+ if (context.platform === 'win32' && context.env.LOCALAPPDATA) {
69
+ const installationDirectory = node_path_1.default.win32.join(context.env.LOCALAPPDATA, 'GitHubDesktop');
70
+ const applicationPath = context
71
+ .findWindowsApplications(installationDirectory)
72
+ .find(context.pathExists);
73
+ return applicationPath
74
+ ? [
75
+ {
76
+ applicationPath,
77
+ id: 'github-desktop',
78
+ name: 'GitHub Desktop',
79
+ platform: 'win32',
80
+ },
81
+ ]
82
+ : [];
83
+ }
84
+ return [];
85
+ };
86
+ exports.discoverAvailableGitClients = discoverAvailableGitClients;
87
+ let availableGitClients = null;
88
+ const getAvailableGitClients = () => {
89
+ availableGitClients !== null && availableGitClients !== void 0 ? availableGitClients : (availableGitClients = (0, exports.discoverAvailableGitClients)(defaultDiscoveryContext));
90
+ return availableGitClients;
91
+ };
92
+ exports.getAvailableGitClients = getAvailableGitClients;
93
+ const getGitClientLaunchInstruction = ({ gitClient, remotionRoot, }) => {
94
+ var _a;
95
+ const repositoryRoot = (_a = bundler_1.BundlerInternals.findClosestFolderWithItem(remotionRoot, '.git')) !== null && _a !== void 0 ? _a : remotionRoot;
96
+ return gitClient.platform === 'darwin'
97
+ ? {
98
+ command: 'open',
99
+ args: [
100
+ '-n',
101
+ gitClient.applicationPath,
102
+ '--args',
103
+ `--cli-open=${repositoryRoot}`,
104
+ ],
105
+ }
106
+ : {
107
+ command: gitClient.applicationPath,
108
+ args: [`--cli-open=${repositoryRoot}`],
109
+ };
110
+ };
111
+ exports.getGitClientLaunchInstruction = getGitClientLaunchInstruction;
112
+ const launchGitClient = async ({ gitClient, remotionRoot, }) => {
113
+ const { command, args } = (0, exports.getGitClientLaunchInstruction)({
114
+ gitClient,
115
+ remotionRoot,
116
+ });
117
+ await new Promise((resolve, reject) => {
118
+ const child = (0, node_child_process_1.spawn)(command, args, {
119
+ cwd: remotionRoot,
120
+ detached: true,
121
+ stdio: 'ignore',
122
+ });
123
+ child.once('error', reject);
124
+ child.once('spawn', () => {
125
+ child.unref();
126
+ resolve();
127
+ });
128
+ });
129
+ };
130
+ exports.launchGitClient = launchGitClient;