@remotion/studio-server 4.0.422 → 4.0.423

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.
@@ -0,0 +1,7 @@
1
+ import type { CompletedClientRender } from '@remotion/studio-shared';
2
+ export declare const getCompletedClientRenders: () => CompletedClientRender[];
3
+ export declare const addCompletedClientRender: ({ render, remotionRoot, }: {
4
+ render: CompletedClientRender;
5
+ remotionRoot: string;
6
+ }) => void;
7
+ export declare const removeCompletedClientRender: (id: string) => void;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeCompletedClientRender = exports.addCompletedClientRender = exports.getCompletedClientRenders = void 0;
4
+ const file_watcher_1 = require("./file-watcher");
5
+ const resolve_output_path_1 = require("./helpers/resolve-output-path");
6
+ const live_events_1 = require("./preview-server/live-events");
7
+ let completedClientRenders = [];
8
+ const cleanupFns = new Map();
9
+ const notifyClientsOfUpdate = () => {
10
+ (0, live_events_1.waitForLiveEventsListener)().then((listener) => {
11
+ listener.sendEventToClient({
12
+ type: 'client-renders-updated',
13
+ renders: (0, exports.getCompletedClientRenders)(),
14
+ });
15
+ });
16
+ };
17
+ const getCompletedClientRenders = () => {
18
+ return completedClientRenders;
19
+ };
20
+ exports.getCompletedClientRenders = getCompletedClientRenders;
21
+ const addCompletedClientRender = ({ render, remotionRoot, }) => {
22
+ if (completedClientRenders.some((r) => r.id === render.id)) {
23
+ return;
24
+ }
25
+ completedClientRenders.push(render);
26
+ const filePath = (0, resolve_output_path_1.resolveOutputPath)(remotionRoot, render.outName);
27
+ const { unwatch } = (0, file_watcher_1.installFileWatcher)({
28
+ file: filePath,
29
+ onChange: (type) => {
30
+ if (type === 'created' || type === 'deleted') {
31
+ updateCompletedClientRender(render.id, {
32
+ deletedOutputLocation: type === 'deleted',
33
+ });
34
+ }
35
+ },
36
+ });
37
+ cleanupFns.set(render.id, unwatch);
38
+ notifyClientsOfUpdate();
39
+ };
40
+ exports.addCompletedClientRender = addCompletedClientRender;
41
+ const removeCompletedClientRender = (id) => {
42
+ const cleanup = cleanupFns.get(id);
43
+ if (cleanup) {
44
+ cleanup();
45
+ cleanupFns.delete(id);
46
+ }
47
+ completedClientRenders = completedClientRenders.filter((r) => r.id !== id);
48
+ notifyClientsOfUpdate();
49
+ };
50
+ exports.removeCompletedClientRender = removeCompletedClientRender;
51
+ const updateCompletedClientRender = (id, updates) => {
52
+ completedClientRenders = completedClientRenders.map((r) => {
53
+ if (r.id === id) {
54
+ return { ...r, ...updates };
55
+ }
56
+ return r;
57
+ });
58
+ notifyClientsOfUpdate();
59
+ };
@@ -0,0 +1 @@
1
+ export declare const resolveOutputPath: (remotionRoot: string, filePath: string) => string;
@@ -0,0 +1,16 @@
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.resolveOutputPath = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const resolveOutputPath = (remotionRoot, filePath) => {
9
+ const absolutePath = node_path_1.default.join(remotionRoot, filePath);
10
+ const relativeToRoot = node_path_1.default.relative(remotionRoot, absolutePath);
11
+ if (relativeToRoot.startsWith('..')) {
12
+ throw new Error(`Not allowed to write to ${relativeToRoot}`);
13
+ }
14
+ return absolutePath;
15
+ };
16
+ exports.resolveOutputPath = resolveOutputPath;
package/dist/index.d.ts CHANGED
@@ -88,4 +88,10 @@ export declare const StudioServerInternals: {
88
88
  version: string;
89
89
  additionalArgs: string[];
90
90
  }) => string[];
91
+ addCompletedClientRender: ({ render, remotionRoot, }: {
92
+ render: import("@remotion/studio-shared").CompletedClientRender;
93
+ remotionRoot: string;
94
+ }) => void;
95
+ getCompletedClientRenders: () => import("@remotion/studio-shared").CompletedClientRender[];
96
+ removeCompletedClientRender: (id: string) => void;
91
97
  };
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ const studio_shared_1 = require("@remotion/studio-shared");
5
5
  var studio_shared_2 = require("@remotion/studio-shared");
6
6
  Object.defineProperty(exports, "getDefaultOutLocation", { enumerable: true, get: function () { return studio_shared_2.getDefaultOutLocation; } });
7
7
  const ansi_diff_1 = require("./ansi-diff");
8
+ const client_render_queue_1 = require("./client-render-queue");
8
9
  const duplicate_composition_1 = require("./codemods/duplicate-composition");
9
10
  const file_watcher_1 = require("./file-watcher");
10
11
  const get_latest_remotion_version_1 = require("./get-latest-remotion-version");
@@ -30,4 +31,7 @@ exports.StudioServerInternals = {
30
31
  parseAndApplyCodemod: duplicate_composition_1.parseAndApplyCodemod,
31
32
  getInstalledDependencies: get_installed_dependencies_1.getInstalledDependencies,
32
33
  getInstallCommand: install_command_1.getInstallCommand,
34
+ addCompletedClientRender: client_render_queue_1.addCompletedClientRender,
35
+ getCompletedClientRenders: client_render_queue_1.getCompletedClientRenders,
36
+ removeCompletedClientRender: client_render_queue_1.removeCompletedClientRender,
33
37
  };
package/dist/routes.js CHANGED
@@ -40,9 +40,11 @@ const fs_1 = __importStar(require("fs"));
40
40
  const node_fs_1 = require("node:fs");
41
41
  const node_path_1 = __importStar(require("node:path"));
42
42
  const node_url_1 = require("node:url");
43
+ const client_render_queue_1 = require("./client-render-queue");
43
44
  const get_file_source_1 = require("./helpers/get-file-source");
44
45
  const get_installed_installable_packages_1 = require("./helpers/get-installed-installable-packages");
45
46
  const open_in_editor_1 = require("./helpers/open-in-editor");
47
+ const resolve_output_path_1 = require("./helpers/resolve-output-path");
46
48
  const api_routes_1 = require("./preview-server/api-routes");
47
49
  const get_package_manager_1 = require("./preview-server/get-package-manager");
48
50
  const handler_1 = require("./preview-server/handler");
@@ -98,6 +100,7 @@ const handleFallback = async ({ remotionRoot, hash, response, getCurrentInputPro
98
100
  remotionRoot,
99
101
  studioServerCommand: packageManager === 'unknown' ? null : packageManager.startCommand,
100
102
  renderQueue: getRenderQueue(),
103
+ completedClientRenders: (0, client_render_queue_1.getCompletedClientRenders)(),
101
104
  numberOfAudioTags,
102
105
  publicFiles: (0, public_folder_1.getFiles)(),
103
106
  includeFavicon: true,
@@ -172,16 +175,18 @@ const handleOpenInEditor = async (remotionRoot, req, res, logLevel) => {
172
175
  }));
173
176
  }
174
177
  };
178
+ const validateSameOrigin = (req) => {
179
+ const { origin, host } = req.headers;
180
+ if (origin) {
181
+ const originUrl = new URL(origin);
182
+ if (originUrl.host !== host) {
183
+ throw new Error('Request from different origin not allowed');
184
+ }
185
+ }
186
+ };
175
187
  const handleAddAsset = ({ req, res, search, publicDir, }) => {
176
188
  try {
177
- const { origin } = req.headers;
178
- const { host } = req.headers;
179
- if (origin) {
180
- const originUrl = new URL(origin);
181
- if (originUrl.host !== host) {
182
- throw new Error('Request from different origin not allowed');
183
- }
184
- }
189
+ validateSameOrigin(req);
185
190
  const query = new node_url_1.URLSearchParams(search);
186
191
  const filePath = query.get('filePath');
187
192
  if (typeof filePath !== 'string') {
@@ -205,6 +210,65 @@ const handleAddAsset = ({ req, res, search, publicDir, }) => {
205
210
  }
206
211
  return Promise.resolve();
207
212
  };
213
+ const handleUploadOutput = ({ req, res, search, remotionRoot, }) => {
214
+ try {
215
+ validateSameOrigin(req);
216
+ const query = new node_url_1.URLSearchParams(search);
217
+ const filePath = query.get('filePath');
218
+ if (typeof filePath !== 'string') {
219
+ throw new Error('No `filePath` provided');
220
+ }
221
+ const absolutePath = (0, resolve_output_path_1.resolveOutputPath)(remotionRoot, filePath);
222
+ fs_1.default.mkdirSync(node_path_1.default.dirname(absolutePath), { recursive: true });
223
+ const writeStream = (0, fs_1.createWriteStream)(absolutePath);
224
+ writeStream.on('close', () => {
225
+ res.end(JSON.stringify({ success: true }));
226
+ });
227
+ writeStream.on('error', (err) => {
228
+ res.statusCode = 500;
229
+ res.end(JSON.stringify({ error: err.message }));
230
+ });
231
+ req.on('error', (err) => {
232
+ writeStream.destroy();
233
+ res.statusCode = 500;
234
+ res.end(JSON.stringify({ error: err.message }));
235
+ });
236
+ req.pipe(writeStream);
237
+ }
238
+ catch (err) {
239
+ res.statusCode = 500;
240
+ res.end(JSON.stringify({ error: err.message }));
241
+ }
242
+ return Promise.resolve();
243
+ };
244
+ const handleRegisterClientRender = async ({ req, res, remotionRoot, }) => {
245
+ try {
246
+ validateSameOrigin(req);
247
+ const body = (await (0, parse_body_1.parseRequestBody)(req));
248
+ (0, client_render_queue_1.addCompletedClientRender)({ render: body, remotionRoot });
249
+ res.setHeader('content-type', 'application/json');
250
+ res.writeHead(200);
251
+ res.end(JSON.stringify({ success: true }));
252
+ }
253
+ catch (err) {
254
+ res.statusCode = 500;
255
+ res.end(JSON.stringify({ error: err.message }));
256
+ }
257
+ };
258
+ const handleUnregisterClientRender = async ({ req, res, }) => {
259
+ try {
260
+ validateSameOrigin(req);
261
+ const body = (await (0, parse_body_1.parseRequestBody)(req));
262
+ (0, client_render_queue_1.removeCompletedClientRender)(body.id);
263
+ res.setHeader('content-type', 'application/json');
264
+ res.writeHead(200);
265
+ res.end(JSON.stringify({ success: true }));
266
+ }
267
+ catch (err) {
268
+ res.statusCode = 500;
269
+ res.end(JSON.stringify({ error: err.message }));
270
+ }
271
+ };
208
272
  const handleFavicon = (_, response) => {
209
273
  const filePath = node_path_1.default.join(__dirname, '..', 'web', 'favicon.png');
210
274
  const stat = (0, node_fs_1.statSync)(filePath);
@@ -259,6 +323,27 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
259
323
  publicDir,
260
324
  });
261
325
  }
326
+ if (url.pathname === '/api/upload-output') {
327
+ return handleUploadOutput({
328
+ req: request,
329
+ res: response,
330
+ search: url.search,
331
+ remotionRoot,
332
+ });
333
+ }
334
+ if (url.pathname === '/api/register-client-render') {
335
+ return handleRegisterClientRender({
336
+ req: request,
337
+ res: response,
338
+ remotionRoot,
339
+ });
340
+ }
341
+ if (url.pathname === '/api/unregister-client-render') {
342
+ return handleUnregisterClientRender({
343
+ req: request,
344
+ res: response,
345
+ });
346
+ }
262
347
  for (const [key, value] of Object.entries(api_routes_1.allApiRoutes)) {
263
348
  if (url.pathname === key) {
264
349
  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.422",
6
+ "version": "4.0.423",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "sideEffects": false,
@@ -25,11 +25,11 @@
25
25
  "dependencies": {
26
26
  "@babel/parser": "7.24.1",
27
27
  "semver": "7.5.3",
28
- "remotion": "4.0.422",
28
+ "remotion": "4.0.423",
29
29
  "recast": "0.23.11",
30
- "@remotion/bundler": "4.0.422",
31
- "@remotion/renderer": "4.0.422",
32
- "@remotion/studio-shared": "4.0.422",
30
+ "@remotion/bundler": "4.0.423",
31
+ "@remotion/renderer": "4.0.423",
32
+ "@remotion/studio-shared": "4.0.423",
33
33
  "memfs": "3.4.3",
34
34
  "source-map": "0.7.3",
35
35
  "open": "^8.4.2"
@@ -39,7 +39,7 @@
39
39
  "react": "19.2.3",
40
40
  "@babel/types": "7.24.0",
41
41
  "@types/semver": "^7.3.4",
42
- "@remotion/eslint-config-internal": "4.0.422",
42
+ "@remotion/eslint-config-internal": "4.0.423",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14"
45
45
  },