@remotion/studio-server 4.0.514 → 4.0.516

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 (26) hide show
  1. package/dist/codemods/delete-jsx-node.d.ts +2 -1
  2. package/dist/codemods/delete-jsx-node.js +10 -5
  3. package/dist/codemods/duplicate-jsx-node.d.ts +3 -3
  4. package/dist/codemods/duplicate-jsx-node.js +10 -313
  5. package/dist/helpers/open-file-for-writing-without-symlinks.d.ts +4 -0
  6. package/dist/helpers/open-file-for-writing-without-symlinks.js +52 -0
  7. package/dist/helpers/video-config-values.js +25 -6
  8. package/dist/preview-server/hmr-timing.d.ts +5 -0
  9. package/dist/preview-server/hmr-timing.js +9 -0
  10. package/dist/preview-server/hot-middleware/index.js +16 -1
  11. package/dist/preview-server/routes/can-update-sequence-props.js +49 -3
  12. package/dist/preview-server/routes/delete-effect-keyframe.d.ts +3 -0
  13. package/dist/preview-server/routes/delete-effect-keyframe.js +89 -0
  14. package/dist/preview-server/routes/delete-jsx-node.js +28 -0
  15. package/dist/preview-server/routes/delete-sequence-keyframe.d.ts +3 -0
  16. package/dist/preview-server/routes/delete-sequence-keyframe.js +82 -0
  17. package/dist/preview-server/routes/download-remote-asset.d.ts +2 -5
  18. package/dist/preview-server/routes/download-remote-asset.js +8 -70
  19. package/dist/preview-server/routes/install-dependency.js +8 -2
  20. package/dist/preview-server/routes/remotion-skills-info.js +11 -1
  21. package/dist/preview-server/routes/save-props-mutex.d.ts +1 -0
  22. package/dist/preview-server/routes/save-props-mutex.js +11 -0
  23. package/dist/preview-server/serve-static.d.ts +2 -1
  24. package/dist/preview-server/serve-static.js +31 -1
  25. package/dist/routes.js +38 -5
  26. package/package.json +9 -9
@@ -0,0 +1,89 @@
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;
@@ -7,6 +7,7 @@ const delete_jsx_node_1 = require("../../codemods/delete-jsx-node");
7
7
  const file_watcher_1 = require("../../file-watcher");
8
8
  const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
9
9
  const format_log_file_location_1 = require("../format-log-file-location");
10
+ const hmr_timing_1 = require("../hmr-timing");
10
11
  const sequence_node_path_mutation_1 = require("../sequence-node-path-mutation");
11
12
  const undo_stack_1 = require("../undo-stack");
12
13
  const log_update_1 = require("./log-updates/log-update");
@@ -21,6 +22,11 @@ const deleteJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
21
22
  return (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
22
23
  var _a;
23
24
  try {
25
+ (0, hmr_timing_1.logHmrTiming)({
26
+ detail: null,
27
+ logLevel,
28
+ stage: 'delete-jsx-node-request-start',
29
+ });
24
30
  if (nodes.length === 0) {
25
31
  throw new Error('No JSX nodes were specified for deletion');
26
32
  }
@@ -41,6 +47,13 @@ const deleteJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
41
47
  const { output, formatted, nodeLabels, logLines, nodePathRemappings } = await (0, delete_jsx_node_1.deleteJsxNodes)({
42
48
  input: fileContents,
43
49
  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
+ },
44
57
  });
45
58
  return {
46
59
  absolutePath,
@@ -53,6 +66,11 @@ const deleteJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
53
66
  logLine: Math.min(...logLines),
54
67
  };
55
68
  }));
69
+ (0, hmr_timing_1.logHmrTiming)({
70
+ detail: `files=${updates.length}`,
71
+ logLevel,
72
+ stage: 'delete-jsx-node-codemod-complete',
73
+ });
56
74
  const nodePathMutation = (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(updates.map((update) => ({
57
75
  absolutePath: update.absolutePath,
58
76
  remappings: update.nodePathRemappings,
@@ -76,12 +94,22 @@ const deleteJsxNodeHandler = ({ input: { nodes }, remotionRoot, logLevel }) => {
76
94
  nodePathRemappings: update.nodePathRemappings,
77
95
  });
78
96
  (0, undo_stack_1.suppressUndoStackInvalidation)(update.absolutePath);
97
+ (0, hmr_timing_1.logHmrTiming)({
98
+ detail: `file=${update.fileRelativeToRoot}`,
99
+ logLevel,
100
+ stage: 'source-file-write-start',
101
+ });
79
102
  (0, file_watcher_1.writeFileAndNotifyFileWatchers)({
80
103
  file: update.absolutePath,
81
104
  content: update.output,
82
105
  originatorClientId: undefined,
83
106
  metadata: { skipSequencePropsUpdate: true },
84
107
  });
108
+ (0, hmr_timing_1.logHmrTiming)({
109
+ detail: `file=${update.fileRelativeToRoot}`,
110
+ logLevel,
111
+ stage: 'source-file-write-complete',
112
+ });
85
113
  const locationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
86
114
  remotionRoot,
87
115
  absolutePath: update.absolutePath,
@@ -0,0 +1,3 @@
1
+ import type { DeleteSequenceKeyframeRequest, DeleteSequenceKeyframeResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const deleteSequenceKeyframeHandler: ApiHandler<DeleteSequenceKeyframeRequest, DeleteSequenceKeyframeResponse>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteSequenceKeyframeHandler = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const renderer_1 = require("@remotion/renderer");
6
+ const studio_shared_1 = require("@remotion/studio-shared");
7
+ const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
8
+ const file_watcher_1 = require("../../file-watcher");
9
+ const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
10
+ const undo_stack_1 = require("../undo-stack");
11
+ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
12
+ const can_update_sequence_props_1 = require("./can-update-sequence-props");
13
+ const log_update_1 = require("./log-updates/log-update");
14
+ const save_props_mutex_1 = require("./save-props-mutex");
15
+ const deleteSequenceKeyframeHandler = ({ input: { fileName, nodePath, key, frame, schema, clientId }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
16
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[delete-sequence-keyframe] Received request for fileName="${fileName}" key="${key}" frame=${frame}`);
17
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
18
+ remotionRoot,
19
+ fileName,
20
+ action: 'modify',
21
+ });
22
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
23
+ const { output, oldValueStrings, newValueStrings, formatted, logLine } = await (0, update_keyframes_1.updateSequenceKeyframes)({
24
+ input: fileContents,
25
+ nodePath: nodePath.nodePath,
26
+ updates: [
27
+ {
28
+ key,
29
+ operation: {
30
+ type: 'remove',
31
+ frame,
32
+ },
33
+ },
34
+ ],
35
+ });
36
+ const oldValueString = oldValueStrings[0];
37
+ const newValueString = newValueStrings[0];
38
+ const undoPropChange = `${key} keyframe restored at frame ${frame}`;
39
+ const redoPropChange = `${key} keyframe deleted at frame ${frame}`;
40
+ (0, undo_stack_1.pushToUndoStack)({
41
+ filePath: absolutePath,
42
+ oldContents: fileContents,
43
+ newContents: null,
44
+ logLevel,
45
+ remotionRoot,
46
+ logLine,
47
+ description: {
48
+ undoMessage: `↩️ ${undoPropChange}`,
49
+ redoMessage: `↪️ ${redoPropChange}`,
50
+ },
51
+ entryType: 'sequence-props',
52
+ suppressHmrOnFileRestore: true,
53
+ });
54
+ (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
55
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
56
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output, clientId);
57
+ (0, log_update_1.logUpdate)({
58
+ fileRelativeToRoot,
59
+ line: logLine,
60
+ key,
61
+ oldValueString,
62
+ newValueString,
63
+ defaultValueString: null,
64
+ formatted,
65
+ logLevel,
66
+ removedProps: [],
67
+ addedProps: [],
68
+ });
69
+ (0, undo_stack_1.printUndoHint)(logLevel);
70
+ const status = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
71
+ fileContents: output,
72
+ keys: (0, studio_shared_1.getAllSchemaKeys)(schema),
73
+ nodePath: nodePath.nodePath,
74
+ effects: [],
75
+ });
76
+ return {
77
+ canUpdate: true,
78
+ props: status.props,
79
+ results: [{ fileName, nodePath, props: status.props }],
80
+ };
81
+ });
82
+ exports.deleteSequenceKeyframeHandler = deleteSequenceKeyframeHandler;
@@ -1,7 +1,4 @@
1
- import { type DownloadRemoteAssetRequest, type DownloadRemoteAssetResponse, type ImageFileType } from '@remotion/studio-shared';
1
+ import { getRemoteAssetFilename, type DownloadRemoteAssetRequest, type DownloadRemoteAssetResponse } from '@remotion/studio-shared';
2
2
  import type { ApiHandler } from '../api-types';
3
- export declare const getRemoteAssetFilename: ({ fileType, url, }: {
4
- fileType: ImageFileType;
5
- url: URL;
6
- }) => string;
3
+ export { getRemoteAssetFilename };
7
4
  export declare const downloadRemoteAssetHandler: ApiHandler<DownloadRemoteAssetRequest, DownloadRemoteAssetResponse>;
@@ -9,58 +9,9 @@ const node_fs_1 = require("node:fs");
9
9
  const node_net_1 = require("node:net");
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const studio_shared_1 = require("@remotion/studio-shared");
12
+ Object.defineProperty(exports, "getRemoteAssetFilename", { enumerable: true, get: function () { return studio_shared_1.getRemoteAssetFilename; } });
12
13
  const validate_same_origin_1 = require("../validate-same-origin");
13
- const maxRemoteAssetSize = 50 * 1024 * 1024;
14
- const remoteAssetDownloadTimeout = 15000;
15
14
  const maxRemoteAssetRedirects = 5;
16
- const remoteAssetAcceptHeader = 'image/png,image/apng,image/jpeg,image/webp,image/bmp,image/gif';
17
- const extensionsForFileType = {
18
- png: ['png'],
19
- apng: ['png', 'apng'],
20
- jpeg: ['jpg', 'jpeg'],
21
- webp: ['webp'],
22
- bmp: ['bmp'],
23
- gif: ['gif'],
24
- };
25
- const safeDecodeURIComponent = (value) => {
26
- try {
27
- return decodeURIComponent(value);
28
- }
29
- catch (_a) {
30
- return value;
31
- }
32
- };
33
- const sanitizeAssetFilename = (filename) => {
34
- return Array.from(filename)
35
- .map((character) => {
36
- const charCode = character.charCodeAt(0);
37
- return charCode <= 31 || '<>:"/\\|?*'.includes(character)
38
- ? '-'
39
- : character;
40
- })
41
- .join('')
42
- .trim()
43
- .replace(/^[. ]+|[. ]+$/g, '');
44
- };
45
- const getRemoteAssetFilename = ({ fileType, url, }) => {
46
- const basename = safeDecodeURIComponent(node_path_1.default.posix.basename(url.pathname));
47
- const sanitized = sanitizeAssetFilename(basename);
48
- const filenameWithoutFallback = sanitized === '' ? 'image' : sanitized;
49
- const extensions = extensionsForFileType[fileType.type];
50
- const extension = node_path_1.default
51
- .extname(filenameWithoutFallback)
52
- .slice(1)
53
- .toLowerCase();
54
- if (extensions.includes(extension)) {
55
- return filenameWithoutFallback;
56
- }
57
- const withoutExtension = extension
58
- ? filenameWithoutFallback.slice(0, -(extension.length + 1))
59
- : filenameWithoutFallback;
60
- const safeName = withoutExtension === '' ? 'image' : withoutExtension;
61
- return `${safeName}.${extensions[0]}`;
62
- };
63
- exports.getRemoteAssetFilename = getRemoteAssetFilename;
64
15
  const isForbiddenIpv4Address = (address) => {
65
16
  const parts = address.split('.').map((part) => Number(part));
66
17
  if (parts.length !== 4 ||
@@ -131,7 +82,7 @@ const fetchRemoteAsset = async ({ signal, url, }) => {
131
82
  await ensureRemoteUrlIsAllowed(currentUrl);
132
83
  const response = await fetch(currentUrl, {
133
84
  headers: {
134
- accept: remoteAssetAcceptHeader,
85
+ accept: studio_shared_1.remoteAssetAcceptHeader,
135
86
  },
136
87
  redirect: 'manual',
137
88
  signal,
@@ -153,13 +104,13 @@ const fetchRemoteAsset = async ({ signal, url, }) => {
153
104
  };
154
105
  const readRemoteAsset = async ({ response, abort, }) => {
155
106
  const contentLength = response.headers.get('content-length');
156
- if (contentLength !== null && Number(contentLength) > maxRemoteAssetSize) {
107
+ if (contentLength !== null && Number(contentLength) > studio_shared_1.maxRemoteAssetSize) {
157
108
  abort();
158
109
  throw new Error('Remote asset exceeds the 50MB size limit');
159
110
  }
160
111
  if (!response.body) {
161
112
  const buffer = await response.arrayBuffer();
162
- if (buffer.byteLength > maxRemoteAssetSize) {
113
+ if (buffer.byteLength > studio_shared_1.maxRemoteAssetSize) {
163
114
  throw new Error('Remote asset exceeds the 50MB size limit');
164
115
  }
165
116
  return new Uint8Array(buffer);
@@ -176,7 +127,7 @@ const readRemoteAsset = async ({ response, abort, }) => {
176
127
  continue;
177
128
  }
178
129
  size += value.byteLength;
179
- if (size > maxRemoteAssetSize) {
130
+ if (size > studio_shared_1.maxRemoteAssetSize) {
180
131
  abort();
181
132
  await reader.cancel();
182
133
  throw new Error('Remote asset exceeds the 50MB size limit');
@@ -203,7 +154,7 @@ const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
203
154
  const controller = new AbortController();
204
155
  const timeout = setTimeout(() => {
205
156
  controller.abort();
206
- }, remoteAssetDownloadTimeout);
157
+ }, studio_shared_1.remoteAssetDownloadTimeout);
207
158
  let contents;
208
159
  try {
209
160
  const response = await fetchRemoteAsset({
@@ -231,7 +182,7 @@ const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
231
182
  if (!(0, studio_shared_1.isImageFileType)(fileType)) {
232
183
  throw new Error('Remote asset is not a supported image');
233
184
  }
234
- const assetPath = (0, exports.getRemoteAssetFilename)({ fileType, url });
185
+ const assetPath = (0, studio_shared_1.getRemoteAssetFilename)({ fileType, url });
235
186
  const absolutePath = node_path_1.default.join(publicDir, assetPath);
236
187
  const relativeToPublicDir = node_path_1.default.relative(publicDir, absolutePath);
237
188
  if (relativeToPublicDir.startsWith('..') ||
@@ -252,20 +203,7 @@ const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
252
203
  (0, node_fs_1.mkdirSync)(node_path_1.default.dirname(absolutePath), { recursive: true });
253
204
  (0, node_fs_1.writeFileSync)(absolutePath, contents);
254
205
  }
255
- const element = {
256
- type: 'asset',
257
- assetType: fileType.type === 'gif'
258
- ? 'gif'
259
- : fileType.type === 'apng' ||
260
- (fileType.type === 'webp' && fileType.animated)
261
- ? 'animated-image'
262
- : 'image',
263
- src: assetPath,
264
- srcType: 'static',
265
- dimensions: fileType.dimensions,
266
- durationInFrames: null,
267
- position: null,
268
- };
206
+ const element = (0, studio_shared_1.getRemoteAssetElement)({ assetPath, fileType });
269
207
  return {
270
208
  assetPath,
271
209
  sizeInBytes: contents.byteLength,
@@ -46,13 +46,19 @@ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { dependenc
46
46
  manager: manager.manager,
47
47
  packages: packagesWithVersions,
48
48
  version: '',
49
- additionalArgs: [],
49
+ additionalArgs: manager.manager === 'yarn' ? [] : ['--ignore-scripts'],
50
50
  });
51
51
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray(`╭─ ${manager.manager} ${command.join(' ')}`));
52
52
  const time = Date.now();
53
53
  try {
54
54
  await new Promise((resolve, reject) => {
55
- const cmd = (0, node_child_process_1.spawn)(manager.manager, command, (0, package_manager_spawn_options_1.getPackageManagerSpawnOptions)());
55
+ const cmd = (0, node_child_process_1.spawn)(manager.manager, command, {
56
+ ...(0, package_manager_spawn_options_1.getPackageManagerSpawnOptions)(),
57
+ env: {
58
+ ...process.env,
59
+ YARN_ENABLE_SCRIPTS: 'false',
60
+ },
61
+ });
56
62
  cmd.on('error', reject);
57
63
  cmd.stdout.on('data', (d) => d
58
64
  .toString()
@@ -8,15 +8,25 @@ const node_fs_1 = require("node:fs");
8
8
  const node_os_1 = require("node:os");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const detect_outdated_remotion_skills_1 = require("../../detect-outdated-remotion-skills");
11
+ const remotion_skill_names_1 = require("../../remotion-skill-names");
11
12
  const getRemotionSkillsInfo = ({ remotionRoot, homeDirectory = (0, node_os_1.homedir)(), }) => {
12
13
  const skillsDirectories = (0, detect_outdated_remotion_skills_1.getRemotionSkillsDirectories)({
13
14
  cwd: remotionRoot,
14
15
  homeDirectory,
15
16
  });
16
- const isSkillAvailable = (skillName) => Object.values(skillsDirectories).some((skillsDirectory) => (0, node_fs_1.existsSync)(node_path_1.default.join(skillsDirectory, skillName, 'SKILL.md')));
17
+ const skills = remotion_skill_names_1.remotionSkillNames.map((name) => ({
18
+ name,
19
+ installedInProject: (0, node_fs_1.existsSync)(node_path_1.default.join(skillsDirectories.project, name, 'SKILL.md')),
20
+ installedGlobally: (0, node_fs_1.existsSync)(node_path_1.default.join(skillsDirectories.global, name, 'SKILL.md')),
21
+ }));
22
+ const isSkillAvailable = (skillName) => {
23
+ const skill = skills.find(({ name }) => name === skillName);
24
+ return Boolean((skill === null || skill === void 0 ? void 0 : skill.installedInProject) || (skill === null || skill === void 0 ? void 0 : skill.installedGlobally));
25
+ };
17
26
  return {
18
27
  remotionUpgradeSkillAvailable: isSkillAvailable('remotion-upgrade'),
19
28
  remotionInteractivitySkillAvailable: isSkillAvailable('remotion-interactivity'),
29
+ skills,
20
30
  };
21
31
  };
22
32
  exports.getRemotionSkillsInfo = getRemotionSkillsInfo;
@@ -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;
@@ -6,10 +6,11 @@
6
6
  * MIT Licensed
7
7
  */
8
8
  import type { IncomingMessage, ServerResponse } from 'node:http';
9
- export declare const serveStatic: ({ root, path, req, res, allowOutsidePublicFolder, }: {
9
+ export declare const serveStatic: ({ root, path, req, res, allowOutsidePublicFolder, allowRemotionConvertCors, }: {
10
10
  root: string;
11
11
  path: string;
12
12
  req: IncomingMessage;
13
13
  res: ServerResponse<IncomingMessage>;
14
14
  allowOutsidePublicFolder: boolean;
15
+ allowRemotionConvertCors: boolean;
15
16
  }) => Promise<void>;
@@ -12,7 +12,36 @@ const node_fs_1 = require("node:fs");
12
12
  const renderer_1 = require("@remotion/renderer");
13
13
  const middleware_1 = require("./dev-middleware/middleware");
14
14
  const range_parser_1 = require("./dev-middleware/range-parser");
15
- const serveStatic = async function ({ root, path, req, res, allowOutsidePublicFolder, }) {
15
+ const remotionConvertOrigins = [
16
+ 'https://remotion.dev',
17
+ 'https://www.remotion.dev',
18
+ 'https://convert.remotion.dev',
19
+ ];
20
+ const applyRemotionConvertCorsHeaders = ({ req, res, }) => {
21
+ const { origin } = req.headers;
22
+ if (!origin || !remotionConvertOrigins.includes(origin)) {
23
+ return false;
24
+ }
25
+ res.setHeader('Access-Control-Allow-Origin', origin);
26
+ res.setHeader('Vary', 'Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network');
27
+ res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
28
+ res.setHeader('Access-Control-Allow-Headers', 'Range, Content-Type');
29
+ res.setHeader('Access-Control-Expose-Headers', 'Accept-Ranges, Content-Length, Content-Range');
30
+ res.setHeader('Access-Control-Max-Age', '600');
31
+ if (req.headers['access-control-request-private-network']) {
32
+ res.setHeader('Access-Control-Allow-Private-Network', 'true');
33
+ }
34
+ return true;
35
+ };
36
+ const serveStatic = async function ({ root, path, req, res, allowOutsidePublicFolder, allowRemotionConvertCors, }) {
37
+ const remotionConvertCorsAllowed = allowRemotionConvertCors
38
+ ? applyRemotionConvertCorsHeaders({ req, res })
39
+ : false;
40
+ if (req.method === 'OPTIONS' && remotionConvertCorsAllowed) {
41
+ res.statusCode = 204;
42
+ res.end();
43
+ return;
44
+ }
16
45
  if (req.method !== 'GET' && req.method !== 'HEAD') {
17
46
  // method not allowed
18
47
  res.statusCode = 405;
@@ -43,6 +72,7 @@ const serveStatic = async function ({ root, path, req, res, allowOutsidePublicFo
43
72
  req,
44
73
  res,
45
74
  allowOutsidePublicFolder: true,
75
+ allowRemotionConvertCors,
46
76
  });
47
77
  }
48
78
  const isDirectory = lstat.isDirectory();
package/dist/routes.js CHANGED
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleRoutes = void 0;
37
- const fs_1 = __importStar(require("fs"));
38
37
  const node_fs_1 = require("node:fs");
39
38
  const node_path_1 = __importStar(require("node:path"));
40
39
  const node_url_1 = require("node:url");
@@ -45,6 +44,7 @@ const better_opn_1 = require("./better-opn");
45
44
  const client_render_queue_1 = require("./client-render-queue");
46
45
  const get_file_source_1 = require("./helpers/get-file-source");
47
46
  const get_installed_installable_packages_1 = require("./helpers/get-installed-installable-packages");
47
+ const open_file_for_writing_without_symlinks_1 = require("./helpers/open-file-for-writing-without-symlinks");
48
48
  const resolve_output_path_1 = require("./helpers/resolve-output-path");
49
49
  const api_routes_1 = require("./preview-server/api-routes");
50
50
  const get_package_manager_1 = require("./preview-server/get-package-manager");
@@ -61,6 +61,21 @@ const origin_policy_1 = require("./preview-server/studio-protocol/origin-policy"
61
61
  const validate_same_origin_1 = require("./preview-server/validate-same-origin");
62
62
  const watch_ignore_next_change_1 = require("./preview-server/watch-ignore-next-change");
63
63
  const loggedStaticFileHints = new Set();
64
+ const clientRenderOutputExtensions = new Set([
65
+ 'aac',
66
+ 'flac',
67
+ 'jpeg',
68
+ 'jpg',
69
+ 'mkv',
70
+ 'mov',
71
+ 'mp3',
72
+ 'mp4',
73
+ 'ogg',
74
+ 'png',
75
+ 'wav',
76
+ 'webm',
77
+ 'webp',
78
+ ]);
64
79
  const static404 = (response) => {
65
80
  response.writeHead(404);
66
81
  response.end('The static/ prefix has been changed, this URL is no longer valid.');
@@ -188,8 +203,14 @@ const handleAddAsset = ({ req, res, search, publicDir, }) => {
188
203
  if (relativeToPublicDir.startsWith('..')) {
189
204
  throw new Error(`Not allowed to write to ${relativeToPublicDir}`);
190
205
  }
191
- fs_1.default.mkdirSync(node_path_1.default.dirname(absolutePath), { recursive: true });
192
- const writeStream = (0, fs_1.createWriteStream)(absolutePath);
206
+ const fileDescriptor = (0, open_file_for_writing_without_symlinks_1.openFileForWritingWithoutSymlinks)({
207
+ rootDirectory: publicDir,
208
+ absolutePath,
209
+ });
210
+ const writeStream = (0, node_fs_1.createWriteStream)(absolutePath, {
211
+ fd: fileDescriptor,
212
+ autoClose: true,
213
+ });
193
214
  writeStream.on('close', () => {
194
215
  res.end(JSON.stringify({ success: true }));
195
216
  });
@@ -210,8 +231,18 @@ const handleUploadOutput = ({ req, res, search, remotionRoot, }) => {
210
231
  throw new Error('No `filePath` provided');
211
232
  }
212
233
  const absolutePath = (0, resolve_output_path_1.resolveOutputPath)(remotionRoot, filePath);
213
- fs_1.default.mkdirSync(node_path_1.default.dirname(absolutePath), { recursive: true });
214
- const writeStream = (0, fs_1.createWriteStream)(absolutePath);
234
+ const extension = node_path_1.default.extname(absolutePath).slice(1).toLowerCase();
235
+ if (!clientRenderOutputExtensions.has(extension)) {
236
+ throw new Error(`Not allowed to upload a .${extension || 'unknown'} file`);
237
+ }
238
+ const fileDescriptor = (0, open_file_for_writing_without_symlinks_1.openFileForWritingWithoutSymlinks)({
239
+ rootDirectory: remotionRoot,
240
+ absolutePath,
241
+ });
242
+ const writeStream = (0, node_fs_1.createWriteStream)(absolutePath, {
243
+ fd: fileDescriptor,
244
+ autoClose: true,
245
+ });
215
246
  writeStream.on('close', () => {
216
247
  res.end(JSON.stringify({ success: true }));
217
248
  });
@@ -365,6 +396,7 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
365
396
  req: request,
366
397
  res: response,
367
398
  allowOutsidePublicFolder: false,
399
+ allowRemotionConvertCors: true,
368
400
  });
369
401
  }
370
402
  if (url.pathname.startsWith(staticHashPrefix)) {
@@ -379,6 +411,7 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
379
411
  req: request,
380
412
  res: response,
381
413
  allowOutsidePublicFolder: false,
414
+ allowRemotionConvertCors: false,
382
415
  });
383
416
  }
384
417
  if (url.pathname.startsWith(outputHashPrefix)) {
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.514",
6
+ "version": "4.0.516",
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.514",
26
+ "@remotion/studio-protocol": "4.0.516",
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.514",
36
- "recast": "0.23.11",
37
- "@remotion/bundler": "4.0.514",
38
- "@remotion/renderer": "4.0.514",
39
- "@remotion/studio-codemods": "4.0.514",
40
- "@remotion/studio-shared": "4.0.514",
35
+ "remotion": "4.0.516",
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",
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.514",
48
+ "@remotion/eslint-config-internal": "4.0.516",
49
49
  "eslint": "9.19.0",
50
50
  "@types/node": "20.12.14",
51
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"