@remotion/studio-server 4.0.501 → 4.0.503

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 (70) hide show
  1. package/dist/codemods/update-inline-caption-patches.d.ts +1 -11
  2. package/dist/codemods/update-inline-caption-patches.js +2 -183
  3. package/dist/codemods/update-sequence-props/update-sequence-props.d.ts +2 -35
  4. package/dist/codemods/update-sequence-props/update-sequence-props.js +7 -720
  5. package/dist/detect-outdated-remotion-skills.d.ts +25 -0
  6. package/dist/detect-outdated-remotion-skills.js +73 -0
  7. package/dist/helpers/custom-editor.d.ts +37 -0
  8. package/dist/helpers/custom-editor.js +167 -0
  9. package/dist/helpers/editor-registry.d.ts +18 -0
  10. package/dist/helpers/editor-registry.js +500 -0
  11. package/dist/helpers/open-in-editor.d.ts +3 -4
  12. package/dist/helpers/open-in-editor.js +5 -75
  13. package/dist/helpers/resolve-composition-component.js +18 -6
  14. package/dist/helpers/resolve-editor.d.ts +27 -0
  15. package/dist/helpers/resolve-editor.js +90 -0
  16. package/dist/helpers/resolve-file-inside-project.d.ts +2 -0
  17. package/dist/helpers/resolve-file-inside-project.js +5 -2
  18. package/dist/index.d.ts +17 -1
  19. package/dist/index.js +9 -1
  20. package/dist/preview-server/api-routes.js +6 -1
  21. package/dist/preview-server/api-types.d.ts +2 -1
  22. package/dist/preview-server/element-install-state.d.ts +19 -0
  23. package/dist/preview-server/element-install-state.js +46 -1
  24. package/dist/preview-server/handler.d.ts +3 -1
  25. package/dist/preview-server/handler.js +3 -2
  26. package/dist/preview-server/parse-body.d.ts +6 -1
  27. package/dist/preview-server/parse-body.js +60 -9
  28. package/dist/preview-server/routes/can-update-default-props.d.ts +1 -1
  29. package/dist/preview-server/routes/can-update-default-props.js +5 -132
  30. package/dist/preview-server/routes/can-update-sequence-props.d.ts +3 -2
  31. package/dist/preview-server/routes/can-update-sequence-props.js +52 -9
  32. package/dist/preview-server/routes/default-editor.d.ts +8 -0
  33. package/dist/preview-server/routes/default-editor.js +131 -0
  34. package/dist/preview-server/routes/delete-effect-keyframe.d.ts +3 -0
  35. package/dist/preview-server/routes/delete-effect-keyframe.js +89 -0
  36. package/dist/preview-server/routes/delete-sequence-keyframe.d.ts +3 -0
  37. package/dist/preview-server/routes/delete-sequence-keyframe.js +82 -0
  38. package/dist/preview-server/routes/element-install-plan.d.ts +32 -0
  39. package/dist/preview-server/routes/element-install-plan.js +122 -0
  40. package/dist/preview-server/routes/insert-element.d.ts +1 -1
  41. package/dist/preview-server/routes/insert-element.js +68 -107
  42. package/dist/preview-server/routes/insert-jsx-element.js +5 -5
  43. package/dist/preview-server/routes/open-in-editor.d.ts +5 -1
  44. package/dist/preview-server/routes/open-in-editor.js +42 -12
  45. package/dist/preview-server/routes/prepare-element-install.d.ts +3 -0
  46. package/dist/preview-server/routes/prepare-element-install.js +25 -0
  47. package/dist/preview-server/routes/safe-element-install-path.d.ts +9 -0
  48. package/dist/preview-server/routes/safe-element-install-path.js +58 -0
  49. package/dist/preview-server/routes/save-props-mutex.d.ts +1 -0
  50. package/dist/preview-server/routes/save-props-mutex.js +11 -0
  51. package/dist/preview-server/sequence-props-watchers.js +2 -1
  52. package/dist/preview-server/start-server.d.ts +2 -0
  53. package/dist/preview-server/start-server.js +1 -0
  54. package/dist/preview-server/studio-protocol/handle-discovery.d.ts +11 -0
  55. package/dist/preview-server/studio-protocol/handle-discovery.js +106 -0
  56. package/dist/preview-server/studio-protocol/handle-install.d.ts +10 -0
  57. package/dist/preview-server/studio-protocol/handle-install.js +147 -0
  58. package/dist/preview-server/studio-protocol/origin-policy.d.ts +11 -0
  59. package/dist/preview-server/studio-protocol/origin-policy.js +43 -0
  60. package/dist/preview-server/studio-protocol/protocol-response.d.ts +7 -0
  61. package/dist/preview-server/studio-protocol/protocol-response.js +13 -0
  62. package/dist/preview-server/update-available.d.ts +6 -0
  63. package/dist/preview-server/update-available.js +30 -4
  64. package/dist/remotion-skill-names.d.ts +2 -0
  65. package/dist/remotion-skill-names.js +16 -0
  66. package/dist/routes.d.ts +3 -1
  67. package/dist/routes.js +19 -157
  68. package/dist/start-studio.d.ts +3 -1
  69. package/dist/start-studio.js +2 -1
  70. package/package.json +9 -8
@@ -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,85 @@
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
- const drag_and_drop_1 = require("@remotion/drag-and-drop");
10
4
  const renderer_1 = require("@remotion/renderer");
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 (drag_and_drop_1.DragAndDropInternals.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 (drag_and_drop_1.DragAndDropInternals.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 () => {
73
25
  try {
74
- validateElement(element);
75
- validatePosition(position);
26
+ (0, element_install_plan_1.validateElementInstallPosition)(position);
76
27
  if (from !== null &&
77
28
  (!Number.isInteger(from) || !Number.isFinite(from) || from < 0)) {
78
29
  throw new Error('from must be a non-negative integer');
79
30
  }
80
- const componentName = drag_and_drop_1.DragAndDropInternals.getElementComponentNameFromSourceCode(element.sourceCode);
81
- if (componentName === null) {
82
- throw new Error('Element source must export exactly one named component');
83
- }
84
31
  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,
32
+ const plan = await (0, element_install_plan_1.getElementInstallPlan)({
87
33
  compositionFile,
88
34
  compositionId,
35
+ element,
36
+ remotionRoot,
89
37
  });
90
- if (!location.canAddSequence) {
91
- throw new Error('Cannot insert Element into this composition component');
92
- }
93
- const derivedElementFileName = drag_and_drop_1.DragAndDropInternals.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');
38
+ if (expectedFileState !== null &&
39
+ !hasExpectedFileState({
40
+ actual: plan.expectedFileState,
41
+ expected: expectedFileState,
42
+ })) {
43
+ const { existingElementSource } = plan;
44
+ if (existingElementSource !== null) {
45
+ return {
46
+ success: false,
47
+ type: 'file-conflict',
48
+ conflict: {
49
+ filePath: plan.filePath,
50
+ existingSource: existingElementSource,
51
+ incomingSource: element.sourceCode,
52
+ },
53
+ };
54
+ }
55
+ throw new Error('Element source changed during installation');
100
56
  }
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) {
57
+ const elementSourcesDiffer = plan.existingElementSource !== null &&
58
+ (0, element_install_plan_1.normalizeElementSourceForComparison)(plan.existingElementSource) !==
59
+ (0, element_install_plan_1.normalizeElementSourceForComparison)(element.sourceCode);
60
+ if (elementSourcesDiffer &&
61
+ !overwriteExisting &&
62
+ plan.existingElementSource !== null) {
109
63
  return {
110
64
  success: false,
111
65
  type: 'file-conflict',
112
66
  conflict: {
113
- filePath: node_path_1.default
114
- .relative(remotionRoot, elementFileName)
115
- .split(node_path_1.default.sep)
116
- .join('/'),
117
- existingSource: existingElementSource,
67
+ filePath: plan.filePath,
68
+ existingSource: plan.existingElementSource,
118
69
  incomingSource: element.sourceCode,
119
70
  },
120
71
  };
121
72
  }
122
- const shouldWriteElementFile = !elementFileExists || elementSourcesDiffer;
123
- const importPath = makeRelativeImportPath({
124
- fromFile: location.fileName,
125
- toFile: elementFileName,
126
- });
73
+ const shouldWriteElementFile = !plan.elementFileExists || elementSourcesDiffer;
127
74
  const inserted = await (0, resolve_composition_component_1.insertJsxElementIntoComposition)({
128
75
  remotionRoot,
129
76
  compositionFile,
130
77
  compositionId,
131
78
  element: {
132
79
  type: 'component',
133
- componentName,
134
- importName: componentName,
135
- importPath,
80
+ componentName: plan.componentName,
81
+ importName: plan.componentName,
82
+ importPath: plan.importPath,
136
83
  props: [],
137
84
  position: null,
138
85
  },
@@ -145,13 +92,29 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
145
92
  position,
146
93
  },
147
94
  });
95
+ const finalPlan = await (0, element_install_plan_1.getElementInstallPlan)({
96
+ compositionFile,
97
+ compositionId,
98
+ element,
99
+ remotionRoot,
100
+ });
101
+ if (finalPlan.safePaths.compositionFileName !==
102
+ plan.safePaths.compositionFileName) {
103
+ throw new Error('Composition source changed during Element installation');
104
+ }
105
+ if (!hasExpectedFileState({
106
+ actual: finalPlan.expectedFileState,
107
+ expected: plan.expectedFileState,
108
+ })) {
109
+ throw new Error('Element source changed during installation');
110
+ }
148
111
  (0, undo_stack_1.pushTransactionToUndoStack)({
149
112
  snapshots: [
150
113
  ...(shouldWriteElementFile
151
114
  ? [
152
115
  {
153
- filePath: elementFileName,
154
- oldContents: existingElementSource,
116
+ filePath: plan.elementFileName,
117
+ oldContents: plan.existingElementSource,
155
118
  newContents: element.sourceCode,
156
119
  logLine: 1,
157
120
  },
@@ -174,11 +137,11 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
174
137
  suppressHmrOnFileRestore: false,
175
138
  });
176
139
  if (shouldWriteElementFile) {
177
- (0, undo_stack_1.suppressUndoStackInvalidation)(elementFileName);
140
+ (0, undo_stack_1.suppressUndoStackInvalidation)(plan.elementFileName);
178
141
  }
179
142
  (0, undo_stack_1.suppressUndoStackInvalidation)(inserted.fileName);
180
143
  if (shouldWriteElementFile) {
181
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)(elementFileName, element.sourceCode, undefined);
144
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(plan.elementFileName, element.sourceCode, undefined);
182
145
  }
183
146
  (0, file_watcher_1.writeFileAndNotifyFileWatchers)(inserted.fileName, inserted.output, undefined);
184
147
  const compositionLocationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
@@ -188,23 +151,21 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
188
151
  });
189
152
  const elementLocationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
190
153
  remotionRoot,
191
- absolutePath: elementFileName,
154
+ absolutePath: plan.elementFileName,
192
155
  line: 1,
193
156
  });
194
157
  const elementFileAction = elementSourcesDiffer
195
158
  ? 'Overwrote existing Element source'
196
- : elementFileExists
159
+ : plan.elementFileExists
197
160
  ? 'Reused existing Element source'
198
161
  : 'Created Element source';
199
162
  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}>`);
163
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(compositionLocationLabel)} Added <${plan.componentName}>`);
201
164
  if (!inserted.formatted) {
202
165
  (0, log_update_1.warnAboutPrettierOnce)(logLevel);
203
166
  }
204
167
  (0, undo_stack_1.printUndoHint)(logLevel);
205
- return {
206
- success: true,
207
- };
168
+ return { success: true };
208
169
  }
209
170
  catch (err) {
210
171
  return {
@@ -5,8 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.insertJsxElementHandler = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
- const drag_and_drop_1 = require("@remotion/drag-and-drop");
9
8
  const renderer_1 = require("@remotion/renderer");
9
+ const studio_protocol_1 = require("@remotion/studio-protocol");
10
10
  const studio_shared_1 = require("@remotion/studio-shared");
11
11
  const file_watcher_1 = require("../../file-watcher");
12
12
  const resolve_composition_component_1 = require("../../helpers/resolve-composition-component");
@@ -79,16 +79,16 @@ const validateElement = (element, remotionRoot) => {
79
79
  return;
80
80
  }
81
81
  if (element.type === 'component') {
82
- if (!drag_and_drop_1.DragAndDropInternals.isComponentIdentifier(element.componentName)) {
82
+ if (!studio_protocol_1.StudioProtocolInternals.isComponentIdentifier(element.componentName)) {
83
83
  throw new Error('Unsupported component name');
84
84
  }
85
- if (!drag_and_drop_1.DragAndDropInternals.isComponentIdentifier(element.importName)) {
85
+ if (!studio_protocol_1.StudioProtocolInternals.isComponentIdentifier(element.importName)) {
86
86
  throw new Error('Unsupported component import name');
87
87
  }
88
- if (!drag_and_drop_1.DragAndDropInternals.isComponentImportPath(element.importPath)) {
88
+ if (!studio_protocol_1.StudioProtocolInternals.isComponentImportPath(element.importPath)) {
89
89
  throw new Error('Unsupported component import path');
90
90
  }
91
- if (!drag_and_drop_1.DragAndDropInternals.areComponentProps(element.props)) {
91
+ if (!studio_protocol_1.StudioProtocolInternals.areComponentProps(element.props)) {
92
92
  throw new Error('Unsupported component props');
93
93
  }
94
94
  return;
@@ -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>;
@@ -5,28 +5,58 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.openInEditorHandler = exports.getEditorName = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
+ const renderer_1 = require("@remotion/renderer");
9
+ const custom_editor_1 = require("../../helpers/custom-editor");
8
10
  const open_in_editor_1 = require("../../helpers/open-in-editor");
9
- const editorGuess = (0, open_in_editor_1.guessEditor)();
10
- const getEditorName = async () => {
11
- const [edit] = await editorGuess;
12
- return (0, open_in_editor_1.getDisplayNameForEditor)(edit ? edit.command : null);
11
+ const resolve_editor_1 = require("../../helpers/resolve-editor");
12
+ const getEditorName = async ({ getDefaultEditor, logLevel, }) => {
13
+ var _a;
14
+ const editor = await (0, resolve_editor_1.resolveEditor)({
15
+ defaultEditor: getDefaultEditor(),
16
+ logLevel,
17
+ preferredEditor: null,
18
+ });
19
+ return (_a = editor === null || editor === void 0 ? void 0 : editor.name) !== null && _a !== void 0 ? _a : null;
13
20
  };
14
21
  exports.getEditorName = getEditorName;
15
- const openInEditorHandler = async ({ input, remotionRoot, logLevel }) => {
22
+ const openInEditorHandler = async ({ input, remotionRoot, logLevel, getDefaultEditor }) => {
16
23
  try {
17
24
  if (!('stack' in input)) {
18
25
  throw new TypeError('Need to pass stack');
19
26
  }
20
27
  const { stack } = input;
21
- const guess = await editorGuess;
22
- const didOpen = await (0, open_in_editor_1.launchEditor)({
23
- colNumber: stack.originalColumnNumber,
24
- editor: guess[0],
25
- fileName: node_path_1.default.resolve(remotionRoot, stack.originalFileName),
26
- lineNumber: stack.originalLineNumber,
27
- vsCodeNewWindow: false,
28
+ if (input.editorId !== null &&
29
+ input.editorId !== 'custom' &&
30
+ !renderer_1.defaultEditorIds.includes(input.editorId)) {
31
+ return { success: false };
32
+ }
33
+ const editor = await (0, resolve_editor_1.resolveEditor)({
34
+ defaultEditor: getDefaultEditor(),
28
35
  logLevel,
36
+ preferredEditor: input.editorId,
29
37
  });
38
+ if (!editor) {
39
+ return { success: false };
40
+ }
41
+ const fileName = node_path_1.default.resolve(remotionRoot, stack.originalFileName);
42
+ const didOpen = editor.type === 'custom'
43
+ ? await (0, custom_editor_1.launchCustomEditor)({
44
+ editor: editor.editor,
45
+ resolvedExecutable: editor,
46
+ targetPath: fileName,
47
+ lineNumber: stack.originalLineNumber,
48
+ columnNumber: stack.originalColumnNumber,
49
+ logLevel,
50
+ spawnProcess: null,
51
+ })
52
+ : await (0, open_in_editor_1.launchEditor)({
53
+ colNumber: stack.originalColumnNumber,
54
+ editor,
55
+ fileName,
56
+ lineNumber: stack.originalLineNumber,
57
+ vsCodeNewWindow: false,
58
+ logLevel,
59
+ });
30
60
  return {
31
61
  success: didOpen,
32
62
  };
@@ -0,0 +1,3 @@
1
+ import type { PrepareElementInstallRequest, PrepareElementInstallResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const prepareElementInstallHandler: ApiHandler<PrepareElementInstallRequest, PrepareElementInstallResponse>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareElementInstallHandler = void 0;
4
+ const element_install_plan_1 = require("./element-install-plan");
5
+ const source_file_write_queue_1 = require("./source-file-write-queue");
6
+ const prepareElementInstallHandler = ({ input, remotionRoot }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
7
+ try {
8
+ const plan = await (0, element_install_plan_1.getElementInstallPlan)({ ...input, remotionRoot });
9
+ return {
10
+ success: true,
11
+ plan: {
12
+ expectedFileState: plan.expectedFileState,
13
+ filePath: plan.filePath,
14
+ },
15
+ };
16
+ }
17
+ catch (error) {
18
+ return {
19
+ success: false,
20
+ reason: error.message,
21
+ stack: error.stack,
22
+ };
23
+ }
24
+ });
25
+ exports.prepareElementInstallHandler = prepareElementInstallHandler;
@@ -0,0 +1,9 @@
1
+ export declare const getSafeElementInstallPaths: ({ compositionFileName, elementFileName, remotionRoot, }: {
2
+ compositionFileName: string;
3
+ elementFileName: string;
4
+ remotionRoot: string;
5
+ }) => Promise<{
6
+ compositionFileName: string;
7
+ elementFileName: string;
8
+ remotionRoot: string;
9
+ }>;
@@ -0,0 +1,58 @@
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.getSafeElementInstallPaths = void 0;
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const isInside = ({ child, parent }) => {
10
+ const relative = node_path_1.default.relative(parent, child);
11
+ return (relative === '' ||
12
+ (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative)));
13
+ };
14
+ const assertInsideProject = ({ fileName, remotionRoot, }) => {
15
+ if (!isInside({ child: fileName, parent: remotionRoot })) {
16
+ throw new Error('Element installation must stay inside the Remotion project');
17
+ }
18
+ };
19
+ const getCanonicalProjectRoot = async (remotionRoot) => {
20
+ return (0, promises_1.realpath)(remotionRoot);
21
+ };
22
+ const getSafeElementInstallPaths = async ({ compositionFileName, elementFileName, remotionRoot, }) => {
23
+ const canonicalRoot = await getCanonicalProjectRoot(remotionRoot);
24
+ const canonicalCompositionFile = await (0, promises_1.realpath)(compositionFileName);
25
+ assertInsideProject({
26
+ fileName: canonicalCompositionFile,
27
+ remotionRoot: canonicalRoot,
28
+ });
29
+ // The Element directory already exists because it is the composition file's
30
+ // directory. Resolving it before appending the file name prevents writes
31
+ // through a directory symlink that leaves the project.
32
+ const canonicalElementDirectory = await (0, promises_1.realpath)(node_path_1.default.dirname(elementFileName));
33
+ assertInsideProject({
34
+ fileName: canonicalElementDirectory,
35
+ remotionRoot: canonicalRoot,
36
+ });
37
+ const canonicalElementFile = node_path_1.default.join(canonicalElementDirectory, node_path_1.default.basename(elementFileName));
38
+ try {
39
+ const elementStats = await (0, promises_1.lstat)(canonicalElementFile);
40
+ if (elementStats.isSymbolicLink()) {
41
+ throw new Error('Element source file must not be a symbolic link');
42
+ }
43
+ if (!elementStats.isFile()) {
44
+ throw new Error('Element source path must be a file');
45
+ }
46
+ }
47
+ catch (error) {
48
+ if (error.code !== 'ENOENT') {
49
+ throw error;
50
+ }
51
+ }
52
+ return {
53
+ compositionFileName: canonicalCompositionFile,
54
+ elementFileName: canonicalElementFile,
55
+ remotionRoot: canonicalRoot,
56
+ };
57
+ };
58
+ exports.getSafeElementInstallPaths = getSafeElementInstallPaths;
@@ -0,0 +1 @@
1
+ export declare const withSavePropsLock: <T>(fn: () => Promise<T>) => Promise<T>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withSavePropsLock = void 0;
4
+ let chain = Promise.resolve();
5
+ const withSavePropsLock = (fn) => {
6
+ const run = () => fn();
7
+ const next = chain.then(run, run);
8
+ chain = next.then(() => undefined, () => undefined);
9
+ return next;
10
+ };
11
+ exports.withSavePropsLock = withSavePropsLock;
@@ -85,9 +85,10 @@ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, com
85
85
  };
86
86
  }
87
87
  }
88
- const status = (0, can_update_sequence_props_1.computeSequencePropsStatusFromFilenameByLine)({
88
+ const status = (0, can_update_sequence_props_1.computeSequencePropsStatusFromFilenameByLocation)({
89
89
  fileName,
90
90
  line,
91
+ column,
91
92
  componentIdentity,
92
93
  keys,
93
94
  assetKeys,
@@ -1,4 +1,5 @@
1
1
  import type { BundlerOverrideFn, RspackOverrideFn, WebpackOverrideFn } from '@remotion/bundler';
2
+ import type { DefaultEditor } from '@remotion/renderer';
2
3
  import type { GitSource, RenderDefaults, RenderJob, StudioRuntimeConfig } from '@remotion/studio-shared';
3
4
  import type { QueueMethods } from './api-types';
4
5
  import type { LiveEventsServer } from './live-events';
@@ -41,5 +42,6 @@ export declare const startServer: (options: {
41
42
  forceNew: boolean;
42
43
  rspack: boolean;
43
44
  getStudioRuntimeConfig: () => StudioRuntimeConfig;
45
+ getDefaultEditor: () => DefaultEditor | null;
44
46
  configFile: string | null;
45
47
  }) => Promise<StartServerResult>;
@@ -102,6 +102,7 @@ const startServer = async (options) => {
102
102
  getPreviewSampleRate: options.getPreviewSampleRate,
103
103
  enableCrossSiteIsolation: options.enableCrossSiteIsolation,
104
104
  getStudioRuntimeConfig: options.getStudioRuntimeConfig,
105
+ getDefaultEditor: options.getDefaultEditor,
105
106
  configFile: options.configFile,
106
107
  });
107
108
  })