@remotion/studio-server 4.0.502 → 4.0.504

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 (60) hide show
  1. package/dist/detect-outdated-remotion-skills.d.ts +25 -0
  2. package/dist/detect-outdated-remotion-skills.js +73 -0
  3. package/dist/helpers/custom-editor.d.ts +37 -0
  4. package/dist/helpers/custom-editor.js +167 -0
  5. package/dist/helpers/editor-registry.d.ts +18 -0
  6. package/dist/helpers/editor-registry.js +500 -0
  7. package/dist/helpers/open-in-editor.d.ts +4 -4
  8. package/dist/helpers/open-in-editor.js +23 -87
  9. package/dist/helpers/resolve-editor.d.ts +27 -0
  10. package/dist/helpers/resolve-editor.js +90 -0
  11. package/dist/helpers/resolve-file-inside-project.d.ts +2 -0
  12. package/dist/helpers/resolve-file-inside-project.js +5 -2
  13. package/dist/index.d.ts +17 -1
  14. package/dist/index.js +9 -1
  15. package/dist/preview-server/api-routes.js +6 -1
  16. package/dist/preview-server/api-types.d.ts +2 -1
  17. package/dist/preview-server/element-install-state.d.ts +9 -2
  18. package/dist/preview-server/element-install-state.js +15 -5
  19. package/dist/preview-server/handler.d.ts +3 -1
  20. package/dist/preview-server/handler.js +2 -1
  21. package/dist/preview-server/routes/can-update-sequence-props.js +2 -1
  22. package/dist/preview-server/routes/default-editor.d.ts +8 -0
  23. package/dist/preview-server/routes/default-editor.js +131 -0
  24. package/dist/preview-server/routes/delete-effect-keyframe.d.ts +3 -0
  25. package/dist/preview-server/routes/delete-effect-keyframe.js +89 -0
  26. package/dist/preview-server/routes/delete-sequence-keyframe.d.ts +3 -0
  27. package/dist/preview-server/routes/delete-sequence-keyframe.js +82 -0
  28. package/dist/preview-server/routes/element-install-plan.d.ts +33 -0
  29. package/dist/preview-server/routes/element-install-plan.js +122 -0
  30. package/dist/preview-server/routes/insert-element.d.ts +1 -1
  31. package/dist/preview-server/routes/insert-element.js +70 -107
  32. package/dist/preview-server/routes/install-dependency.d.ts +2 -1
  33. package/dist/preview-server/routes/install-dependency.js +33 -44
  34. package/dist/preview-server/routes/open-in-editor.d.ts +5 -1
  35. package/dist/preview-server/routes/open-in-editor.js +42 -12
  36. package/dist/preview-server/routes/prepare-element-install.d.ts +3 -0
  37. package/dist/preview-server/routes/prepare-element-install.js +25 -0
  38. package/dist/preview-server/routes/safe-element-install-path.d.ts +9 -0
  39. package/dist/preview-server/routes/safe-element-install-path.js +58 -0
  40. package/dist/preview-server/routes/save-props-mutex.d.ts +1 -0
  41. package/dist/preview-server/routes/save-props-mutex.js +11 -0
  42. package/dist/preview-server/routes/update-public-license.d.ts +4 -0
  43. package/dist/preview-server/routes/update-public-license.js +9 -5
  44. package/dist/preview-server/start-server.d.ts +2 -0
  45. package/dist/preview-server/start-server.js +1 -0
  46. package/dist/preview-server/studio-protocol/handle-discovery.js +57 -26
  47. package/dist/preview-server/studio-protocol/handle-install.js +13 -3
  48. package/dist/preview-server/studio-protocol/handle-license-key.d.ts +6 -0
  49. package/dist/preview-server/studio-protocol/handle-license-key.js +129 -0
  50. package/dist/preview-server/studio-protocol/origin-policy.d.ts +6 -3
  51. package/dist/preview-server/studio-protocol/origin-policy.js +43 -17
  52. package/dist/preview-server/update-available.d.ts +6 -0
  53. package/dist/preview-server/update-available.js +30 -4
  54. package/dist/remotion-skill-names.d.ts +2 -0
  55. package/dist/remotion-skill-names.js +16 -0
  56. package/dist/routes.d.ts +3 -1
  57. package/dist/routes.js +16 -5
  58. package/dist/start-studio.d.ts +3 -1
  59. package/dist/start-studio.js +2 -1
  60. package/package.json +8 -8
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteSequenceKeyframeHandler = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const renderer_1 = require("@remotion/renderer");
6
+ const studio_shared_1 = require("@remotion/studio-shared");
7
+ const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
8
+ const file_watcher_1 = require("../../file-watcher");
9
+ const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
10
+ const undo_stack_1 = require("../undo-stack");
11
+ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
12
+ const can_update_sequence_props_1 = require("./can-update-sequence-props");
13
+ const log_update_1 = require("./log-updates/log-update");
14
+ const save_props_mutex_1 = require("./save-props-mutex");
15
+ const deleteSequenceKeyframeHandler = ({ input: { fileName, nodePath, key, frame, schema, clientId }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
16
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[delete-sequence-keyframe] Received request for fileName="${fileName}" key="${key}" frame=${frame}`);
17
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
18
+ remotionRoot,
19
+ fileName,
20
+ action: 'modify',
21
+ });
22
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
23
+ const { output, oldValueStrings, newValueStrings, formatted, logLine } = await (0, update_keyframes_1.updateSequenceKeyframes)({
24
+ input: fileContents,
25
+ nodePath: nodePath.nodePath,
26
+ updates: [
27
+ {
28
+ key,
29
+ operation: {
30
+ type: 'remove',
31
+ frame,
32
+ },
33
+ },
34
+ ],
35
+ });
36
+ const oldValueString = oldValueStrings[0];
37
+ const newValueString = newValueStrings[0];
38
+ const undoPropChange = `${key} keyframe restored at frame ${frame}`;
39
+ const redoPropChange = `${key} keyframe deleted at frame ${frame}`;
40
+ (0, undo_stack_1.pushToUndoStack)({
41
+ filePath: absolutePath,
42
+ oldContents: fileContents,
43
+ newContents: null,
44
+ logLevel,
45
+ remotionRoot,
46
+ logLine,
47
+ description: {
48
+ undoMessage: `↩️ ${undoPropChange}`,
49
+ redoMessage: `↪️ ${redoPropChange}`,
50
+ },
51
+ entryType: 'sequence-props',
52
+ suppressHmrOnFileRestore: true,
53
+ });
54
+ (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
55
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
56
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output, clientId);
57
+ (0, log_update_1.logUpdate)({
58
+ fileRelativeToRoot,
59
+ line: logLine,
60
+ key,
61
+ oldValueString,
62
+ newValueString,
63
+ defaultValueString: null,
64
+ formatted,
65
+ logLevel,
66
+ removedProps: [],
67
+ addedProps: [],
68
+ });
69
+ (0, undo_stack_1.printUndoHint)(logLevel);
70
+ const status = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
71
+ fileContents: output,
72
+ keys: (0, studio_shared_1.getAllSchemaKeys)(schema),
73
+ nodePath: nodePath.nodePath,
74
+ effects: [],
75
+ });
76
+ return {
77
+ canUpdate: true,
78
+ props: status.props,
79
+ results: [{ fileName, nodePath, props: status.props }],
80
+ };
81
+ });
82
+ exports.deleteSequenceKeyframeHandler = deleteSequenceKeyframeHandler;
@@ -0,0 +1,33 @@
1
+ import type { ElementInstallExpectedFileState, PrepareElementInstallRequest } from '@remotion/studio-shared';
2
+ export declare const normalizeElementSourceForComparison: (source: string) => string;
3
+ export declare const getElementSourceHash: (source: string) => string;
4
+ export declare const validateElementInstallPosition: (position: import("@remotion/studio-shared").InsertableCompositionElementPosition | null) => void;
5
+ export declare const validateElementForInstallation: (element: {
6
+ dependencies: import("@remotion/studio-protocol").ElementDependency[];
7
+ durationInFrames?: number | undefined;
8
+ slug: string;
9
+ displayName: string;
10
+ sourceCode: string;
11
+ dimensions: import("@remotion/studio-protocol").ComponentDimensions | null;
12
+ }) => void;
13
+ export declare const makeRelativeElementImportPath: ({ fromFile, toFile, }: {
14
+ fromFile: string;
15
+ toFile: string;
16
+ }) => string;
17
+ export declare const getElementInstallPlan: ({ compositionFile, compositionId, element, remotionRoot, }: PrepareElementInstallRequest & {
18
+ remotionRoot: string;
19
+ }) => Promise<{
20
+ componentName: string;
21
+ elementFileExists: boolean;
22
+ elementFileName: string;
23
+ existingElementSource: string | null;
24
+ expectedFileState: ElementInstallExpectedFileState;
25
+ filePath: string;
26
+ importPath: string;
27
+ location: import("../../helpers/resolve-composition-component").ResolvedCompositionComponentWithFile;
28
+ safePaths: {
29
+ compositionFileName: string;
30
+ elementFileName: string;
31
+ remotionRoot: string;
32
+ };
33
+ }>;
@@ -0,0 +1,122 @@
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.getElementInstallPlan = exports.makeRelativeElementImportPath = exports.validateElementForInstallation = exports.validateElementInstallPosition = exports.getElementSourceHash = exports.normalizeElementSourceForComparison = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const studio_protocol_1 = require("@remotion/studio-protocol");
11
+ const resolve_composition_component_1 = require("../../helpers/resolve-composition-component");
12
+ const safe_element_install_path_1 = require("./safe-element-install-path");
13
+ const normalizeElementSourceForComparison = (source) => {
14
+ return source.replace(/\r\n/g, '\n').trim();
15
+ };
16
+ exports.normalizeElementSourceForComparison = normalizeElementSourceForComparison;
17
+ const getElementSourceHash = (source) => {
18
+ return (0, node_crypto_1.createHash)('sha256').update(source).digest('hex');
19
+ };
20
+ exports.getElementSourceHash = getElementSourceHash;
21
+ const validateElementInstallPosition = (position) => {
22
+ if (position === null) {
23
+ return;
24
+ }
25
+ if (!Number.isFinite(position.x) || !Number.isFinite(position.y)) {
26
+ throw new Error('Position must be finite');
27
+ }
28
+ };
29
+ exports.validateElementInstallPosition = validateElementInstallPosition;
30
+ const validateDimensions = (dimensions) => {
31
+ if (dimensions === null) {
32
+ return;
33
+ }
34
+ if (!Number.isFinite(dimensions.width) ||
35
+ !Number.isFinite(dimensions.height) ||
36
+ dimensions.width <= 0 ||
37
+ dimensions.height <= 0) {
38
+ throw new Error('Element dimensions must be positive finite numbers');
39
+ }
40
+ };
41
+ const validateElementForInstallation = (element) => {
42
+ if (studio_protocol_1.StudioProtocolInternals.makeElementFileNameFromSlug(element.slug) === null) {
43
+ throw new Error('Element slug must produce a safe lowercase .tsx file name');
44
+ }
45
+ if (typeof element.sourceCode !== 'string' ||
46
+ element.sourceCode.trim().length === 0 ||
47
+ element.sourceCode.length > 200000) {
48
+ throw new Error('Unsupported Element source code');
49
+ }
50
+ if (studio_protocol_1.StudioProtocolInternals.getElementComponentNameFromSourceCode(element.sourceCode) === null) {
51
+ throw new Error('Element source must export exactly one named component');
52
+ }
53
+ validateDimensions(element.dimensions);
54
+ };
55
+ exports.validateElementForInstallation = validateElementForInstallation;
56
+ const makeRelativeElementImportPath = ({ fromFile, toFile, }) => {
57
+ const withoutExtension = toFile.replace(/\.tsx$/, '');
58
+ let relative = node_path_1.default
59
+ .relative(node_path_1.default.dirname(fromFile), withoutExtension)
60
+ .split(node_path_1.default.sep)
61
+ .join('/');
62
+ if (!relative.startsWith('.')) {
63
+ relative = `./${relative}`;
64
+ }
65
+ return relative;
66
+ };
67
+ exports.makeRelativeElementImportPath = makeRelativeElementImportPath;
68
+ const getExpectedFileState = (existingSource) => {
69
+ if (existingSource === null) {
70
+ return { exists: false };
71
+ }
72
+ return {
73
+ exists: true,
74
+ sourceHash: (0, exports.getElementSourceHash)(existingSource),
75
+ };
76
+ };
77
+ const getElementInstallPlan = async ({ compositionFile, compositionId, element, remotionRoot, }) => {
78
+ (0, exports.validateElementForInstallation)(element);
79
+ const componentName = studio_protocol_1.StudioProtocolInternals.getElementComponentNameFromSourceCode(element.sourceCode);
80
+ if (componentName === null) {
81
+ throw new Error('Element source must export exactly one named component');
82
+ }
83
+ const location = await (0, resolve_composition_component_1.resolveCompositionComponentWithFile)({
84
+ remotionRoot,
85
+ compositionFile,
86
+ compositionId,
87
+ });
88
+ if (!location.canAddSequence) {
89
+ throw new Error('Cannot insert Element into this composition component');
90
+ }
91
+ const derivedElementFileName = studio_protocol_1.StudioProtocolInternals.makeElementFileNameFromSlug(element.slug);
92
+ if (derivedElementFileName === null) {
93
+ throw new Error('Element slug must produce a safe lowercase .tsx file name');
94
+ }
95
+ const safePaths = await (0, safe_element_install_path_1.getSafeElementInstallPaths)({
96
+ compositionFileName: location.fileName,
97
+ elementFileName: node_path_1.default.resolve(node_path_1.default.dirname(location.fileName), derivedElementFileName),
98
+ remotionRoot,
99
+ });
100
+ const elementFileExists = (0, node_fs_1.existsSync)(safePaths.elementFileName);
101
+ const existingElementSource = elementFileExists
102
+ ? (0, node_fs_1.readFileSync)(safePaths.elementFileName, 'utf-8')
103
+ : null;
104
+ return {
105
+ componentName,
106
+ elementFileExists,
107
+ elementFileName: safePaths.elementFileName,
108
+ existingElementSource,
109
+ expectedFileState: getExpectedFileState(existingElementSource),
110
+ filePath: node_path_1.default
111
+ .relative(safePaths.remotionRoot, safePaths.elementFileName)
112
+ .split(node_path_1.default.sep)
113
+ .join('/'),
114
+ importPath: (0, exports.makeRelativeElementImportPath)({
115
+ fromFile: safePaths.compositionFileName,
116
+ toFile: safePaths.elementFileName,
117
+ }),
118
+ location,
119
+ safePaths,
120
+ };
121
+ };
122
+ exports.getElementInstallPlan = getElementInstallPlan;
@@ -1,3 +1,3 @@
1
- import { type InsertElementRequest, type InsertElementResponse } from '@remotion/studio-shared';
1
+ import type { InsertElementRequest, InsertElementResponse } from '@remotion/studio-shared';
2
2
  import type { ApiHandler } from '../api-types';
3
3
  export declare const insertElementHandler: ApiHandler<InsertElementRequest, InsertElementResponse>;
@@ -1,138 +1,86 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.insertElementHandler = void 0;
7
- const node_fs_1 = require("node:fs");
8
- const node_path_1 = __importDefault(require("node:path"));
9
4
  const renderer_1 = require("@remotion/renderer");
10
- const studio_protocol_1 = require("@remotion/studio-protocol");
11
5
  const file_watcher_1 = require("../../file-watcher");
12
6
  const resolve_composition_component_1 = require("../../helpers/resolve-composition-component");
13
7
  const format_log_file_location_1 = require("../format-log-file-location");
14
8
  const undo_stack_1 = require("../undo-stack");
9
+ const element_install_plan_1 = require("./element-install-plan");
15
10
  const log_update_1 = require("./log-updates/log-update");
16
11
  const source_file_write_queue_1 = require("./source-file-write-queue");
17
- const validatePosition = (position) => {
18
- if (position === null) {
19
- return;
12
+ const hasExpectedFileState = ({ expected, actual, }) => {
13
+ if (expected.exists !== actual.exists) {
14
+ return false;
20
15
  }
21
- if (!Number.isFinite(position.x) || !Number.isFinite(position.y)) {
22
- throw new Error('Position must be finite');
16
+ if (!expected.exists) {
17
+ return true;
23
18
  }
24
- };
25
- const isInside = ({ child, parent }) => {
26
- const relative = node_path_1.default.relative(parent, child);
27
- return (relative === '' ||
28
- (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative)));
29
- };
30
- const withoutTsxExtension = (fileName) => {
31
- return fileName.replace(/\.tsx$/, '');
32
- };
33
- const normalizeSourceForComparison = (source) => {
34
- return source.replace(/\r\n/g, '\n').trim();
35
- };
36
- const makeRelativeImportPath = ({ fromFile, toFile, }) => {
37
- const withoutExtension = withoutTsxExtension(toFile);
38
- let relative = node_path_1.default
39
- .relative(node_path_1.default.dirname(fromFile), withoutExtension)
40
- .split(node_path_1.default.sep)
41
- .join('/');
42
- if (!relative.startsWith('.')) {
43
- relative = `./${relative}`;
19
+ if (!actual.exists) {
20
+ return false;
44
21
  }
45
- return relative;
22
+ return actual.sourceHash === expected.sourceHash;
46
23
  };
47
- const validateDimensions = (dimensions) => {
48
- if (dimensions === null) {
49
- return;
50
- }
51
- if (!Number.isFinite(dimensions.width) ||
52
- !Number.isFinite(dimensions.height) ||
53
- dimensions.width <= 0 ||
54
- dimensions.height <= 0) {
55
- throw new Error('Element dimensions must be positive finite numbers');
56
- }
57
- };
58
- const validateElement = (element) => {
59
- if (studio_protocol_1.StudioProtocolInternals.makeElementFileNameFromSlug(element.slug) === null) {
60
- throw new Error('Element slug must produce a safe lowercase .tsx file name');
61
- }
62
- if (typeof element.sourceCode !== 'string' ||
63
- element.sourceCode.trim().length === 0 ||
64
- element.sourceCode.length > 200000) {
65
- throw new Error('Unsupported Element source code');
66
- }
67
- if (studio_protocol_1.StudioProtocolInternals.getElementComponentNameFromSourceCode(element.sourceCode) === null) {
68
- throw new Error('Element source must export exactly one named component');
69
- }
70
- validateDimensions(element.dimensions);
71
- };
72
- const insertElementHandler = ({ input: { compositionFile, compositionId, element, from, position, overwriteExisting, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
24
+ const insertElementHandler = ({ input: { compositionFile, compositionId, element, expectedFileState, from, position, overwriteExisting, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
25
+ var _a;
73
26
  try {
74
- validateElement(element);
75
- validatePosition(position);
27
+ (0, element_install_plan_1.validateElementInstallPosition)(position);
76
28
  if (from !== null &&
77
29
  (!Number.isInteger(from) || !Number.isFinite(from) || from < 0)) {
78
30
  throw new Error('from must be a non-negative integer');
79
31
  }
80
- const componentName = studio_protocol_1.StudioProtocolInternals.getElementComponentNameFromSourceCode(element.sourceCode);
81
- if (componentName === null) {
82
- throw new Error('Element source must export exactly one named component');
83
- }
84
32
  renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[insert-element] Received request for compositionFile="${compositionFile}" compositionId="${compositionId}" element="${element.slug}"`);
85
- const location = await (0, resolve_composition_component_1.resolveCompositionComponentWithFile)({
86
- remotionRoot,
33
+ const plan = await (0, element_install_plan_1.getElementInstallPlan)({
87
34
  compositionFile,
88
35
  compositionId,
36
+ element,
37
+ remotionRoot,
89
38
  });
90
- if (!location.canAddSequence) {
91
- throw new Error('Cannot insert Element into this composition component');
92
- }
93
- const derivedElementFileName = studio_protocol_1.StudioProtocolInternals.makeElementFileNameFromSlug(element.slug);
94
- if (derivedElementFileName === null) {
95
- throw new Error('Element slug must produce a safe lowercase .tsx file name');
96
- }
97
- const elementFileName = node_path_1.default.resolve(node_path_1.default.dirname(location.fileName), derivedElementFileName);
98
- if (!isInside({ child: elementFileName, parent: remotionRoot })) {
99
- throw new Error('Element file must stay inside the Remotion project');
39
+ if (expectedFileState !== null &&
40
+ !hasExpectedFileState({
41
+ actual: plan.expectedFileState,
42
+ expected: expectedFileState,
43
+ })) {
44
+ const { existingElementSource } = plan;
45
+ if (existingElementSource !== null) {
46
+ return {
47
+ success: false,
48
+ type: 'file-conflict',
49
+ conflict: {
50
+ filePath: plan.filePath,
51
+ existingSource: existingElementSource,
52
+ incomingSource: element.sourceCode,
53
+ },
54
+ };
55
+ }
56
+ throw new Error('Element source changed during installation');
100
57
  }
101
- const elementFileExists = (0, node_fs_1.existsSync)(elementFileName);
102
- const existingElementSource = elementFileExists
103
- ? (0, node_fs_1.readFileSync)(elementFileName, 'utf-8')
104
- : null;
105
- const elementSourcesDiffer = existingElementSource !== null &&
106
- normalizeSourceForComparison(existingElementSource) !==
107
- normalizeSourceForComparison(element.sourceCode);
108
- if (elementSourcesDiffer && !overwriteExisting) {
58
+ const elementSourcesDiffer = plan.existingElementSource !== null &&
59
+ (0, element_install_plan_1.normalizeElementSourceForComparison)(plan.existingElementSource) !==
60
+ (0, element_install_plan_1.normalizeElementSourceForComparison)(element.sourceCode);
61
+ if (elementSourcesDiffer &&
62
+ !overwriteExisting &&
63
+ plan.existingElementSource !== null) {
109
64
  return {
110
65
  success: false,
111
66
  type: 'file-conflict',
112
67
  conflict: {
113
- filePath: node_path_1.default
114
- .relative(remotionRoot, elementFileName)
115
- .split(node_path_1.default.sep)
116
- .join('/'),
117
- existingSource: existingElementSource,
68
+ filePath: plan.filePath,
69
+ existingSource: plan.existingElementSource,
118
70
  incomingSource: element.sourceCode,
119
71
  },
120
72
  };
121
73
  }
122
- const shouldWriteElementFile = !elementFileExists || elementSourcesDiffer;
123
- const importPath = makeRelativeImportPath({
124
- fromFile: location.fileName,
125
- toFile: elementFileName,
126
- });
74
+ const shouldWriteElementFile = !plan.elementFileExists || elementSourcesDiffer;
127
75
  const inserted = await (0, resolve_composition_component_1.insertJsxElementIntoComposition)({
128
76
  remotionRoot,
129
77
  compositionFile,
130
78
  compositionId,
131
79
  element: {
132
80
  type: 'component',
133
- componentName,
134
- importName: componentName,
135
- importPath,
81
+ componentName: plan.componentName,
82
+ importName: plan.componentName,
83
+ importPath: plan.importPath,
136
84
  props: [],
137
85
  position: null,
138
86
  },
@@ -140,18 +88,35 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
140
88
  prettierConfigOverride: null,
141
89
  wrapInSequence: {
142
90
  dimensions: element.dimensions,
91
+ durationInFrames: (_a = element.durationInFrames) !== null && _a !== void 0 ? _a : null,
143
92
  from,
144
93
  name: element.displayName,
145
94
  position,
146
95
  },
147
96
  });
97
+ const finalPlan = await (0, element_install_plan_1.getElementInstallPlan)({
98
+ compositionFile,
99
+ compositionId,
100
+ element,
101
+ remotionRoot,
102
+ });
103
+ if (finalPlan.safePaths.compositionFileName !==
104
+ plan.safePaths.compositionFileName) {
105
+ throw new Error('Composition source changed during Element installation');
106
+ }
107
+ if (!hasExpectedFileState({
108
+ actual: finalPlan.expectedFileState,
109
+ expected: plan.expectedFileState,
110
+ })) {
111
+ throw new Error('Element source changed during installation');
112
+ }
148
113
  (0, undo_stack_1.pushTransactionToUndoStack)({
149
114
  snapshots: [
150
115
  ...(shouldWriteElementFile
151
116
  ? [
152
117
  {
153
- filePath: elementFileName,
154
- oldContents: existingElementSource,
118
+ filePath: plan.elementFileName,
119
+ oldContents: plan.existingElementSource,
155
120
  newContents: element.sourceCode,
156
121
  logLine: 1,
157
122
  },
@@ -174,11 +139,11 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
174
139
  suppressHmrOnFileRestore: false,
175
140
  });
176
141
  if (shouldWriteElementFile) {
177
- (0, undo_stack_1.suppressUndoStackInvalidation)(elementFileName);
142
+ (0, undo_stack_1.suppressUndoStackInvalidation)(plan.elementFileName);
178
143
  }
179
144
  (0, undo_stack_1.suppressUndoStackInvalidation)(inserted.fileName);
180
145
  if (shouldWriteElementFile) {
181
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)(elementFileName, element.sourceCode, undefined);
146
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(plan.elementFileName, element.sourceCode, undefined);
182
147
  }
183
148
  (0, file_watcher_1.writeFileAndNotifyFileWatchers)(inserted.fileName, inserted.output, undefined);
184
149
  const compositionLocationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
@@ -188,23 +153,21 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
188
153
  });
189
154
  const elementLocationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
190
155
  remotionRoot,
191
- absolutePath: elementFileName,
156
+ absolutePath: plan.elementFileName,
192
157
  line: 1,
193
158
  });
194
159
  const elementFileAction = elementSourcesDiffer
195
160
  ? 'Overwrote existing Element source'
196
- : elementFileExists
161
+ : plan.elementFileExists
197
162
  ? 'Reused existing Element source'
198
163
  : 'Created Element source';
199
164
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(elementLocationLabel)} ${elementFileAction}`);
200
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(compositionLocationLabel)} Added <${componentName}>`);
165
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(compositionLocationLabel)} Added <${plan.componentName}>`);
201
166
  if (!inserted.formatted) {
202
167
  (0, log_update_1.warnAboutPrettierOnce)(logLevel);
203
168
  }
204
169
  (0, undo_stack_1.printUndoHint)(logLevel);
205
- return {
206
- success: true,
207
- };
170
+ return { success: true };
208
171
  }
209
172
  catch (err) {
210
173
  return {
@@ -1,4 +1,5 @@
1
+ import type { ElementDependency } from '@remotion/studio-protocol';
1
2
  import { type InstallPackageRequest, type InstallPackageResponse } from '@remotion/studio-shared';
2
3
  import type { ApiHandler } from '../api-types';
3
- export declare const getPackageInstallSpec: (packageName: string) => string;
4
+ export declare const getPackageInstallSpec: (dependency: string | ElementDependency) => string;
4
5
  export declare const handleInstallPackage: ApiHandler<InstallPackageRequest, InstallPackageResponse>;
@@ -12,21 +12,26 @@ const getExtraPackageVersion = (packageName) => {
12
12
  const pkg = studio_shared_1.extraPackages.find((p) => p.name === packageName);
13
13
  return pkg ? pkg.version : null;
14
14
  };
15
- const getPackageInstallSpec = (packageName) => {
16
- const extraVersion = getExtraPackageVersion(packageName);
17
- if (extraVersion) {
18
- return `${packageName}@${extraVersion}`;
19
- }
20
- if (packageName === 'remotion' || packageName.startsWith('@remotion/')) {
21
- return `${packageName}@${version_1.VERSION}`;
22
- }
23
- return packageName;
15
+ const getPackageInstallSpec = (dependency) => {
16
+ const { name, version } = typeof dependency === 'string'
17
+ ? { name: dependency, version: null }
18
+ : dependency;
19
+ const extraVersion = getExtraPackageVersion(name);
20
+ if (extraVersion)
21
+ return `${name}@${extraVersion}`;
22
+ if (name === 'remotion' || name.startsWith('@remotion/'))
23
+ return `${name}@${version_1.VERSION}`;
24
+ return version === null ? name : `${name}@${version}`;
24
25
  };
25
26
  exports.getPackageInstallSpec = getPackageInstallSpec;
26
- const handleInstallPackage = async ({ logLevel, remotionRoot, input: { packageNames } }) => {
27
- for (const packageName of packageNames) {
28
- if (!(0, studio_shared_1.isValidPackageName)(packageName)) {
29
- return Promise.reject(new Error(`Package name ${JSON.stringify(packageName)} is invalid.`));
27
+ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { dependencies } }) => {
28
+ for (const dependency of dependencies) {
29
+ if (!(0, studio_shared_1.isValidPackageName)(dependency.name)) {
30
+ return Promise.reject(new Error(`Package name ${JSON.stringify(dependency.name)} is invalid.`));
31
+ }
32
+ if (dependency.version !== null &&
33
+ !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(dependency.version)) {
34
+ return Promise.reject(new Error(`Package version ${JSON.stringify(dependency.version)} is invalid.`));
30
35
  }
31
36
  }
32
37
  const manager = (0, get_package_manager_1.getPackageManager)({
@@ -36,13 +41,9 @@ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { packageNa
36
41
  logLevel,
37
42
  });
38
43
  if (manager === 'unknown') {
39
- throw new Error(`No lockfile was found in your project (one of ${get_package_manager_1.lockFilePaths
40
- .map((p) => p.path)
41
- .join(', ')}). Install dependencies using your favorite manager!`);
44
+ throw new Error(`No lockfile was found in your project (one of ${get_package_manager_1.lockFilePaths.map((p) => p.path).join(', ')}). Install dependencies using your favorite manager!`);
42
45
  }
43
- // Remotion packages must match the Studio version and catalogued extra
44
- // packages use their supported version. Other packages resolve normally.
45
- const packagesWithVersions = packageNames.map(exports.getPackageInstallSpec);
46
+ const packagesWithVersions = dependencies.map(exports.getPackageInstallSpec);
46
47
  const command = (0, install_command_1.getInstallCommand)({
47
48
  manager: manager.manager,
48
49
  packages: packagesWithVersions,
@@ -54,34 +55,22 @@ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { packageNa
54
55
  try {
55
56
  await new Promise((resolve, reject) => {
56
57
  const cmd = (0, node_child_process_1.spawn)(manager.manager, command, (0, package_manager_spawn_options_1.getPackageManagerSpawnOptions)());
57
- cmd.on('error', (err) => {
58
- reject(err);
59
- });
60
- cmd.stdout.on('data', (d) => {
61
- const splitted = d.toString().trim().split('\n');
62
- splitted.forEach((line) => {
63
- renderer_1.RenderInternals.Log.info({ indent: true, logLevel }, line);
64
- });
65
- });
66
- cmd.stdout.on('end', () => {
67
- resolve();
68
- });
69
- cmd.on('close', (code, signal) => {
70
- if (code === 0) {
71
- resolve();
72
- }
73
- else {
74
- reject(new Error(`Command exited with code ${code} and signal ${signal}`));
75
- }
76
- });
58
+ cmd.on('error', reject);
59
+ cmd.stdout.on('data', (d) => d
60
+ .toString()
61
+ .trim()
62
+ .split('\n')
63
+ .forEach((line) => renderer_1.RenderInternals.Log.info({ indent: true, logLevel }, line)));
64
+ cmd.stdout.on('end', resolve);
65
+ cmd.on('close', (code, signal) => code === 0
66
+ ? resolve()
67
+ : reject(new Error(`Command exited with code ${code} and signal ${signal}`)));
77
68
  });
78
- const timeEnd = Date.now();
79
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), `Done in ${timeEnd - time}ms`);
80
- return Promise.resolve({});
69
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), `Done in ${Date.now() - time}ms`);
70
+ return {};
81
71
  }
82
72
  catch (err) {
83
- const timeEnd = Date.now();
84
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), renderer_1.RenderInternals.chalk.red(`Errored in ${timeEnd - time}ms`));
73
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), renderer_1.RenderInternals.chalk.red(`Errored in ${Date.now() - time}ms`));
85
74
  return Promise.reject(err);
86
75
  }
87
76
  };
@@ -1,4 +1,8 @@
1
+ import { type DefaultEditor } from '@remotion/renderer';
1
2
  import type { OpenInEditorRequest, OpenInEditorResponse } from '@remotion/studio-shared';
2
3
  import type { ApiHandler } from '../api-types';
3
- export declare const getEditorName: () => Promise<string | null>;
4
+ export declare const getEditorName: ({ getDefaultEditor, logLevel, }: {
5
+ getDefaultEditor: () => DefaultEditor | null;
6
+ logLevel: "error" | "info" | "trace" | "verbose" | "warn";
7
+ }) => Promise<string | null>;
4
8
  export declare const openInEditorHandler: ApiHandler<OpenInEditorRequest, OpenInEditorResponse>;