@remotion/studio-server 4.0.516 → 4.0.518

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.
@@ -1,11 +1,9 @@
1
1
  import { deleteJsxElementAtPath, findJsxElementPathForDeletion, getJsxElementTagLabel } from '@remotion/studio-codemods';
2
2
  import type { SequenceNodePath } from 'remotion';
3
3
  export { deleteJsxElementAtPath, findJsxElementPathForDeletion, getJsxElementTagLabel, };
4
- export declare const deleteJsxNodes: ({ input, nodePaths, onFormatFile, prettierConfigOverride, }: {
4
+ export declare const deleteJsxNodes: ({ input, nodePaths, }: {
5
5
  input: string;
6
6
  nodePaths: SequenceNodePath[];
7
- onFormatFile: ((stage: "complete" | "start") => void) | null;
8
- prettierConfigOverride?: Record<string, unknown> | null | undefined;
9
7
  }) => Promise<{
10
8
  output: string;
11
9
  formatted: boolean;
@@ -16,10 +14,9 @@ export declare const deleteJsxNodes: ({ input, nodePaths, onFormatFile, prettier
16
14
  newNodePath: SequenceNodePath | null;
17
15
  }[];
18
16
  }>;
19
- export declare const deleteJsxNode: ({ input, nodePath, prettierConfigOverride, }: {
17
+ export declare const deleteJsxNode: ({ input, nodePath, }: {
20
18
  input: string;
21
19
  nodePath: SequenceNodePath;
22
- prettierConfigOverride?: Record<string, unknown> | null | undefined;
23
20
  }) => Promise<{
24
21
  output: string;
25
22
  formatted: boolean;
@@ -5,29 +5,13 @@ const studio_codemods_1 = require("@remotion/studio-codemods");
5
5
  Object.defineProperty(exports, "deleteJsxElementAtPath", { enumerable: true, get: function () { return studio_codemods_1.deleteJsxElementAtPath; } });
6
6
  Object.defineProperty(exports, "findJsxElementPathForDeletion", { enumerable: true, get: function () { return studio_codemods_1.findJsxElementPathForDeletion; } });
7
7
  Object.defineProperty(exports, "getJsxElementTagLabel", { enumerable: true, get: function () { return studio_codemods_1.getJsxElementTagLabel; } });
8
- const format_file_content_1 = require("./format-file-content");
9
- const deleteJsxNodes = ({ input, nodePaths, onFormatFile, prettierConfigOverride, }) => (0, studio_codemods_1.deleteJsxNodes)({
8
+ const deleteJsxNodes = ({ input, nodePaths, }) => (0, studio_codemods_1.deleteJsxNodes)({
10
9
  input,
11
10
  nodePaths,
12
- formatFile: async ({ contents, prettierConfigOverride: override }) => {
13
- onFormatFile === null || onFormatFile === void 0 ? void 0 : onFormatFile('start');
14
- const result = await (0, format_file_content_1.formatFileContent)({
15
- input: contents,
16
- prettierConfigOverride: override,
17
- });
18
- onFormatFile === null || onFormatFile === void 0 ? void 0 : onFormatFile('complete');
19
- return result;
20
- },
21
- prettierConfigOverride,
22
11
  });
23
12
  exports.deleteJsxNodes = deleteJsxNodes;
24
- const deleteJsxNode = ({ input, nodePath, prettierConfigOverride, }) => (0, studio_codemods_1.deleteJsxNode)({
13
+ const deleteJsxNode = ({ input, nodePath, }) => (0, studio_codemods_1.deleteJsxNode)({
25
14
  input,
26
15
  nodePath,
27
- formatFile: ({ contents, prettierConfigOverride: override }) => (0, format_file_content_1.formatFileContent)({
28
- input: contents,
29
- prettierConfigOverride: override,
30
- }),
31
- prettierConfigOverride,
32
16
  });
33
17
  exports.deleteJsxNode = deleteJsxNode;
@@ -44,10 +44,10 @@ const getStatusForSkillsDirectory = ({ currentVersion, scope, skillsDirectory, }
44
44
  }
45
45
  const outdatedSkillNames = remotion_skill_names_1.remotionSkillNames.filter((skillName) => {
46
46
  const skill = installedSkills.get(skillName);
47
- return (!(skill === null || skill === void 0 ? void 0 : skill.installed) ||
48
- skill.version === null ||
49
- semver_1.default.valid(skill.version) === null ||
50
- semver_1.default.lt(skill.version, currentVersion));
47
+ return ((skill === null || skill === void 0 ? void 0 : skill.installed) &&
48
+ (skill.version === null ||
49
+ semver_1.default.valid(skill.version) === null ||
50
+ semver_1.default.lt(skill.version, currentVersion)));
51
51
  });
52
52
  if (outdatedSkillNames.length > 0) {
53
53
  return { type: 'outdated', scope, skillsDirectory, outdatedSkillNames };
@@ -1,5 +1,7 @@
1
1
  import type { ApiRoutes } from '@remotion/studio-shared';
2
2
  import type { ApiHandler } from './api-types';
3
+ type StandardApiRoute = Exclude<keyof ApiRoutes, '/api/update-config'>;
3
4
  export declare const allApiRoutes: {
4
- [key in keyof ApiRoutes]: ApiHandler<ApiRoutes[key]['Request'], ApiRoutes[key]['Response']>;
5
+ [key in StandardApiRoute]: ApiHandler<ApiRoutes[key]['Request'], ApiRoutes[key]['Response']>;
5
6
  };
7
+ export {};
@@ -57,7 +57,6 @@ const unsubscribe_from_default_props_1 = require("./routes/unsubscribe-from-defa
57
57
  const unsubscribe_from_file_existence_1 = require("./routes/unsubscribe-from-file-existence");
58
58
  const unsubscribe_from_sequence_props_1 = require("./routes/unsubscribe-from-sequence-props");
59
59
  const update_available_1 = require("./routes/update-available");
60
- const update_config_1 = require("./routes/update-config");
61
60
  const update_default_props_1 = require("./routes/update-default-props");
62
61
  const update_effect_keyframe_settings_1 = require("./routes/update-effect-keyframe-settings");
63
62
  const update_element_install_target_1 = require("./routes/update-element-install-target");
@@ -113,7 +112,6 @@ exports.allApiRoutes = {
113
112
  '/api/delete-static-file': delete_static_file_1.deleteStaticFileHandler,
114
113
  '/api/rename-static-file': rename_static_file_1.renameStaticFileHandler,
115
114
  '/api/restart-studio': restart_studio_1.handleRestartStudio,
116
- '/api/update-config': update_config_1.updateConfigHandler,
117
115
  '/api/default-coding-agent-info': default_coding_agent_1.getDefaultCodingAgentInfoHandler,
118
116
  '/api/default-editor-info': default_editor_1.getDefaultEditorInfoHandler,
119
117
  '/api/install-package': install_dependency_1.handleInstallPackage,
@@ -11,7 +11,7 @@ export type ElementInstallTarget = {
11
11
  studioUrl: string;
12
12
  updatedAt: number;
13
13
  };
14
- export type StudioProtocolTargetPurpose = 'install-element' | 'set-license-key';
14
+ export type StudioProtocolTargetPurpose = 'install-element' | 'set-license-key' | 'add-element-library';
15
15
  type StudioProtocolTarget = {
16
16
  readonly id: string;
17
17
  readonly origin: string;
@@ -77,7 +77,7 @@ const consumeStudioProtocolTarget = ({ now, origin, purpose, targetId, }) => {
77
77
  current.readOnly) {
78
78
  return null;
79
79
  }
80
- if (purpose === 'set-license-key') {
80
+ if (purpose !== 'install-element') {
81
81
  return issued.target;
82
82
  }
83
83
  if (!current.canInstall ||
@@ -10,7 +10,6 @@ const format_log_file_location_1 = require("../format-log-file-location");
10
10
  const hmr_timing_1 = require("../hmr-timing");
11
11
  const sequence_node_path_mutation_1 = require("../sequence-node-path-mutation");
12
12
  const undo_stack_1 = require("../undo-stack");
13
- const log_update_1 = require("./log-updates/log-update");
14
13
  const source_file_write_queue_1 = require("./source-file-write-queue");
15
14
  const getDeletedNodeDescription = (nodeLabels) => {
16
15
  if (nodeLabels.length === 1) {
@@ -44,23 +43,15 @@ const deleteJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
44
43
  action: 'modify',
45
44
  });
46
45
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
47
- const { output, formatted, nodeLabels, logLines, nodePathRemappings } = await (0, delete_jsx_node_1.deleteJsxNodes)({
46
+ const { output, nodeLabels, logLines, nodePathRemappings } = await (0, delete_jsx_node_1.deleteJsxNodes)({
48
47
  input: fileContents,
49
48
  nodePaths: fileItems.map((item) => item.nodePath),
50
- onFormatFile: (stage) => {
51
- (0, hmr_timing_1.logHmrTiming)({
52
- detail: `file=${fileRelativeToRoot}`,
53
- logLevel,
54
- stage: `source-file-format-${stage}`,
55
- });
56
- },
57
49
  });
58
50
  return {
59
51
  absolutePath,
60
52
  fileRelativeToRoot,
61
53
  fileContents,
62
54
  output,
63
- formatted,
64
55
  nodeLabels,
65
56
  nodePathRemappings,
66
57
  logLine: Math.min(...logLines),
@@ -116,10 +107,7 @@ const deleteJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
116
107
  line: update.logLine,
117
108
  });
118
109
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${(0, source_file_write_queue_1.getCodemodTimingPrefix)(logLevel)}${renderer_1.RenderInternals.chalk.blueBright(`${locationLabel}`)} Deleted ${deletedNodeDescription}`);
119
- if (!update.formatted) {
120
- (0, log_update_1.warnAboutPrettierOnce)(logLevel);
121
- }
122
- renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `[delete-jsx-node] Wrote ${update.fileRelativeToRoot}${update.formatted ? ' (formatted)' : ''}`);
110
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `[delete-jsx-node] Wrote ${update.fileRelativeToRoot}`);
123
111
  }
124
112
  (0, undo_stack_1.printUndoHint)(logLevel);
125
113
  return {
@@ -1,7 +1,12 @@
1
- import { type ConfigUpdate, type UpdateConfigRequest, type UpdateConfigResponse } from '@remotion/studio-shared';
1
+ import { type ConfigUpdate, type StudioRuntimeConfig, type UpdateConfigRequest, type UpdateConfigResponse } from '@remotion/studio-shared';
2
2
  import type { ApiHandler } from '../api-types';
3
- export declare const updateConfigFile: ({ configContents, updates, }: {
3
+ export declare const updateConfigFile: ({ configContents, existingElementLibraryUrls, updates, }: {
4
4
  readonly configContents: string;
5
+ readonly existingElementLibraryUrls: readonly string[];
5
6
  readonly updates: ConfigUpdate[];
6
7
  }) => string;
7
- export declare const updateConfigHandler: ApiHandler<UpdateConfigRequest, UpdateConfigResponse>;
8
+ type UpdateConfigHandlerParams = Parameters<ApiHandler<UpdateConfigRequest, UpdateConfigResponse>>[0] & {
9
+ readonly getStudioRuntimeConfig: () => StudioRuntimeConfig;
10
+ };
11
+ export declare const updateConfigHandler: ({ input, configFile, getStudioRuntimeConfig, }: UpdateConfigHandlerParams) => Promise<UpdateConfigResponse>;
12
+ export {};
@@ -74,7 +74,7 @@ const validateUpdates = (updates) => {
74
74
  }
75
75
  const { setter, type } = update;
76
76
  if (typeof setter !== 'string' ||
77
- !validSetterName.test(setter) ||
77
+ (!validSetterName.test(setter) && setter !== 'addElementLibrary') ||
78
78
  !(setter in studio_shared_1.configMethodLifecycles)) {
79
79
  return `Invalid config setter: ${JSON.stringify(setter)}.`;
80
80
  }
@@ -82,6 +82,34 @@ const validateUpdates = (updates) => {
82
82
  return `The config setter ${setter} was specified more than once.`;
83
83
  }
84
84
  setters.add(setter);
85
+ if (setter === 'addElementLibrary') {
86
+ if (type !== 'set' ||
87
+ !('value' in update) ||
88
+ typeof update.value !== 'object' ||
89
+ update.value === null ||
90
+ Array.isArray(update.value)) {
91
+ return 'Config.addElementLibrary() expects an object value.';
92
+ }
93
+ const elementLibrary = update.value;
94
+ const { displayName, url } = elementLibrary;
95
+ if (typeof url !== 'string') {
96
+ return 'Config.addElementLibrary() expects an HTTP or HTTPS URL.';
97
+ }
98
+ try {
99
+ const parsedUrl = new URL(url);
100
+ if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
101
+ return 'Config.addElementLibrary() expects an HTTP or HTTPS URL.';
102
+ }
103
+ }
104
+ catch (_a) {
105
+ return 'Config.addElementLibrary() expects an HTTP or HTTPS URL.';
106
+ }
107
+ if (displayName !== undefined &&
108
+ (typeof displayName !== 'string' || displayName.trim() === '')) {
109
+ return 'Config.addElementLibrary() expects a non-empty display name.';
110
+ }
111
+ continue;
112
+ }
85
113
  if (type === 'delete') {
86
114
  continue;
87
115
  }
@@ -93,20 +121,64 @@ const validateUpdates = (updates) => {
93
121
  }
94
122
  return null;
95
123
  };
96
- const updateConfigFile = ({ configContents, updates, }) => {
124
+ const updateConfigFile = ({ configContents, existingElementLibraryUrls, updates, }) => {
97
125
  const setters = new Set(updates.map(({ setter }) => setter));
126
+ const elementLibraryUrls = new Set();
127
+ for (const url of existingElementLibraryUrls) {
128
+ try {
129
+ elementLibraryUrls.add(new URL(url).href);
130
+ }
131
+ catch (_a) {
132
+ // Runtime config only contains validated URLs.
133
+ }
134
+ }
135
+ let changed = false;
98
136
  const ast = (0, parse_ast_1.parseAst)(configContents);
99
137
  recast.types.visit(ast.program, {
100
138
  visitExpressionStatement(path) {
101
139
  const { expression } = path.node;
102
- if (expression.type === 'CallExpression' &&
103
- expression.callee.type === 'MemberExpression' &&
104
- !expression.callee.computed &&
105
- expression.callee.object.type === 'Identifier' &&
106
- expression.callee.object.name === 'Config' &&
107
- expression.callee.property.type === 'Identifier' &&
108
- setters.has(expression.callee.property.name)) {
140
+ if (expression.type !== 'CallExpression' ||
141
+ expression.callee.type !== 'MemberExpression' ||
142
+ expression.callee.computed ||
143
+ expression.callee.object.type !== 'Identifier' ||
144
+ expression.callee.object.name !== 'Config' ||
145
+ expression.callee.property.type !== 'Identifier') {
146
+ this.traverse(path);
147
+ return;
148
+ }
149
+ const setter = expression.callee.property.name;
150
+ if (setter === 'addElementLibrary') {
151
+ const argument = expression.arguments[0];
152
+ if ((argument === null || argument === void 0 ? void 0 : argument.type) === 'ObjectExpression') {
153
+ const urlProperty = argument.properties.find((property) => {
154
+ if (property.type !== 'ObjectProperty' || property.computed) {
155
+ return false;
156
+ }
157
+ return ((property.key.type === 'Identifier' &&
158
+ property.key.name === 'url') ||
159
+ (property.key.type === 'StringLiteral' &&
160
+ property.key.value === 'url'));
161
+ });
162
+ if ((urlProperty === null || urlProperty === void 0 ? void 0 : urlProperty.type) === 'ObjectProperty' &&
163
+ urlProperty.value.type === 'StringLiteral') {
164
+ try {
165
+ const parsedUrl = new URL(urlProperty.value.value);
166
+ if (parsedUrl.protocol === 'http:' ||
167
+ parsedUrl.protocol === 'https:') {
168
+ elementLibraryUrls.add(parsedUrl.href);
169
+ }
170
+ }
171
+ catch (_a) {
172
+ // Preserve invalid existing config without treating it as a match.
173
+ }
174
+ }
175
+ }
176
+ this.traverse(path);
177
+ return;
178
+ }
179
+ if (setters.has(setter)) {
109
180
  path.prune();
181
+ changed = true;
110
182
  return false;
111
183
  }
112
184
  this.traverse(path);
@@ -117,22 +189,41 @@ const updateConfigFile = ({ configContents, updates, }) => {
117
189
  if (update.type === 'delete') {
118
190
  continue;
119
191
  }
120
- const serializedValue = JSON.stringify(update.value)
192
+ const { setter, value: initialValue } = update;
193
+ let value = initialValue;
194
+ if (setter === 'addElementLibrary') {
195
+ const elementLibrary = initialValue;
196
+ const { displayName: rawDisplayName, url } = elementLibrary;
197
+ const normalizedUrl = new URL(url).href;
198
+ if (elementLibraryUrls.has(normalizedUrl)) {
199
+ continue;
200
+ }
201
+ const displayName = typeof rawDisplayName === 'string' ? rawDisplayName.trim() : null;
202
+ value =
203
+ displayName === null
204
+ ? { url: normalizedUrl }
205
+ : { url: normalizedUrl, displayName };
206
+ elementLibraryUrls.add(normalizedUrl);
207
+ }
208
+ const serializedValue = JSON.stringify(value)
121
209
  .replaceAll('\u2028', '\\u2028')
122
210
  .replaceAll('\u2029', '\\u2029');
123
- const statement = (0, parse_ast_1.parseAst)(`Config.${update.setter}(${serializedValue});`)
124
- .program.body[0];
211
+ const statement = (0, parse_ast_1.parseAst)(`Config.${setter}(${serializedValue});`).program
212
+ .body[0];
125
213
  const separator = updatedConfig.endsWith('\n') ? '' : '\n';
126
214
  updatedConfig += `${separator}${recast.prettyPrint(statement, {
127
215
  lineTerminator: '\n',
128
216
  quote: 'single',
129
217
  useTabs: true,
130
218
  }).code}\n`;
219
+ changed = true;
131
220
  }
132
- return updatedConfig;
221
+ return changed ? updatedConfig : configContents;
133
222
  };
134
223
  exports.updateConfigFile = updateConfigFile;
135
- const updateConfigHandler = async ({ input, configFile }) => {
224
+ const updateConfigHandler = async ({ input, configFile, getStudioRuntimeConfig, }) => {
225
+ var _a;
226
+ var _b;
136
227
  if (configFile === null || configFile === undefined) {
137
228
  return {
138
229
  success: false,
@@ -182,9 +273,17 @@ const updateConfigHandler = async ({ input, configFile }) => {
182
273
  }
183
274
  }
184
275
  const configContents = (0, node_fs_1.readFileSync)(configFile, 'utf8');
276
+ const updatedConfig = (0, exports.updateConfigFile)({
277
+ configContents,
278
+ existingElementLibraryUrls: (_b = (_a = getStudioRuntimeConfig().elementLibraries) === null || _a === void 0 ? void 0 : _a.map(({ url }) => url)) !== null && _b !== void 0 ? _b : [],
279
+ updates: input.updates,
280
+ });
281
+ if (updatedConfig === configContents) {
282
+ return { success: true };
283
+ }
185
284
  (0, file_watcher_1.writeFileAndNotifyFileWatchers)({
186
285
  file: configFile,
187
- content: (0, exports.updateConfigFile)({ configContents, updates: input.updates }),
286
+ content: updatedConfig,
188
287
  originatorClientId: input.clientId,
189
288
  metadata: null,
190
289
  });
@@ -85,6 +85,14 @@ const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRo
85
85
  purpose: 'set-license-key',
86
86
  target,
87
87
  });
88
+ const issuedElementLibraryTarget = target === null
89
+ ? null
90
+ : (0, element_install_state_1.issueStudioProtocolTarget)({
91
+ now,
92
+ origin: requestOrigin,
93
+ purpose: 'add-element-library',
94
+ target,
95
+ });
88
96
  response.writeHead(200, {
89
97
  'Cache-Control': 'no-store',
90
98
  'Content-Type': 'application/json',
@@ -120,6 +128,18 @@ const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRo
120
128
  lastFocusedAt: target.lastFocusedAt,
121
129
  },
122
130
  },
131
+ {
132
+ type: 'add-element-library',
133
+ target: issuedElementLibraryTarget === null ||
134
+ target === null ||
135
+ target.lastFocusedAt === null
136
+ ? null
137
+ : {
138
+ id: issuedElementLibraryTarget.id,
139
+ expiresAt: issuedElementLibraryTarget.expiresAt,
140
+ lastFocusedAt: target.lastFocusedAt,
141
+ },
142
+ },
123
143
  ],
124
144
  }));
125
145
  resolve();
@@ -0,0 +1,11 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { LiveEventsServer } from '../live-events';
3
+ type FocusStudioTab = (studioUrl: string) => void;
4
+ export declare const handleStudioProtocolElementLibrary: ({ configFile, focusStudioTab, liveEventsServer, request, response, }: {
5
+ readonly configFile: string | null;
6
+ readonly focusStudioTab: FocusStudioTab;
7
+ readonly liveEventsServer: LiveEventsServer;
8
+ readonly request: IncomingMessage;
9
+ readonly response: ServerResponse<IncomingMessage>;
10
+ }) => Promise<void>;
11
+ export {};
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleStudioProtocolElementLibrary = void 0;
4
+ const zod_1 = require("zod");
5
+ const element_install_state_1 = require("../element-install-state");
6
+ const parse_body_1 = require("../parse-body");
7
+ const origin_policy_1 = require("./origin-policy");
8
+ const protocol_response_1 = require("./protocol-response");
9
+ const studioProtocolElementLibraryRequestSchema = zod_1.z.object({
10
+ operation: zod_1.z.literal('add-element-library'),
11
+ protocol: zod_1.z.literal('remotion-studio-protocol'),
12
+ protocolVersion: zod_1.z.literal(1),
13
+ targetId: zod_1.z.string().min(1),
14
+ url: zod_1.z.string(),
15
+ displayName: zod_1.z.string().nullable(),
16
+ });
17
+ const MAX_STUDIO_PROTOCOL_ELEMENT_LIBRARY_BODY_SIZE = 16 * 1024;
18
+ const handleStudioProtocolElementLibrary = async ({ configFile, focusStudioTab, liveEventsServer, request, response, }) => {
19
+ var _a;
20
+ var _b;
21
+ (0, origin_policy_1.setStudioProtocolCorsHeaders)({ request, response });
22
+ const requestOrigin = (0, origin_policy_1.getAllowedStudioProtocolOrigin)(request.headers.origin);
23
+ if (requestOrigin === null) {
24
+ (0, protocol_response_1.writeStudioProtocolError)({
25
+ code: 'unsupported-origin',
26
+ message: 'Origin not allowed',
27
+ response,
28
+ status: 403,
29
+ });
30
+ return;
31
+ }
32
+ if (request.method !== 'POST') {
33
+ (0, protocol_response_1.writeStudioProtocolError)({
34
+ code: 'method-not-allowed',
35
+ message: 'Use POST to add an Element catalog.',
36
+ response,
37
+ status: 405,
38
+ });
39
+ return;
40
+ }
41
+ let body;
42
+ try {
43
+ body = await (0, parse_body_1.parseRequestBody)(request, {
44
+ maxBytes: MAX_STUDIO_PROTOCOL_ELEMENT_LIBRARY_BODY_SIZE,
45
+ });
46
+ }
47
+ catch (error) {
48
+ if (error instanceof parse_body_1.RequestBodyTooLargeError) {
49
+ (0, protocol_response_1.writeStudioProtocolError)({
50
+ code: 'request-too-large',
51
+ message: 'The request body is too large.',
52
+ response,
53
+ status: 413,
54
+ });
55
+ return;
56
+ }
57
+ (0, protocol_response_1.writeStudioProtocolError)({
58
+ code: 'invalid-request',
59
+ message: 'The request body is not valid JSON.',
60
+ response,
61
+ status: 400,
62
+ });
63
+ return;
64
+ }
65
+ const parsedRequest = studioProtocolElementLibraryRequestSchema.safeParse(body);
66
+ if (!parsedRequest.success) {
67
+ (0, protocol_response_1.writeStudioProtocolError)({
68
+ code: 'unsupported-protocol',
69
+ message: 'Invalid Remotion Studio Protocol request.',
70
+ response,
71
+ status: 400,
72
+ });
73
+ return;
74
+ }
75
+ let normalizedUrl;
76
+ try {
77
+ const parsedUrl = new URL(parsedRequest.data.url);
78
+ if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
79
+ throw new Error('Unsupported protocol');
80
+ }
81
+ normalizedUrl = parsedUrl.href;
82
+ }
83
+ catch (_c) {
84
+ (0, protocol_response_1.writeStudioProtocolError)({
85
+ code: 'invalid-url',
86
+ message: 'The Element catalog URL must be an absolute HTTP or HTTPS URL.',
87
+ response,
88
+ status: 400,
89
+ });
90
+ return;
91
+ }
92
+ const displayName = (_b = (_a = parsedRequest.data.displayName) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : null;
93
+ if (displayName === '') {
94
+ (0, protocol_response_1.writeStudioProtocolError)({
95
+ code: 'invalid-display-name',
96
+ message: 'The Element catalog display name must not be empty.',
97
+ response,
98
+ status: 400,
99
+ });
100
+ return;
101
+ }
102
+ const target = (0, element_install_state_1.consumeStudioProtocolTarget)({
103
+ now: Date.now(),
104
+ origin: requestOrigin,
105
+ purpose: 'add-element-library',
106
+ targetId: parsedRequest.data.targetId,
107
+ });
108
+ if (target === null) {
109
+ (0, protocol_response_1.writeStudioProtocolError)({
110
+ code: 'target-expired',
111
+ message: 'The selected Studio target is no longer available.',
112
+ response,
113
+ status: 409,
114
+ });
115
+ return;
116
+ }
117
+ if (configFile === null) {
118
+ (0, protocol_response_1.writeStudioProtocolError)({
119
+ code: 'no-config-file',
120
+ message: 'The selected Studio did not load a Remotion config file.',
121
+ response,
122
+ status: 409,
123
+ });
124
+ return;
125
+ }
126
+ const delivered = liveEventsServer.sendEventToClientId(target.clientId, {
127
+ type: 'element-library-add-request',
128
+ url: normalizedUrl,
129
+ displayName,
130
+ origin: requestOrigin,
131
+ });
132
+ if (!delivered) {
133
+ (0, protocol_response_1.writeStudioProtocolError)({
134
+ code: 'target-expired',
135
+ message: 'The selected Remotion Studio tab is no longer connected.',
136
+ response,
137
+ status: 409,
138
+ });
139
+ return;
140
+ }
141
+ focusStudioTab(target.studioUrl);
142
+ response.writeHead(200, { 'Content-Type': 'application/json' });
143
+ response.end(JSON.stringify({
144
+ protocol: 'remotion-studio-protocol',
145
+ protocolVersion: 1,
146
+ status: 'awaiting-confirmation',
147
+ }));
148
+ };
149
+ exports.handleStudioProtocolElementLibrary = handleStudioProtocolElementLibrary;
package/dist/routes.js CHANGED
@@ -53,8 +53,10 @@ const handler_1 = require("./preview-server/handler");
53
53
  const public_folder_1 = require("./preview-server/public-folder");
54
54
  const app_icon_1 = require("./preview-server/routes/app-icon");
55
55
  const open_in_editor_1 = require("./preview-server/routes/open-in-editor");
56
+ const update_config_1 = require("./preview-server/routes/update-config");
56
57
  const serve_static_1 = require("./preview-server/serve-static");
57
58
  const handle_discovery_1 = require("./preview-server/studio-protocol/handle-discovery");
59
+ const handle_element_library_1 = require("./preview-server/studio-protocol/handle-element-library");
58
60
  const handle_install_1 = require("./preview-server/studio-protocol/handle-install");
59
61
  const handle_license_key_1 = require("./preview-server/studio-protocol/handle-license-key");
60
62
  const origin_policy_1 = require("./preview-server/studio-protocol/origin-policy");
@@ -321,7 +323,8 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
321
323
  }
322
324
  if (url.pathname === '/api/studio-protocol' ||
323
325
  url.pathname === '/api/studio-protocol/install' ||
324
- url.pathname === '/api/studio-protocol/license-key') {
326
+ url.pathname === '/api/studio-protocol/license-key' ||
327
+ url.pathname === '/api/studio-protocol/element-library') {
325
328
  if (request.method === 'OPTIONS') {
326
329
  return (0, origin_policy_1.handleStudioProtocolOptions)({
327
330
  request,
@@ -348,6 +351,17 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
348
351
  response,
349
352
  });
350
353
  }
354
+ if (url.pathname === '/api/studio-protocol/element-library') {
355
+ return (0, handle_element_library_1.handleStudioProtocolElementLibrary)({
356
+ configFile,
357
+ focusStudioTab: (studioUrl) => {
358
+ (0, better_opn_1.focusBrowserTab)({ url: studioUrl }).catch(() => undefined);
359
+ },
360
+ liveEventsServer,
361
+ request,
362
+ response,
363
+ });
364
+ }
351
365
  return (0, handle_install_1.handleStudioProtocolInstall)({
352
366
  focusStudioTab: (studioUrl) => {
353
367
  (0, better_opn_1.focusBrowserTab)({ url: studioUrl }).catch(() => undefined);
@@ -357,6 +371,22 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
357
371
  response,
358
372
  });
359
373
  }
374
+ if (url.pathname === '/api/update-config') {
375
+ return (0, handler_1.handleRequest)({
376
+ remotionRoot,
377
+ entryPoint,
378
+ handler: (params) => (0, update_config_1.updateConfigHandler)({ ...params, getStudioRuntimeConfig }),
379
+ request,
380
+ response,
381
+ logLevel,
382
+ methods,
383
+ binariesDirectory,
384
+ publicDir,
385
+ configFile,
386
+ getDefaultCodingAgent,
387
+ getDefaultEditor,
388
+ });
389
+ }
360
390
  for (const [key, value] of Object.entries(api_routes_1.allApiRoutes)) {
361
391
  if (url.pathname === key) {
362
392
  return (0, handler_1.handleRequest)({
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.516",
6
+ "version": "4.0.518",
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.516",
26
+ "@remotion/studio-protocol": "4.0.518",
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.516",
35
+ "remotion": "4.0.518",
36
36
  "recast": "0.23.21",
37
- "@remotion/bundler": "4.0.516",
38
- "@remotion/renderer": "4.0.516",
39
- "@remotion/studio-codemods": "4.0.516",
40
- "@remotion/studio-shared": "4.0.516",
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",
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.516",
48
+ "@remotion/eslint-config-internal": "4.0.518",
49
49
  "eslint": "9.19.0",
50
50
  "@types/node": "20.12.14",
51
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
@@ -1,3 +0,0 @@
1
- import type { DeleteEffectKeyframeRequest, DeleteEffectKeyframeResponse } from '@remotion/studio-shared';
2
- import type { ApiHandler } from '../api-types';
3
- export declare const deleteEffectKeyframeHandler: ApiHandler<DeleteEffectKeyframeRequest, DeleteEffectKeyframeResponse>;
@@ -1,89 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteEffectKeyframeHandler = 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 parse_ast_1 = require("../../codemods/parse-ast");
8
- const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
9
- const file_watcher_1 = require("../../file-watcher");
10
- const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
11
- const undo_stack_1 = require("../undo-stack");
12
- const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
13
- const can_update_effect_props_1 = require("./can-update-effect-props");
14
- const can_update_sequence_props_1 = require("./can-update-sequence-props");
15
- const log_effect_update_1 = require("./log-updates/log-effect-update");
16
- const save_props_mutex_1 = require("./save-props-mutex");
17
- const deleteEffectKeyframeHandler = ({ input: { fileName, sequenceNodePath, effectIndex, key, frame, schema, clientId, }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
18
- renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[delete-effect-keyframe] Received request for fileName="${fileName}" effectIndex=${effectIndex} key="${key}" frame=${frame}`);
19
- const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
20
- remotionRoot,
21
- fileName,
22
- action: 'modify',
23
- });
24
- const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
25
- const { output, oldValueStrings, newValueStrings, formatted, logLine, effectCallee, } = await (0, update_keyframes_1.updateEffectKeyframes)({
26
- input: fileContents,
27
- sequenceNodePath: sequenceNodePath.nodePath,
28
- effectIndex,
29
- updates: [
30
- {
31
- key,
32
- operation: {
33
- type: 'remove',
34
- frame,
35
- },
36
- },
37
- ],
38
- });
39
- const oldValueString = oldValueStrings[0];
40
- const newValueString = newValueStrings[0];
41
- const undoPropChange = `${key} keyframe restored at frame ${frame}`;
42
- const redoPropChange = `${key} keyframe deleted at frame ${frame}`;
43
- (0, undo_stack_1.pushToUndoStack)({
44
- filePath: absolutePath,
45
- oldContents: fileContents,
46
- newContents: null,
47
- logLevel,
48
- remotionRoot,
49
- logLine,
50
- description: {
51
- undoMessage: `↩️ ${undoPropChange}`,
52
- redoMessage: `↪️ ${redoPropChange}`,
53
- },
54
- entryType: 'effect-props',
55
- suppressHmrOnFileRestore: true,
56
- });
57
- (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
58
- (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
59
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output, clientId);
60
- (0, log_effect_update_1.logEffectUpdate)({
61
- fileRelativeToRoot,
62
- line: logLine,
63
- effectName: effectCallee,
64
- propKey: key,
65
- oldValueString,
66
- newValueString,
67
- defaultValueString: null,
68
- formatted,
69
- logLevel,
70
- removedProps: [],
71
- addedProps: [],
72
- });
73
- (0, undo_stack_1.printUndoHint)(logLevel);
74
- const ast = (0, parse_ast_1.parseAst)((0, node_fs_1.readFileSync)(absolutePath, 'utf-8'));
75
- const jsx = (0, can_update_sequence_props_1.findJsxElementAtNodePath)(ast, sequenceNodePath.nodePath);
76
- if (!jsx) {
77
- return {
78
- canUpdate: false,
79
- effectIndex,
80
- reason: 'not-found',
81
- };
82
- }
83
- return (0, can_update_effect_props_1.computeEffectPropStatus)({
84
- jsx,
85
- effectIndex,
86
- keys: (0, studio_shared_1.getAllSchemaKeys)(schema),
87
- });
88
- });
89
- exports.deleteEffectKeyframeHandler = deleteEffectKeyframeHandler;
@@ -1,3 +0,0 @@
1
- import type { DeleteSequenceKeyframeRequest, DeleteSequenceKeyframeResponse } from '@remotion/studio-shared';
2
- import type { ApiHandler } from '../api-types';
3
- export declare const deleteSequenceKeyframeHandler: ApiHandler<DeleteSequenceKeyframeRequest, DeleteSequenceKeyframeResponse>;
@@ -1,82 +0,0 @@
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;
@@ -1 +0,0 @@
1
- export declare const withSavePropsLock: <T>(fn: () => Promise<T>) => Promise<T>;
@@ -1,11 +0,0 @@
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;