@remotion/studio-server 4.0.518 → 4.0.519

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 (27) hide show
  1. package/dist/codemods/delete-jsx-node.d.ts +1 -4
  2. package/dist/codemods/duplicate-jsx-node.d.ts +11 -0
  3. package/dist/codemods/duplicate-jsx-node.js +11 -1
  4. package/dist/codemods/get-node-path-remappings.d.ts +1 -0
  5. package/dist/codemods/get-node-path-remappings.js +15 -2
  6. package/dist/codemods/split-video-from-audio.d.ts +1 -4
  7. package/dist/helpers/open-in-editor.js +1 -1
  8. package/dist/preview-server/api-routes.js +2 -0
  9. package/dist/preview-server/element-install-state.d.ts +0 -1
  10. package/dist/preview-server/element-install-state.js +1 -2
  11. package/dist/preview-server/routes/delete-jsx-node.js +0 -1
  12. package/dist/preview-server/routes/duplicate-jsx-node.js +71 -39
  13. package/dist/preview-server/routes/element-install-plan.d.ts +4 -2
  14. package/dist/preview-server/routes/element-install-plan.js +22 -9
  15. package/dist/preview-server/routes/insert-element.js +13 -6
  16. package/dist/preview-server/routes/insert-jsx-element.js +0 -1
  17. package/dist/preview-server/routes/prepare-element-install.js +7 -2
  18. package/dist/preview-server/routes/release-notes.d.ts +3 -0
  19. package/dist/preview-server/routes/release-notes.js +127 -0
  20. package/dist/preview-server/routes/reorder-sequence.js +0 -1
  21. package/dist/preview-server/routes/split-jsx-sequence.js +0 -1
  22. package/dist/preview-server/routes/split-video-from-audio.js +0 -1
  23. package/dist/preview-server/sequence-node-path-mutation.d.ts +0 -2
  24. package/dist/preview-server/start-server.js +9 -5
  25. package/dist/preview-server/studio-protocol/handle-discovery.js +2 -3
  26. package/dist/preview-server/undo-stack.js +4 -13
  27. package/package.json +8 -8
@@ -9,10 +9,7 @@ export declare const deleteJsxNodes: ({ input, nodePaths, }: {
9
9
  formatted: boolean;
10
10
  nodeLabels: string[];
11
11
  logLines: number[];
12
- nodePathRemappings: {
13
- oldNodePath: SequenceNodePath;
14
- newNodePath: SequenceNodePath | null;
15
- }[];
12
+ nodePathRemappings: import("@remotion/studio-shared").SequenceNodePathRemapping[];
16
13
  }>;
17
14
  export declare const deleteJsxNode: ({ input, nodePath, }: {
18
15
  input: string;
@@ -12,3 +12,14 @@ export declare const duplicateJsxNode: ({ input, nodePath, prettierConfigOverrid
12
12
  logLine: number;
13
13
  nodePathRemappings: import("@remotion/studio-shared").SequenceNodePathRemapping[];
14
14
  }>;
15
+ export declare const duplicateJsxNodes: ({ input, nodePaths, prettierConfigOverride, }: {
16
+ input: string;
17
+ nodePaths: SequenceNodePath[];
18
+ prettierConfigOverride?: Record<string, unknown> | null | undefined;
19
+ }) => Promise<{
20
+ output: string;
21
+ formatted: boolean;
22
+ nodeLabels: string[];
23
+ logLines: number[];
24
+ nodePathRemappings: import("@remotion/studio-shared").SequenceNodePathRemapping[];
25
+ }>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.duplicateJsxNode = exports.duplicateJsxElementAtPath = void 0;
3
+ exports.duplicateJsxNodes = exports.duplicateJsxNode = exports.duplicateJsxElementAtPath = void 0;
4
4
  const studio_codemods_1 = require("@remotion/studio-codemods");
5
5
  Object.defineProperty(exports, "duplicateJsxElementAtPath", { enumerable: true, get: function () { return studio_codemods_1.duplicateJsxElementAtPath; } });
6
6
  const format_file_content_1 = require("./format-file-content");
@@ -14,3 +14,13 @@ const duplicateJsxNode = ({ input, nodePath, prettierConfigOverride, }) => (0, s
14
14
  prettierConfigOverride,
15
15
  });
16
16
  exports.duplicateJsxNode = duplicateJsxNode;
17
+ const duplicateJsxNodes = ({ input, nodePaths, prettierConfigOverride, }) => (0, studio_codemods_1.duplicateJsxNodes)({
18
+ input,
19
+ nodePaths,
20
+ formatFile: ({ contents, prettierConfigOverride: override }) => (0, format_file_content_1.formatFileContent)({
21
+ input: contents,
22
+ prettierConfigOverride: override,
23
+ }),
24
+ prettierConfigOverride,
25
+ });
26
+ exports.duplicateJsxNodes = duplicateJsxNodes;
@@ -4,6 +4,7 @@ import type { SequenceNodePath } from 'remotion';
4
4
  export type CapturedJsxNodePath = {
5
5
  node: JSXOpeningElement;
6
6
  nodePath: SequenceNodePath;
7
+ signature: string;
7
8
  };
8
9
  export declare const captureJsxNodePaths: (ast: File) => CapturedJsxNodePath[];
9
10
  export declare const getNodePathRemappings: ({ ast, captured, output, }: {
@@ -44,6 +44,7 @@ const captureJsxNodePaths = (ast) => {
44
44
  captured.push({
45
45
  node: path.node,
46
46
  nodePath: (0, can_update_sequence_props_1.getNodePathForRecastPath)(path, ast),
47
+ signature: recast.prettyPrint(path.node).code,
47
48
  });
48
49
  return this.traverse(path);
49
50
  },
@@ -74,15 +75,27 @@ const getNodePathRemappings = ({ ast, captured, output, }) => {
74
75
  for (let i = 0; i < nodesAfterMutation.length; i++) {
75
76
  finalNodePathByNode.set(nodesAfterMutation[i], finalNodePaths[i]);
76
77
  }
77
- const nodePathRemappings = captured.flatMap(({ node, nodePath }) => {
78
+ const capturedNodes = new Set(captured.map(({ node }) => node));
79
+ const nodePathRemappings = captured.flatMap(({ node, nodePath, signature }) => {
78
80
  var _a;
79
81
  const newNodePath = (_a = finalNodePathByNode.get(node)) !== null && _a !== void 0 ? _a : null;
80
82
  if (newNodePath !== null &&
81
- JSON.stringify(nodePath) === JSON.stringify(newNodePath)) {
83
+ JSON.stringify(nodePath) === JSON.stringify(newNodePath) &&
84
+ recast.prettyPrint(node).code === signature) {
82
85
  return [];
83
86
  }
84
87
  return [{ oldNodePath: nodePath, newNodePath }];
85
88
  });
89
+ for (const node of nodesAfterMutation) {
90
+ if (capturedNodes.has(node)) {
91
+ continue;
92
+ }
93
+ const newNodePath = finalNodePathByNode.get(node);
94
+ if (!newNodePath) {
95
+ throw new Error('Could not map inserted JSX node path');
96
+ }
97
+ nodePathRemappings.push({ oldNodePath: null, newNodePath });
98
+ }
86
99
  return { finalNodePathByNode, nodePathRemappings };
87
100
  };
88
101
  exports.getNodePathRemappings = getNodePathRemappings;
@@ -8,8 +8,5 @@ export declare const splitVideoFromAudio: ({ input, nodePath, prettierConfigOver
8
8
  formatted: boolean;
9
9
  nodeLabel: string;
10
10
  logLine: number;
11
- nodePathRemappings: {
12
- oldNodePath: SequenceNodePath;
13
- newNodePath: SequenceNodePath | null;
14
- }[];
11
+ nodePathRemappings: import("@remotion/studio-shared").SequenceNodePathRemapping[];
15
12
  }>;
@@ -255,7 +255,7 @@ function getArgumentsForLineNumber(editor, fileName, lineNumber, colNumber) {
255
255
  editor.endsWith('/Zed Preview.app/Contents/MacOS/cli');
256
256
  const isFolder = node_fs_1.default.existsSync(fileName) && node_fs_1.default.lstatSync(fileName).isDirectory();
257
257
  if (isZedEditor) {
258
- return [fileName + ':' + lineNumber + ':' + colNumber];
258
+ return ['--existing', fileName + ':' + lineNumber + ':' + colNumber];
259
259
  }
260
260
  switch (editorBasename) {
261
261
  case 'atom':
@@ -37,6 +37,7 @@ const prepare_element_install_1 = require("./routes/prepare-element-install");
37
37
  const project_info_1 = require("./routes/project-info");
38
38
  const redo_1 = require("./routes/redo");
39
39
  const register_client_render_1 = require("./routes/register-client-render");
40
+ const release_notes_1 = require("./routes/release-notes");
40
41
  const remotion_skills_info_1 = require("./routes/remotion-skills-info");
41
42
  const remove_render_1 = require("./routes/remove-render");
42
43
  const rename_static_file_1 = require("./routes/rename-static-file");
@@ -107,6 +108,7 @@ exports.allApiRoutes = {
107
108
  '/api/split-jsx-sequence': split_jsx_sequence_1.splitJsxSequenceHandler,
108
109
  '/api/split-video-from-audio': split_video_from_audio_1.splitVideoFromAudioHandler,
109
110
  '/api/update-available': update_available_1.handleUpdate,
111
+ '/api/release-notes': release_notes_1.getReleaseNotesHandler,
110
112
  '/api/remotion-skills-info': remotion_skills_info_1.remotionSkillsInfoHandler,
111
113
  '/api/project-info': project_info_1.projectInfoHandler,
112
114
  '/api/delete-static-file': delete_static_file_1.deleteStaticFileHandler,
@@ -5,7 +5,6 @@ export type ElementInstallTarget = {
5
5
  clientId: string;
6
6
  compositionFile: string | null;
7
7
  compositionId: string | null;
8
- canInstall: boolean;
9
8
  lastFocusedAt: number | null;
10
9
  readOnly: boolean;
11
10
  studioUrl: string;
@@ -80,8 +80,7 @@ const consumeStudioProtocolTarget = ({ now, origin, purpose, targetId, }) => {
80
80
  if (purpose !== 'install-element') {
81
81
  return issued.target;
82
82
  }
83
- if (!current.canInstall ||
84
- current.compositionFile !== issued.target.compositionFile ||
83
+ if (current.compositionFile !== issued.target.compositionFile ||
85
84
  current.compositionId !== issued.target.compositionId) {
86
85
  return null;
87
86
  }
@@ -65,7 +65,6 @@ const deleteJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
65
65
  const nodePathMutation = (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(updates.map((update) => ({
66
66
  absolutePath: update.absolutePath,
67
67
  remappings: update.nodePathRemappings,
68
- restoredNodePaths: [],
69
68
  })));
70
69
  for (const update of updates) {
71
70
  const deletedNodeDescription = getDeletedNodeDescription(update.nodeLabels);
@@ -11,56 +11,88 @@ const sequence_node_path_mutation_1 = require("../sequence-node-path-mutation");
11
11
  const undo_stack_1 = require("../undo-stack");
12
12
  const log_update_1 = require("./log-updates/log-update");
13
13
  const source_file_write_queue_1 = require("./source-file-write-queue");
14
- const duplicateJsxNodeHandler = ({ input: { fileName, nodePath }, remotionRoot, logLevel }) => {
14
+ const duplicateJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
15
15
  return (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
16
+ var _a;
16
17
  try {
17
- renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[duplicate-jsx-node] Received request for fileName="${fileName}"`);
18
- const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
19
- remotionRoot,
20
- fileName,
21
- action: 'modify',
22
- });
23
- const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
24
- const { output, formatted, nodeLabel, logLine, nodePathRemappings } = await (0, duplicate_jsx_node_1.duplicateJsxNode)({ input: fileContents, nodePath });
25
- const nodePathMutation = (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)([
26
- {
18
+ if (nodes.length === 0) {
19
+ throw new Error('No JSX nodes were specified for duplication');
20
+ }
21
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[duplicate-jsx-node] Received request to duplicate ${nodes.length} JSX node${nodes.length === 1 ? '' : 's'}`);
22
+ const itemsByFileName = new Map();
23
+ for (const item of nodes) {
24
+ const fileItems = (_a = itemsByFileName.get(item.fileName)) !== null && _a !== void 0 ? _a : [];
25
+ fileItems.push(item);
26
+ itemsByFileName.set(item.fileName, fileItems);
27
+ }
28
+ const updates = await Promise.all([...itemsByFileName.entries()].map(async ([fileName, fileItems]) => {
29
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
30
+ remotionRoot,
31
+ fileName,
32
+ action: 'modify',
33
+ });
34
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
35
+ const { output, formatted, nodeLabels, logLines, nodePathRemappings } = await (0, duplicate_jsx_node_1.duplicateJsxNodes)({
36
+ input: fileContents,
37
+ nodePaths: fileItems.map((item) => item.nodePath),
38
+ });
39
+ return {
27
40
  absolutePath,
28
- remappings: nodePathRemappings,
29
- restoredNodePaths: [],
30
- },
31
- ]);
32
- (0, undo_stack_1.pushToUndoStack)({
33
- filePath: absolutePath,
34
- oldContents: fileContents,
35
- newContents: null,
41
+ fileRelativeToRoot,
42
+ fileContents,
43
+ formatted,
44
+ logLine: Math.min(...logLines),
45
+ nodeLabels,
46
+ nodePathRemappings,
47
+ output,
48
+ };
49
+ }));
50
+ const nodePathMutation = (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(updates.map((update) => ({
51
+ absolutePath: update.absolutePath,
52
+ remappings: update.nodePathRemappings,
53
+ })));
54
+ const duplicatedNodeDescription = nodes.length === 1
55
+ ? updates[0].nodeLabels[0]
56
+ : `${nodes.length} JSX nodes`;
57
+ (0, undo_stack_1.pushTransactionToUndoStack)({
58
+ snapshots: updates.map((update) => ({
59
+ filePath: update.absolutePath,
60
+ oldContents: update.fileContents,
61
+ newContents: null,
62
+ logLine: update.logLine,
63
+ nodePathRemappings: update.nodePathRemappings,
64
+ })),
36
65
  logLevel,
37
66
  remotionRoot,
38
- logLine,
39
67
  description: {
40
- undoMessage: `↩️ Duplication of ${nodeLabel}`,
41
- redoMessage: `↪️ Duplication of ${nodeLabel}`,
68
+ undoMessage: `↩️ Duplication of ${duplicatedNodeDescription}`,
69
+ redoMessage: `↪️ Duplication of ${duplicatedNodeDescription}`,
42
70
  },
43
71
  entryType: 'duplicate-jsx-node',
44
72
  suppressHmrOnFileRestore: false,
45
- nodePathRemappings,
46
- });
47
- (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
48
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)({
49
- file: absolutePath,
50
- content: output,
51
- originatorClientId: undefined,
52
- metadata: { skipSequencePropsUpdate: true },
53
- });
54
- const locationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
55
- remotionRoot,
56
- absolutePath,
57
- line: logLine,
58
73
  });
59
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${(0, source_file_write_queue_1.getCodemodTimingPrefix)(logLevel)}${renderer_1.RenderInternals.chalk.blueBright(`${locationLabel}`)} Duplicated ${nodeLabel}`);
60
- if (!formatted) {
61
- (0, log_update_1.warnAboutPrettierOnce)(logLevel);
74
+ for (const update of updates) {
75
+ (0, undo_stack_1.suppressUndoStackInvalidation)(update.absolutePath);
76
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)({
77
+ file: update.absolutePath,
78
+ content: update.output,
79
+ originatorClientId: undefined,
80
+ metadata: { skipSequencePropsUpdate: true },
81
+ });
82
+ const locationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
83
+ remotionRoot,
84
+ absolutePath: update.absolutePath,
85
+ line: update.logLine,
86
+ });
87
+ const fileDescription = update.nodeLabels.length === 1
88
+ ? update.nodeLabels[0]
89
+ : `${update.nodeLabels.length} JSX nodes`;
90
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${(0, source_file_write_queue_1.getCodemodTimingPrefix)(logLevel)}${renderer_1.RenderInternals.chalk.blueBright(`${locationLabel}`)} Duplicated ${fileDescription}`);
91
+ if (!update.formatted) {
92
+ (0, log_update_1.warnAboutPrettierOnce)(logLevel);
93
+ }
94
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `[duplicate-jsx-node] Wrote ${update.fileRelativeToRoot}${update.formatted ? ' (formatted)' : ''}`);
62
95
  }
63
- renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `[duplicate-jsx-node] Wrote ${fileRelativeToRoot}${formatted ? ' (formatted)' : ''}`);
64
96
  (0, undo_stack_1.printUndoHint)(logLevel);
65
97
  return {
66
98
  success: true,
@@ -15,17 +15,19 @@ export declare const makeRelativeElementImportPath: ({ fromFile, toFile, }: {
15
15
  fromFile: string;
16
16
  toFile: string;
17
17
  }) => string;
18
- export declare const getElementInstallPlan: ({ compositionFile, compositionId, element, remotionRoot, }: PrepareElementInstallRequest & {
18
+ export declare const getElementInstallPlan: ({ destination, element, entryPoint, remotionRoot, }: PrepareElementInstallRequest & {
19
+ entryPoint: string;
19
20
  remotionRoot: string;
20
21
  }) => Promise<{
21
22
  componentName: string;
23
+ destinationCompositionFileName: string;
22
24
  elementFileExists: boolean;
23
25
  elementFileName: string;
24
26
  existingElementSource: string | null;
25
27
  expectedFileState: ElementInstallExpectedFileState;
26
28
  filePath: string;
27
29
  importPath: string;
28
- location: import("@remotion/studio-codemods").ResolvedCompositionComponentWithFile;
30
+ location: import("@remotion/studio-codemods").ResolvedCompositionComponentWithFile | null;
29
31
  safePaths: {
30
32
  compositionFileName: string;
31
33
  elementFileName: string;
@@ -9,6 +9,7 @@ const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const studio_protocol_1 = require("@remotion/studio-protocol");
11
11
  const resolve_composition_component_1 = require("../../helpers/resolve-composition-component");
12
+ const project_info_1 = require("../project-info");
12
13
  const safe_element_install_path_1 = require("./safe-element-install-path");
13
14
  const normalizeElementSourceForComparison = (source) => {
14
15
  return source.replace(/\r\n/g, '\n').trim();
@@ -74,27 +75,38 @@ const getExpectedFileState = (existingSource) => {
74
75
  sourceHash: (0, exports.getElementSourceHash)(existingSource),
75
76
  };
76
77
  };
77
- const getElementInstallPlan = async ({ compositionFile, compositionId, element, remotionRoot, }) => {
78
+ const getElementInstallPlan = async ({ destination, element, entryPoint, remotionRoot, }) => {
79
+ var _a;
78
80
  (0, exports.validateElementForInstallation)(element);
79
81
  const componentName = studio_protocol_1.StudioProtocolInternals.getElementComponentNameFromSourceCode(element.sourceCode);
80
82
  if (componentName === null) {
81
83
  throw new Error('Element source must export exactly one named component');
82
84
  }
83
- const location = await (0, resolve_composition_component_1.resolveCompositionComponentWithFile)({
84
- remotionRoot,
85
- compositionFile,
86
- compositionId,
87
- });
88
- if (!location.canAddSequence) {
85
+ const location = destination.type === 'current-composition'
86
+ ? await (0, resolve_composition_component_1.resolveCompositionComponentWithFile)({
87
+ remotionRoot,
88
+ compositionFile: destination.compositionFile,
89
+ compositionId: destination.compositionId,
90
+ })
91
+ : null;
92
+ if (location !== null && !location.canAddSequence) {
89
93
  throw new Error('Cannot insert Element into this composition component');
90
94
  }
91
95
  const derivedElementFileName = studio_protocol_1.StudioProtocolInternals.makeElementFileNameFromSlug(element.slug);
92
96
  if (derivedElementFileName === null) {
93
97
  throw new Error('Element slug must produce a safe lowercase .tsx file name');
94
98
  }
99
+ const destinationCompositionFile = destination.compositionFile;
100
+ const destinationCompositionFileName = destinationCompositionFile === null
101
+ ? (await (0, project_info_1.getProjectInfo)(remotionRoot, entryPoint)).rootFile
102
+ : node_path_1.default.resolve(remotionRoot, destinationCompositionFile);
103
+ if (destinationCompositionFileName === null) {
104
+ throw new Error('Could not find the root file of the project');
105
+ }
106
+ const compositionFileName = (_a = location === null || location === void 0 ? void 0 : location.fileName) !== null && _a !== void 0 ? _a : destinationCompositionFileName;
95
107
  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),
108
+ compositionFileName,
109
+ elementFileName: node_path_1.default.resolve(node_path_1.default.dirname(compositionFileName), derivedElementFileName),
98
110
  remotionRoot,
99
111
  });
100
112
  const elementFileExists = (0, node_fs_1.existsSync)(safePaths.elementFileName);
@@ -103,6 +115,7 @@ const getElementInstallPlan = async ({ compositionFile, compositionId, element,
103
115
  : null;
104
116
  return {
105
117
  componentName,
118
+ destinationCompositionFileName,
106
119
  elementFileExists,
107
120
  elementFileName: safePaths.elementFileName,
108
121
  existingElementSource,
@@ -22,7 +22,7 @@ const hasExpectedFileState = ({ expected, actual, }) => {
22
22
  }
23
23
  return actual.sourceHash === expected.sourceHash;
24
24
  };
25
- const insertElementHandler = ({ input: { compositionFile, compositionId, element, expectedFileState, from, position, overwriteExisting, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
25
+ const insertElementHandler = ({ input: { compositionFile, compositionId, element, expectedFileState, from, position, overwriteExisting, }, entryPoint, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
26
26
  var _a, _b;
27
27
  try {
28
28
  (0, element_install_plan_1.validateElementInstallPosition)(position);
@@ -34,9 +34,13 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
34
34
  const componentOwnsSequence = installationMode === 'component-owned-sequence';
35
35
  renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[insert-element] Received request for compositionFile="${compositionFile}" compositionId="${compositionId}" element="${element.slug}"`);
36
36
  const plan = await (0, element_install_plan_1.getElementInstallPlan)({
37
- compositionFile,
38
- compositionId,
37
+ destination: {
38
+ type: 'current-composition',
39
+ compositionFile,
40
+ compositionId,
41
+ },
39
42
  element,
43
+ entryPoint,
40
44
  remotionRoot,
41
45
  });
42
46
  if (expectedFileState !== null &&
@@ -112,9 +116,13 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
112
116
  },
113
117
  });
114
118
  const finalPlan = await (0, element_install_plan_1.getElementInstallPlan)({
115
- compositionFile,
116
- compositionId,
119
+ destination: {
120
+ type: 'current-composition',
121
+ compositionFile,
122
+ compositionId,
123
+ },
117
124
  element,
125
+ entryPoint,
118
126
  remotionRoot,
119
127
  });
120
128
  if (finalPlan.safePaths.compositionFileName !==
@@ -131,7 +139,6 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
131
139
  {
132
140
  absolutePath: inserted.fileName,
133
141
  remappings: inserted.nodePathRemappings,
134
- restoredNodePaths: [],
135
142
  },
136
143
  ]);
137
144
  (0, undo_stack_1.pushTransactionToUndoStack)({
@@ -171,7 +171,6 @@ const insertJsxElementHandler = ({ input: { compositionFile, compositionId, elem
171
171
  {
172
172
  absolutePath: fileName,
173
173
  remappings: nodePathRemappings,
174
- restoredNodePaths: [],
175
174
  },
176
175
  ]);
177
176
  if (insertedNodePath === null) {
@@ -3,12 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareElementInstallHandler = void 0;
4
4
  const element_install_plan_1 = require("./element-install-plan");
5
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 () => {
6
+ const prepareElementInstallHandler = ({ entryPoint, input, remotionRoot }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
7
7
  try {
8
- const plan = await (0, element_install_plan_1.getElementInstallPlan)({ ...input, remotionRoot });
8
+ const plan = await (0, element_install_plan_1.getElementInstallPlan)({
9
+ ...input,
10
+ entryPoint,
11
+ remotionRoot,
12
+ });
9
13
  return {
10
14
  success: true,
11
15
  plan: {
16
+ compositionFile: plan.destinationCompositionFileName,
12
17
  expectedFileState: plan.expectedFileState,
13
18
  filePath: plan.filePath,
14
19
  },
@@ -0,0 +1,3 @@
1
+ import type { GetReleaseNotesRequest, GetReleaseNotesResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const getReleaseNotesHandler: ApiHandler<GetReleaseNotesRequest, GetReleaseNotesResponse>;
@@ -0,0 +1,127 @@
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.getReleaseNotesHandler = void 0;
7
+ const semver_1 = __importDefault(require("semver"));
8
+ const githubApiVersion = '2022-11-28';
9
+ const releaseNotesTimeout = 5000;
10
+ const releaseNotesCacheDuration = 5 * 60 * 1000;
11
+ const maximumReleaseNotes = 5;
12
+ const releaseNotesCache = new Map();
13
+ const getReleaseNotesHandler = async ({ input }) => {
14
+ const currentVersion = semver_1.default.valid(input.currentVersion);
15
+ const latestVersion = semver_1.default.valid(input.latestVersion);
16
+ if (currentVersion === null || latestVersion === null) {
17
+ throw new Error(`Invalid Remotion version range: ${input.currentVersion} to ${input.latestVersion}`);
18
+ }
19
+ const cacheKey = `${currentVersion}:${latestVersion}`;
20
+ const cachedReleaseNotes = releaseNotesCache.get(cacheKey);
21
+ if (cachedReleaseNotes && cachedReleaseNotes.expiresAt > Date.now()) {
22
+ return cachedReleaseNotes.response;
23
+ }
24
+ const controller = new AbortController();
25
+ const timeout = setTimeout(() => controller.abort(), releaseNotesTimeout);
26
+ try {
27
+ const releaseResponse = await fetch('https://api.github.com/repos/remotion-dev/remotion/releases?per_page=100', {
28
+ headers: {
29
+ accept: 'application/vnd.github+json',
30
+ 'user-agent': 'Remotion Studio',
31
+ 'x-github-api-version': githubApiVersion,
32
+ },
33
+ signal: controller.signal,
34
+ });
35
+ if (!releaseResponse.ok) {
36
+ return { hasMore: false, releases: [] };
37
+ }
38
+ const githubReleases = (await releaseResponse.json());
39
+ if (!Array.isArray(githubReleases)) {
40
+ return { hasMore: false, releases: [] };
41
+ }
42
+ const matchingReleases = githubReleases
43
+ .flatMap((release) => {
44
+ if (typeof release !== 'object' || release === null) {
45
+ return [];
46
+ }
47
+ const { body, published_at: publishedAt, tag_name: tagName, } = release;
48
+ if (typeof tagName !== 'string') {
49
+ return [];
50
+ }
51
+ const version = semver_1.default.valid(tagName.startsWith('v') ? tagName.slice(1) : tagName);
52
+ if (version === null ||
53
+ !semver_1.default.gt(version, currentVersion) ||
54
+ !semver_1.default.lte(version, latestVersion)) {
55
+ return [];
56
+ }
57
+ return [
58
+ {
59
+ body: typeof body === 'string' ? body : null,
60
+ publishedAt: typeof publishedAt === 'string' ? publishedAt : null,
61
+ version,
62
+ },
63
+ ];
64
+ })
65
+ .sort((a, b) => semver_1.default.rcompare(a.version, b.version));
66
+ const hasMore = matchingReleases.length > maximumReleaseNotes;
67
+ const releases = await Promise.all(matchingReleases.slice(0, maximumReleaseNotes).map(async (release) => {
68
+ if (release.body === null || release.body.trim() === '') {
69
+ return {
70
+ publishedAt: release.publishedAt,
71
+ releaseNotesHtml: null,
72
+ version: release.version,
73
+ };
74
+ }
75
+ try {
76
+ const markdownResponse = await fetch('https://api.github.com/markdown', {
77
+ body: JSON.stringify({
78
+ context: 'remotion-dev/remotion',
79
+ mode: 'gfm',
80
+ text: release.body,
81
+ }),
82
+ headers: {
83
+ accept: 'text/html',
84
+ 'content-type': 'application/json',
85
+ 'user-agent': 'Remotion Studio',
86
+ 'x-github-api-version': githubApiVersion,
87
+ },
88
+ method: 'POST',
89
+ signal: controller.signal,
90
+ });
91
+ if (!markdownResponse.ok) {
92
+ return {
93
+ publishedAt: release.publishedAt,
94
+ releaseNotesHtml: null,
95
+ version: release.version,
96
+ };
97
+ }
98
+ const releaseNotesHtml = await markdownResponse.text();
99
+ return {
100
+ publishedAt: release.publishedAt,
101
+ releaseNotesHtml: releaseNotesHtml || null,
102
+ version: release.version,
103
+ };
104
+ }
105
+ catch (_a) {
106
+ return {
107
+ publishedAt: release.publishedAt,
108
+ releaseNotesHtml: null,
109
+ version: release.version,
110
+ };
111
+ }
112
+ }));
113
+ const response = { hasMore, releases };
114
+ releaseNotesCache.set(cacheKey, {
115
+ expiresAt: Date.now() + releaseNotesCacheDuration,
116
+ response,
117
+ });
118
+ return response;
119
+ }
120
+ catch (_a) {
121
+ return { hasMore: false, releases: [] };
122
+ }
123
+ finally {
124
+ clearTimeout(timeout);
125
+ }
126
+ };
127
+ exports.getReleaseNotesHandler = getReleaseNotesHandler;
@@ -32,7 +32,6 @@ const reorderSequenceHandler = ({ input: { fileName, sourceNodePath, targetNodeP
32
32
  {
33
33
  absolutePath,
34
34
  remappings: nodePathRemappings,
35
- restoredNodePaths: [],
36
35
  },
37
36
  ]);
38
37
  (0, undo_stack_1.pushToUndoStack)({
@@ -30,7 +30,6 @@ const splitJsxSequenceHandler = ({ input: { fileName, nodePath, sequenceKeys, sp
30
30
  {
31
31
  absolutePath,
32
32
  remappings: nodePathRemappings,
33
- restoredNodePaths: [],
34
33
  },
35
34
  ]);
36
35
  (0, undo_stack_1.pushToUndoStack)({
@@ -28,7 +28,6 @@ const splitVideoFromAudioHandler = ({ input: { fileName, nodePath }, remotionRoo
28
28
  {
29
29
  absolutePath,
30
30
  remappings: nodePathRemappings,
31
- restoredNodePaths: [],
32
31
  },
33
32
  ]);
34
33
  (0, undo_stack_1.pushToUndoStack)({
@@ -1,7 +1,5 @@
1
1
  import type { SequenceNodePathMutation, SequenceNodePathRemapping } from '@remotion/studio-shared';
2
- import type { SequenceNodePath } from 'remotion';
3
2
  export declare const broadcastSequenceNodePathMutation: (files: {
4
3
  absolutePath: string;
5
4
  remappings: SequenceNodePathRemapping[];
6
- restoredNodePaths: SequenceNodePath[];
7
5
  }[]) => SequenceNodePathMutation;
@@ -19,16 +19,20 @@ const startServer = async (options) => {
19
19
  var _a, _b;
20
20
  const desiredPort = (_b = (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : (process.env.PORT ? Number(process.env.PORT) : undefined)) !== null && _b !== void 0 ? _b : undefined;
21
21
  const portConfig = renderer_1.RenderInternals.getPortConfig(options.forceIPv4);
22
- const onPortUnavailable = options.forceNew
23
- ? undefined
24
- : async (port) => {
22
+ const onPortUnavailable = async (port) => {
23
+ if (!options.forceNew) {
25
24
  const detection = await (0, detect_remotion_server_1.detectRemotionServer)({
26
25
  port,
27
26
  cwd: options.remotionRoot,
28
27
  hostname: portConfig.hostsToTry[0],
29
28
  });
30
- return detection.type === 'match' ? 'stop' : 'continue';
31
- };
29
+ if (detection.type === 'match') {
30
+ return 'stop';
31
+ }
32
+ }
33
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel: options.logLevel }, renderer_1.RenderInternals.chalk.gray(`Port ${port} is busy, trying another.`));
34
+ return 'continue';
35
+ };
32
36
  let portSelection = await renderer_1.RenderInternals.getDesiredPort({
33
37
  desiredPort,
34
38
  from: 3000,
@@ -32,8 +32,7 @@ const getLiveStudioTarget = (requestId) => {
32
32
  }
33
33
  return target;
34
34
  };
35
- const isInstallableTarget = (target) => target !== null &&
36
- target.canInstall &&
35
+ const isElementRequestTarget = (target) => target !== null &&
37
36
  target.compositionFile !== null &&
38
37
  target.compositionId !== null &&
39
38
  target.lastFocusedAt !== null;
@@ -68,7 +67,7 @@ const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRo
68
67
  setTimeout(() => {
69
68
  const now = Date.now();
70
69
  const target = getLiveStudioTarget(requestId);
71
- const installTarget = isInstallableTarget(target) ? target : null;
70
+ const installTarget = isElementRequestTarget(target) ? target : null;
72
71
  const issuedInstallTarget = installTarget === null
73
72
  ? null
74
73
  : (0, element_install_state_1.issueStudioProtocolTarget)({
@@ -257,18 +257,10 @@ function popUndo() {
257
257
  return [
258
258
  {
259
259
  absolutePath: snapshot.filePath,
260
- remappings: snapshot.nodePathRemappings.flatMap((remapping) => {
261
- if (remapping.newNodePath === null) {
262
- return [];
263
- }
264
- return [
265
- {
266
- oldNodePath: remapping.newNodePath,
267
- newNodePath: remapping.oldNodePath,
268
- },
269
- ];
270
- }),
271
- restoredNodePaths: snapshot.nodePathRemappings.flatMap((remapping) => remapping.newNodePath === null ? [remapping.oldNodePath] : []),
260
+ remappings: snapshot.nodePathRemappings.map((remapping) => ({
261
+ oldNodePath: remapping.newNodePath,
262
+ newNodePath: remapping.oldNodePath,
263
+ })),
272
264
  },
273
265
  ];
274
266
  });
@@ -339,7 +331,6 @@ function popRedo() {
339
331
  {
340
332
  absolutePath: snapshot.filePath,
341
333
  remappings: snapshot.nodePathRemappings,
342
- restoredNodePaths: [],
343
334
  },
344
335
  ];
345
336
  });
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/studio-server"
4
4
  },
5
5
  "name": "@remotion/studio-server",
6
- "version": "4.0.518",
6
+ "version": "4.0.519",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@remotion/studio-protocol": "4.0.518",
26
+ "@remotion/studio-protocol": "4.0.519",
27
27
  "@babel/types": "7.24.0",
28
28
  "@babel/parser": "7.24.1",
29
29
  "@svgr/core": "8.1.0",
@@ -32,12 +32,12 @@
32
32
  "semver": "7.5.3",
33
33
  "zod": "4.4.3",
34
34
  "prettier": "3.8.1",
35
- "remotion": "4.0.518",
35
+ "remotion": "4.0.519",
36
36
  "recast": "0.23.21",
37
- "@remotion/bundler": "4.0.518",
38
- "@remotion/renderer": "4.0.518",
39
- "@remotion/studio-codemods": "4.0.518",
40
- "@remotion/studio-shared": "4.0.518",
37
+ "@remotion/bundler": "4.0.519",
38
+ "@remotion/renderer": "4.0.519",
39
+ "@remotion/studio-codemods": "4.0.519",
40
+ "@remotion/studio-shared": "4.0.519",
41
41
  "memfs": "3.4.3",
42
42
  "open": "8.4.2"
43
43
  },
@@ -45,7 +45,7 @@
45
45
  "ast-types": "0.16.1",
46
46
  "react": "19.2.3",
47
47
  "@types/semver": "7.5.3",
48
- "@remotion/eslint-config-internal": "4.0.518",
48
+ "@remotion/eslint-config-internal": "4.0.519",
49
49
  "eslint": "9.19.0",
50
50
  "@types/node": "20.12.14",
51
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"