@remotion/studio-server 4.0.420 → 4.0.422

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,13 @@
1
+ type RemotionDetectionResult = {
2
+ type: 'match';
3
+ } | {
4
+ type: 'mismatch';
5
+ } | {
6
+ type: 'not-remotion';
7
+ };
8
+ export declare const detectRemotionServer: ({ port, cwd, hostname, }: {
9
+ port: number;
10
+ cwd: string;
11
+ hostname: string;
12
+ }) => Promise<RemotionDetectionResult>;
13
+ export {};
@@ -0,0 +1,51 @@
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.detectRemotionServer = void 0;
7
+ const http_1 = __importDefault(require("http"));
8
+ const detectRemotionServer = ({ port, cwd, hostname, }) => {
9
+ return new Promise((resolve) => {
10
+ const req = http_1.default.get({
11
+ hostname,
12
+ port,
13
+ path: '/__remotion_config',
14
+ timeout: 1000,
15
+ }, (res) => {
16
+ if (res.statusCode !== 200) {
17
+ res.resume();
18
+ return resolve({ type: 'not-remotion' });
19
+ }
20
+ let data = '';
21
+ res.on('data', (chunk) => {
22
+ data += chunk;
23
+ });
24
+ res.on('error', () => {
25
+ resolve({ type: 'not-remotion' });
26
+ });
27
+ res.on('end', () => {
28
+ try {
29
+ const json = JSON.parse(data);
30
+ if (json.isRemotion !== true) {
31
+ return resolve({ type: 'not-remotion' });
32
+ }
33
+ // Normalize paths for comparison to avoid issues with different separators or casing on Windows
34
+ const normalize = (p) => p.replace(/\\/g, '/').toLowerCase();
35
+ if (normalize(json.cwd) === normalize(cwd)) {
36
+ return resolve({ type: 'match' });
37
+ }
38
+ return resolve({ type: 'mismatch' });
39
+ }
40
+ catch (_a) {
41
+ resolve({ type: 'not-remotion' });
42
+ }
43
+ });
44
+ });
45
+ req.on('error', () => resolve({ type: 'not-remotion' }));
46
+ req.on('timeout', () => {
47
+ req.destroy();
48
+ });
49
+ });
50
+ };
51
+ exports.detectRemotionServer = detectRemotionServer;
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { ApiRoutes, CopyStillToClipboardRequest, getDefaultOutLocation, OpenInFi
2
2
  export type { AggregateRenderProgress, BundlingState, CopyingState, DownloadProgress, HotMiddlewareOptions, JobProgressCallback, ModuleMap, PackageManager, ProjectInfo, RenderingProgressInput, RenderJob, RenderJobWithCleanup, RequiredChromiumOptions, StitchingProgressInput, UiOpenGlOptions, } from '@remotion/studio-shared';
3
3
  import { AnsiDiff } from './ansi-diff';
4
4
  export declare const StudioServerInternals: {
5
- startStudio: ({ browserArgs, browserFlag, configValueShouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, parsedCliOpen, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, enableCrossSiteIsolation, askAIEnabled, }: {
5
+ startStudio: ({ browserArgs, browserFlag, configValueShouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, parsedCliOpen, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, enableCrossSiteIsolation, askAIEnabled, forceNew, }: {
6
6
  browserArgs: string;
7
7
  browserFlag: string;
8
8
  logLevel: import("@remotion/renderer").LogLevel;
@@ -31,7 +31,8 @@ export declare const StudioServerInternals: {
31
31
  binariesDirectory: string | null;
32
32
  forceIPv4: boolean;
33
33
  askAIEnabled: boolean;
34
- }) => Promise<void>;
34
+ forceNew: boolean;
35
+ }) => Promise<import("./start-studio").StartStudioResult>;
35
36
  getRemotionVersion: () => any;
36
37
  waitForLiveEventsListener: () => Promise<import("./preview-server/live-events").LiveEventsServer>;
37
38
  lockFilePaths: {
@@ -40,7 +41,12 @@ export declare const StudioServerInternals: {
40
41
  installCommand: string;
41
42
  startCommand: string;
42
43
  }[];
43
- getPackageManager: (remotionRoot: string, packageManager: string | undefined, dirUp: number) => {
44
+ getPackageManager: ({ remotionRoot, packageManager, dirUp, logLevel, }: {
45
+ remotionRoot: string;
46
+ packageManager: string | undefined;
47
+ dirUp: number;
48
+ logLevel: import("@remotion/renderer").LogLevel;
49
+ }) => {
44
50
  manager: import("@remotion/studio-shared").PackageManager;
45
51
  path: string;
46
52
  installCommand: string;
@@ -70,11 +76,6 @@ export declare const StudioServerInternals: {
70
76
  newContents: string;
71
77
  changesMade: import("./codemods/recast-mods").Change[];
72
78
  };
73
- openBrowser: ({ url, browserFlag, browserArgs, }: {
74
- url: string;
75
- browserFlag: string | undefined;
76
- browserArgs: string | undefined;
77
- }) => Promise<boolean | import("child_process").ChildProcess>;
78
79
  getInstalledDependencies: (remotionRoot: string) => {
79
80
  dependencies: string[];
80
81
  devDependencies: string[];
package/dist/index.js CHANGED
@@ -5,7 +5,6 @@ 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 better_opn_1 = require("./better-opn");
9
8
  const duplicate_composition_1 = require("./codemods/duplicate-composition");
10
9
  const file_watcher_1 = require("./file-watcher");
11
10
  const get_latest_remotion_version_1 = require("./get-latest-remotion-version");
@@ -29,7 +28,6 @@ exports.StudioServerInternals = {
29
28
  AnsiDiff: ansi_diff_1.AnsiDiff,
30
29
  formatBytes: studio_shared_1.formatBytes,
31
30
  parseAndApplyCodemod: duplicate_composition_1.parseAndApplyCodemod,
32
- openBrowser: better_opn_1.openBrowser,
33
31
  getInstalledDependencies: get_installed_dependencies_1.getInstalledDependencies,
34
32
  getInstallCommand: install_command_1.getInstallCommand,
35
33
  };
@@ -0,0 +1,11 @@
1
+ import type { LogLevel } from '@remotion/renderer';
2
+ export declare const maybeOpenBrowser: ({ browserArgs, browserFlag, configValueShouldOpenBrowser, parsedCliOpen, url, logLevel, }: {
3
+ browserArgs: string;
4
+ browserFlag: string;
5
+ configValueShouldOpenBrowser: boolean;
6
+ parsedCliOpen: boolean;
7
+ url: string;
8
+ logLevel: LogLevel;
9
+ }) => Promise<{
10
+ didOpenBrowser: boolean;
11
+ }>;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.maybeOpenBrowser = void 0;
4
+ const renderer_1 = require("@remotion/renderer");
5
+ const better_opn_1 = require("./better-opn");
6
+ const getShouldOpenBrowser = ({ configValueShouldOpenBrowser, parsedCliOpen, }) => {
7
+ var _a;
8
+ if (parsedCliOpen === false) {
9
+ return {
10
+ shouldOpenBrowser: false,
11
+ reasonForBrowserDecision: '--no-open specified',
12
+ };
13
+ }
14
+ if (((_a = process.env.BROWSER) !== null && _a !== void 0 ? _a : '').toLowerCase() === 'none') {
15
+ return {
16
+ shouldOpenBrowser: false,
17
+ reasonForBrowserDecision: 'env BROWSER=none was set',
18
+ };
19
+ }
20
+ if (configValueShouldOpenBrowser === false) {
21
+ return { shouldOpenBrowser: false, reasonForBrowserDecision: 'Config file' };
22
+ }
23
+ return { shouldOpenBrowser: true, reasonForBrowserDecision: 'default' };
24
+ };
25
+ const maybeOpenBrowser = async ({ browserArgs, browserFlag, configValueShouldOpenBrowser, parsedCliOpen, url, logLevel, }) => {
26
+ const { reasonForBrowserDecision, shouldOpenBrowser } = getShouldOpenBrowser({
27
+ configValueShouldOpenBrowser,
28
+ parsedCliOpen,
29
+ });
30
+ if (shouldOpenBrowser) {
31
+ await (0, better_opn_1.openBrowser)({
32
+ url,
33
+ browserArgs,
34
+ browserFlag,
35
+ });
36
+ }
37
+ else {
38
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `Not opening browser, reason: ${reasonForBrowserDecision}`);
39
+ }
40
+ return { didOpenBrowser: shouldOpenBrowser };
41
+ };
42
+ exports.maybeOpenBrowser = maybeOpenBrowser;
@@ -0,0 +1,37 @@
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
+ const bun_test_1 = require("bun:test");
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_os_1 = __importDefault(require("node:os"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const get_package_manager_1 = require("../get-package-manager");
11
+ (0, bun_test_1.describe)('getPackageManager multiple lockfiles', () => {
12
+ let tempDir;
13
+ (0, bun_test_1.beforeEach)(() => {
14
+ tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'remotion-test-'));
15
+ });
16
+ (0, bun_test_1.afterEach)(() => {
17
+ node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
18
+ });
19
+ (0, bun_test_1.test)('should not throw error when multiple lockfiles are detected', () => {
20
+ node_fs_1.default.writeFileSync(node_path_1.default.join(tempDir, 'package-lock.json'), '{}');
21
+ node_fs_1.default.writeFileSync(node_path_1.default.join(tempDir, 'bun.lock'), '');
22
+ // Should not throw
23
+ const manager = (0, get_package_manager_1.getPackageManager)(tempDir, undefined, 0);
24
+ // Should return one of them (usually the first one in the list, which is npm)
25
+ (0, bun_test_1.expect)(manager).not.toBe('unknown');
26
+ if (typeof manager !== 'string') {
27
+ (0, bun_test_1.expect)(['npm', 'bun']).toContain(manager.manager);
28
+ }
29
+ });
30
+ (0, bun_test_1.test)('should return npm if only package-lock.json exists', () => {
31
+ node_fs_1.default.writeFileSync(node_path_1.default.join(tempDir, 'package-lock.json'), '{}');
32
+ const manager = (0, get_package_manager_1.getPackageManager)(tempDir, undefined, 0);
33
+ if (typeof manager !== 'string') {
34
+ (0, bun_test_1.expect)(manager.manager).toBe('npm');
35
+ }
36
+ });
37
+ });
@@ -1,3 +1,4 @@
1
+ import type { LogLevel } from '@remotion/renderer';
1
2
  import type { PackageManager } from '@remotion/studio-shared';
2
3
  type LockfilePath = {
3
4
  manager: PackageManager;
@@ -6,5 +7,10 @@ type LockfilePath = {
6
7
  startCommand: string;
7
8
  };
8
9
  export declare const lockFilePaths: LockfilePath[];
9
- export declare const getPackageManager: (remotionRoot: string, packageManager: string | undefined, dirUp: number) => LockfilePath | "unknown";
10
+ export declare const getPackageManager: ({ remotionRoot, packageManager, dirUp, logLevel, }: {
11
+ remotionRoot: string;
12
+ packageManager: string | undefined;
13
+ dirUp: number;
14
+ logLevel: LogLevel;
15
+ }) => LockfilePath | "unknown";
10
16
  export {};
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getPackageManager = exports.lockFilePaths = void 0;
7
+ const renderer_1 = require("@remotion/renderer");
7
8
  const node_fs_1 = __importDefault(require("node:fs"));
8
9
  const node_path_1 = __importDefault(require("node:path"));
9
10
  exports.lockFilePaths = [
@@ -38,7 +39,8 @@ exports.lockFilePaths = [
38
39
  startCommand: 'bunx remotion studio',
39
40
  },
40
41
  ];
41
- const getPackageManager = (remotionRoot, packageManager, dirUp) => {
42
+ let warnedAboutMultipleLockfiles = false;
43
+ const getPackageManager = ({ remotionRoot, packageManager, dirUp, logLevel, }) => {
42
44
  if (packageManager) {
43
45
  const manager = exports.lockFilePaths.find((p) => p.manager === packageManager);
44
46
  if (!manager) {
@@ -53,18 +55,21 @@ const getPackageManager = (remotionRoot, packageManager, dirUp) => {
53
55
  return 'unknown';
54
56
  }
55
57
  if (existingPkgManagers.length === 0) {
56
- return (0, exports.getPackageManager)(remotionRoot, packageManager, dirUp + 1);
58
+ return (0, exports.getPackageManager)({
59
+ remotionRoot,
60
+ packageManager,
61
+ dirUp: dirUp + 1,
62
+ logLevel,
63
+ });
57
64
  }
58
- if (existingPkgManagers.length > 1) {
59
- const error = [
60
- `Found multiple lockfiles:`,
61
- ...existingPkgManagers.map((m) => {
62
- return `- ${m.path}`;
63
- }),
64
- '',
65
- 'This can lead to bugs, delete all but one of these files and run this command again.',
66
- ].join('\n');
67
- throw new Error(error);
65
+ if (existingPkgManagers.length > 1 && !warnedAboutMultipleLockfiles) {
66
+ warnedAboutMultipleLockfiles = true;
67
+ renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, '⚠️ Multiple lockfiles detected:');
68
+ for (const pkgManager of existingPkgManagers) {
69
+ renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, ` - ${pkgManager.path}`);
70
+ }
71
+ renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, '');
72
+ renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, 'This can lead to bugs, delete all but one of these files.');
68
73
  }
69
74
  return existingPkgManagers[0];
70
75
  };
@@ -21,7 +21,12 @@ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { packageNa
21
21
  return Promise.reject(new Error(`Package ${packageName} is not allowed to be installed.`));
22
22
  }
23
23
  }
24
- const manager = (0, get_package_manager_1.getPackageManager)(remotionRoot, undefined, 0);
24
+ const manager = (0, get_package_manager_1.getPackageManager)({
25
+ remotionRoot,
26
+ packageManager: undefined,
27
+ dirUp: 0,
28
+ logLevel,
29
+ });
25
30
  if (manager === 'unknown') {
26
31
  throw new Error(`No lockfile was found in your project (one of ${get_package_manager_1.lockFilePaths
27
32
  .map((p) => p.path)
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleUpdate = void 0;
4
4
  const update_available_1 = require("../update-available");
5
- const handleUpdate = async ({ remotionRoot }) => {
6
- const data = await (0, update_available_1.isUpdateAvailableWithTimeout)(remotionRoot);
5
+ const handleUpdate = async ({ remotionRoot, logLevel }) => {
6
+ const data = await (0, update_available_1.isUpdateAvailableWithTimeout)(remotionRoot, logLevel);
7
7
  return data;
8
8
  };
9
9
  exports.handleUpdate = handleUpdate;
@@ -3,6 +3,15 @@ import type { LogLevel } from '@remotion/renderer';
3
3
  import type { GitSource, RenderDefaults, RenderJob } from '@remotion/studio-shared';
4
4
  import type { QueueMethods } from './api-types';
5
5
  import type { LiveEventsServer } from './live-events';
6
+ export type StartServerResult = {
7
+ type: 'started';
8
+ port: number;
9
+ liveEventsServer: LiveEventsServer;
10
+ close: () => Promise<void>;
11
+ } | {
12
+ type: 'already-running';
13
+ port: number;
14
+ };
6
15
  export declare const startServer: (options: {
7
16
  entry: string;
8
17
  userDefinedComponent: string;
@@ -32,8 +41,5 @@ export declare const startServer: (options: {
32
41
  audioLatencyHint: AudioContextLatencyCategory | null;
33
42
  enableCrossSiteIsolation: boolean;
34
43
  askAIEnabled: boolean;
35
- }) => Promise<{
36
- port: number;
37
- liveEventsServer: LiveEventsServer;
38
- close: () => Promise<void>;
39
- }>;
44
+ forceNew: boolean;
45
+ }) => Promise<StartServerResult>;
@@ -7,19 +7,32 @@ exports.startServer = void 0;
7
7
  const bundler_1 = require("@remotion/bundler");
8
8
  const renderer_1 = require("@remotion/renderer");
9
9
  const node_http_1 = __importDefault(require("node:http"));
10
+ const detect_remotion_server_1 = require("../detect-remotion-server");
10
11
  const routes_1 = require("../routes");
11
12
  const dev_middleware_1 = require("./dev-middleware");
12
13
  const hot_middleware_1 = require("./hot-middleware");
13
14
  const live_events_1 = require("./live-events");
14
15
  const startServer = async (options) => {
15
16
  var _a, _b, _c;
17
+ 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;
18
+ const portConfig = renderer_1.RenderInternals.getPortConfig(options.forceIPv4);
19
+ const onPortUnavailable = options.forceNew
20
+ ? undefined
21
+ : async (port) => {
22
+ const detection = await (0, detect_remotion_server_1.detectRemotionServer)({
23
+ port,
24
+ cwd: options.remotionRoot,
25
+ hostname: portConfig.hostsToTry[0],
26
+ });
27
+ return detection.type === 'match' ? 'stop' : 'continue';
28
+ };
16
29
  const [, config] = await bundler_1.BundlerInternals.webpackConfig({
17
30
  entry: options.entry,
18
31
  userDefinedComponent: options.userDefinedComponent,
19
32
  outDir: null,
20
33
  environment: 'development',
21
34
  webpackOverride: options === null || options === void 0 ? void 0 : options.webpackOverride,
22
- maxTimelineTracks: (_a = options === null || options === void 0 ? void 0 : options.maxTimelineTracks) !== null && _a !== void 0 ? _a : null,
35
+ maxTimelineTracks: (_c = options === null || options === void 0 ? void 0 : options.maxTimelineTracks) !== null && _c !== void 0 ? _c : null,
23
36
  remotionRoot: options.remotionRoot,
24
37
  keyboardShortcutsEnabled: options.keyboardShortcutsEnabled,
25
38
  experimentalClientSideRenderingEnabled: options.experimentalClientSideRenderingEnabled,
@@ -86,35 +99,47 @@ const startServer = async (options) => {
86
99
  }
87
100
  });
88
101
  });
89
- const desiredPort = (_c = (_b = options === null || options === void 0 ? void 0 : options.port) !== null && _b !== void 0 ? _b : (process.env.PORT ? Number(process.env.PORT) : undefined)) !== null && _c !== void 0 ? _c : undefined;
90
102
  const maxTries = 5;
91
- const portConfig = renderer_1.RenderInternals.getPortConfig(options.forceIPv4);
92
103
  for (let i = 0; i < maxTries; i++) {
93
104
  try {
105
+ const { port, unlockPort, didUsePort } = await renderer_1.RenderInternals.getDesiredPort({
106
+ desiredPort,
107
+ from: 3000,
108
+ to: 3100,
109
+ hostsToTry: portConfig.hostsToTry,
110
+ onPortUnavailable,
111
+ });
112
+ if (didUsePort) {
113
+ unlockPort();
114
+ await Promise.all([
115
+ new Promise((resolve) => {
116
+ server.close(() => resolve());
117
+ }),
118
+ new Promise((resolve) => {
119
+ compiler.close(() => resolve());
120
+ }),
121
+ ]);
122
+ return {
123
+ type: 'already-running',
124
+ port,
125
+ };
126
+ }
94
127
  const selectedPort = await new Promise((resolve, reject) => {
95
- renderer_1.RenderInternals.getDesiredPort({
96
- desiredPort,
97
- from: 3000,
98
- to: 3100,
99
- hostsToTry: portConfig.hostsToTry,
100
- })
101
- .then(({ port, unlockPort }) => {
102
- renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: options.logLevel }, `Binding server to host ${portConfig.host}, port ${port}`);
103
- server.listen({
104
- port,
105
- host: portConfig.host,
106
- });
107
- server.on('listening', () => {
108
- resolve(port);
109
- return unlockPort();
110
- });
111
- server.on('error', (err) => {
112
- reject(err);
113
- });
114
- })
115
- .catch((err) => reject(err));
128
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: options.logLevel }, `Binding server to host ${portConfig.host}, port ${port}`);
129
+ server.listen({
130
+ port,
131
+ host: portConfig.host,
132
+ });
133
+ server.on('listening', () => {
134
+ resolve(port);
135
+ return unlockPort();
136
+ });
137
+ server.on('error', (err) => {
138
+ reject(err);
139
+ });
116
140
  });
117
141
  return {
142
+ type: 'started',
118
143
  port: selectedPort,
119
144
  liveEventsServer,
120
145
  close: async () => {
@@ -1,3 +1,4 @@
1
+ import type { LogLevel } from '@remotion/renderer';
1
2
  import type { UpdateAvailableResponse } from '@remotion/studio-shared';
2
3
  export declare const getRemotionVersion: () => any;
3
- export declare const isUpdateAvailableWithTimeout: (remotionRoot: string) => Promise<UpdateAvailableResponse>;
4
+ export declare const isUpdateAvailableWithTimeout: (remotionRoot: string, logLevel: LogLevel) => Promise<UpdateAvailableResponse>;
@@ -7,9 +7,14 @@ exports.isUpdateAvailableWithTimeout = exports.getRemotionVersion = void 0;
7
7
  const semver_1 = __importDefault(require("semver"));
8
8
  const get_latest_remotion_version_1 = require("../get-latest-remotion-version");
9
9
  const get_package_manager_1 = require("./get-package-manager");
10
- const isUpdateAvailable = async ({ remotionRoot, currentVersion, }) => {
10
+ const isUpdateAvailable = async ({ remotionRoot, currentVersion, logLevel, }) => {
11
11
  const latest = await (0, get_latest_remotion_version_1.getLatestRemotionVersion)();
12
- const pkgManager = (0, get_package_manager_1.getPackageManager)(remotionRoot, undefined, 0);
12
+ const pkgManager = (0, get_package_manager_1.getPackageManager)({
13
+ remotionRoot,
14
+ packageManager: undefined,
15
+ dirUp: 0,
16
+ logLevel,
17
+ });
13
18
  return {
14
19
  updateAvailable: semver_1.default.lt(currentVersion, latest),
15
20
  currentVersion,
@@ -25,10 +30,15 @@ const getRemotionVersion = () => {
25
30
  return version;
26
31
  };
27
32
  exports.getRemotionVersion = getRemotionVersion;
28
- const isUpdateAvailableWithTimeout = (remotionRoot) => {
33
+ const isUpdateAvailableWithTimeout = (remotionRoot, logLevel) => {
29
34
  const version = (0, exports.getRemotionVersion)();
30
35
  const threeSecTimeout = new Promise((resolve) => {
31
- const pkgManager = (0, get_package_manager_1.getPackageManager)(remotionRoot, undefined, 0);
36
+ const pkgManager = (0, get_package_manager_1.getPackageManager)({
37
+ remotionRoot,
38
+ packageManager: undefined,
39
+ dirUp: 0,
40
+ logLevel,
41
+ });
32
42
  setTimeout(() => {
33
43
  resolve({
34
44
  currentVersion: version,
@@ -41,7 +51,7 @@ const isUpdateAvailableWithTimeout = (remotionRoot) => {
41
51
  });
42
52
  return Promise.race([
43
53
  threeSecTimeout,
44
- isUpdateAvailable({ remotionRoot, currentVersion: version }),
54
+ isUpdateAvailable({ remotionRoot, currentVersion: version, logLevel }),
45
55
  ]);
46
56
  };
47
57
  exports.isUpdateAvailableWithTimeout = isUpdateAvailableWithTimeout;
@@ -0,0 +1,5 @@
1
+ export type RemotionConfigResponse = {
2
+ isRemotion: true;
3
+ cwd: string;
4
+ version: string | null;
5
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/routes.js CHANGED
@@ -60,6 +60,19 @@ const output404 = (response) => {
60
60
  response.end('The outputs/ prefix has been changed, this URL is no longer valid.');
61
61
  return Promise.resolve();
62
62
  };
63
+ const handleRemotionConfig = (response, remotionRoot) => {
64
+ var _a;
65
+ response.writeHead(200, {
66
+ 'Content-Type': 'application/json',
67
+ });
68
+ const body = {
69
+ isRemotion: true,
70
+ cwd: remotionRoot,
71
+ version: (_a = process.env.REMOTION_VERSION) !== null && _a !== void 0 ? _a : null,
72
+ };
73
+ response.end(JSON.stringify(body));
74
+ return Promise.resolve();
75
+ };
63
76
  const handleFallback = async ({ remotionRoot, hash, response, getCurrentInputProps, getEnvVariables, publicDir, getRenderQueue, getRenderDefaults, numberOfAudioTags, audioLatencyHint, gitSource, logLevel, enableCrossSiteIsolation, }) => {
64
77
  const [edit] = await editorGuess;
65
78
  const displayName = (0, open_in_editor_1.getDisplayNameForEditor)(edit ? edit.command : null);
@@ -68,7 +81,12 @@ const handleFallback = async ({ remotionRoot, hash, response, getCurrentInputPro
68
81
  response.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
69
82
  response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
70
83
  }
71
- const packageManager = (0, get_package_manager_1.getPackageManager)(remotionRoot, undefined, 0);
84
+ const packageManager = (0, get_package_manager_1.getPackageManager)({
85
+ remotionRoot,
86
+ packageManager: undefined,
87
+ dirUp: 0,
88
+ logLevel,
89
+ });
72
90
  (0, public_folder_1.fetchFolder)({ publicDir, staticHash: hash });
73
91
  const installedDependencies = (0, get_installed_installable_packages_1.getInstalledInstallablePackages)(remotionRoot);
74
92
  response.end(bundler_1.BundlerInternals.indexHtml({
@@ -265,6 +283,9 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
265
283
  if (url.pathname === studio_shared_1.SOURCE_MAP_ENDPOINT) {
266
284
  return handleWasm(request, response);
267
285
  }
286
+ if (url.pathname === '/__remotion_config') {
287
+ return handleRemotionConfig(response, remotionRoot);
288
+ }
268
289
  if (url.pathname === '/events') {
269
290
  return liveEventsServer.router(request, response);
270
291
  }
@@ -2,7 +2,12 @@ import type { WebpackOverrideFn } from '@remotion/bundler';
2
2
  import type { LogLevel } from '@remotion/renderer';
3
3
  import type { GitSource, RenderDefaults, RenderJob } from '@remotion/studio-shared';
4
4
  import type { QueueMethods } from './preview-server/api-types';
5
- export declare const startStudio: ({ browserArgs, browserFlag, configValueShouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, parsedCliOpen, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, enableCrossSiteIsolation, askAIEnabled, }: {
5
+ export type StartStudioResult = {
6
+ type: 'restarted';
7
+ } | {
8
+ type: 'already-running';
9
+ };
10
+ export declare const startStudio: ({ browserArgs, browserFlag, configValueShouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, parsedCliOpen, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, enableCrossSiteIsolation, askAIEnabled, forceNew, }: {
6
11
  browserArgs: string;
7
12
  browserFlag: string;
8
13
  logLevel: LogLevel;
@@ -31,4 +36,5 @@ export declare const startStudio: ({ browserArgs, browserFlag, configValueShould
31
36
  binariesDirectory: string | null;
32
37
  forceIPv4: boolean;
33
38
  askAIEnabled: boolean;
34
- }) => Promise<void>;
39
+ forceNew: boolean;
40
+ }) => Promise<StartStudioResult>;
@@ -8,8 +8,8 @@ const renderer_1 = require("@remotion/renderer");
8
8
  const node_crypto_1 = __importDefault(require("node:crypto"));
9
9
  const node_fs_1 = require("node:fs");
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
- const better_opn_1 = require("./better-opn");
12
11
  const get_network_address_1 = require("./get-network-address");
12
+ const maybe_open_browser_1 = require("./maybe-open-browser");
13
13
  const close_and_restart_1 = require("./preview-server/close-and-restart");
14
14
  const get_absolute_public_dir_1 = require("./preview-server/get-absolute-public-dir");
15
15
  const live_events_1 = require("./preview-server/live-events");
@@ -17,26 +17,7 @@ const public_folder_1 = require("./preview-server/public-folder");
17
17
  const start_server_1 = require("./preview-server/start-server");
18
18
  const server_ready_1 = require("./server-ready");
19
19
  const watch_root_file_1 = require("./watch-root-file");
20
- const getShouldOpenBrowser = ({ configValueShouldOpenBrowser, parsedCliOpen, }) => {
21
- var _a;
22
- if (parsedCliOpen === false) {
23
- return {
24
- shouldOpenBrowser: false,
25
- reasonForBrowserDecision: '--no-open specified',
26
- };
27
- }
28
- if (((_a = process.env.BROWSER) !== null && _a !== void 0 ? _a : '').toLowerCase() === 'none') {
29
- return {
30
- shouldOpenBrowser: false,
31
- reasonForBrowserDecision: 'env BROWSER=none was set',
32
- };
33
- }
34
- if (configValueShouldOpenBrowser === false) {
35
- return { shouldOpenBrowser: false, reasonForBrowserDecision: 'Config file' };
36
- }
37
- return { shouldOpenBrowser: true, reasonForBrowserDecision: 'default' };
38
- };
39
- const startStudio = async ({ browserArgs, browserFlag, configValueShouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, parsedCliOpen, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, enableCrossSiteIsolation, askAIEnabled, }) => {
20
+ const startStudio = async ({ browserArgs, browserFlag, configValueShouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, parsedCliOpen, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, enableCrossSiteIsolation, askAIEnabled, forceNew, }) => {
40
21
  try {
41
22
  if (typeof Bun === 'undefined') {
42
23
  process.title = 'node (npx remotion studio)';
@@ -78,7 +59,7 @@ const startStudio = async ({ browserArgs, browserFlag, configValueShouldOpenBrow
78
59
  },
79
60
  staticHash,
80
61
  });
81
- const { port, liveEventsServer, close } = await (0, start_server_1.startServer)({
62
+ const result = await (0, start_server_1.startServer)({
82
63
  entry: node_path_1.default.resolve(previewEntry),
83
64
  userDefinedComponent: fullEntryPath,
84
65
  getCurrentInputProps,
@@ -107,7 +88,24 @@ const startStudio = async ({ browserArgs, browserFlag, configValueShouldOpenBrow
107
88
  audioLatencyHint,
108
89
  enableCrossSiteIsolation,
109
90
  askAIEnabled,
91
+ forceNew,
110
92
  });
93
+ if (result.type === 'already-running') {
94
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `Already running on port ${result.port}.`);
95
+ const res = await (0, maybe_open_browser_1.maybeOpenBrowser)({
96
+ browserArgs,
97
+ browserFlag,
98
+ configValueShouldOpenBrowser,
99
+ parsedCliOpen,
100
+ url: `http://localhost:${result.port}`,
101
+ logLevel,
102
+ });
103
+ if (res.didOpenBrowser) {
104
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, 'Opened browser. Pass --force-new to force a new instance.');
105
+ }
106
+ return { type: 'already-running' };
107
+ }
108
+ const { port, liveEventsServer, close } = result;
111
109
  const cleanupLiveEventsListener = (0, live_events_1.setLiveEventsListener)(liveEventsServer);
112
110
  const networkAddress = (0, get_network_address_1.getNetworkAddress)();
113
111
  if (networkAddress) {
@@ -118,25 +116,20 @@ const startStudio = async ({ browserArgs, browserFlag, configValueShouldOpenBrow
118
116
  }
119
117
  (0, server_ready_1.printServerReadyComment)('Server ready', logLevel);
120
118
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, 'Building...');
121
- const { reasonForBrowserDecision, shouldOpenBrowser } = getShouldOpenBrowser({
119
+ await (0, maybe_open_browser_1.maybeOpenBrowser)({
120
+ browserArgs,
121
+ browserFlag,
122
122
  configValueShouldOpenBrowser,
123
123
  parsedCliOpen,
124
+ url: `http://localhost:${port}`,
125
+ logLevel,
124
126
  });
125
- if (shouldOpenBrowser) {
126
- await (0, better_opn_1.openBrowser)({
127
- url: `http://localhost:${port}`,
128
- browserArgs,
129
- browserFlag,
130
- });
131
- }
132
- else {
133
- renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `Not opening browser, reason: ${reasonForBrowserDecision}`);
134
- }
135
127
  await (0, close_and_restart_1.noOpUntilRestart)();
136
128
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, 'Closing server to restart...');
137
129
  await liveEventsServer.closeConnections();
138
130
  cleanupLiveEventsListener();
139
131
  await close();
140
132
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.blue('Restarting server...'));
133
+ return { type: 'restarted' };
141
134
  };
142
135
  exports.startStudio = startStudio;
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.420",
6
+ "version": "4.0.422",
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.420",
28
+ "remotion": "4.0.422",
29
29
  "recast": "0.23.11",
30
- "@remotion/bundler": "4.0.420",
31
- "@remotion/renderer": "4.0.420",
32
- "@remotion/studio-shared": "4.0.420",
30
+ "@remotion/bundler": "4.0.422",
31
+ "@remotion/renderer": "4.0.422",
32
+ "@remotion/studio-shared": "4.0.422",
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.420",
42
+ "@remotion/eslint-config-internal": "4.0.422",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14"
45
45
  },