@remotion/studio-server 4.0.438 → 4.0.439

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 (35) hide show
  1. package/dist/client-render-queue.js +3 -3
  2. package/dist/codemods/apply-visual-control.js +8 -1
  3. package/dist/codemods/format-inline-content.d.ts +21 -0
  4. package/dist/codemods/format-inline-content.js +108 -0
  5. package/dist/codemods/read-visual-control-values.d.ts +7 -0
  6. package/dist/codemods/read-visual-control-values.js +166 -0
  7. package/dist/codemods/update-default-props.d.ts +4 -1
  8. package/dist/codemods/update-default-props.js +51 -22
  9. package/dist/file-watcher.d.ts +24 -7
  10. package/dist/file-watcher.js +148 -29
  11. package/dist/index.d.ts +5 -2
  12. package/dist/index.js +3 -0
  13. package/dist/preview-server/api-routes.js +8 -2
  14. package/dist/preview-server/default-props-watchers.js +12 -17
  15. package/dist/preview-server/file-existence-watchers.js +3 -3
  16. package/dist/preview-server/hot-middleware/index.js +1 -15
  17. package/dist/preview-server/live-events.d.ts +6 -1
  18. package/dist/preview-server/live-events.js +12 -2
  19. package/dist/preview-server/routes/apply-codemod.js +2 -1
  20. package/dist/preview-server/routes/apply-visual-control-change.js +103 -5
  21. package/dist/preview-server/routes/can-update-default-props.d.ts +10 -3
  22. package/dist/preview-server/routes/can-update-default-props.js +138 -13
  23. package/dist/preview-server/routes/can-update-sequence-props.js +4 -0
  24. package/dist/preview-server/routes/log-update.d.ts +8 -0
  25. package/dist/preview-server/routes/log-update.js +60 -27
  26. package/dist/preview-server/routes/save-sequence-props.js +47 -5
  27. package/dist/preview-server/routes/update-default-props.js +41 -4
  28. package/dist/preview-server/sequence-props-watchers.js +4 -9
  29. package/dist/preview-server/start-server.js +15 -1
  30. package/dist/preview-server/undo-stack.d.ts +24 -4
  31. package/dist/preview-server/undo-stack.js +75 -11
  32. package/dist/preview-server/watch-ignore-next-change.d.ts +3 -0
  33. package/dist/preview-server/watch-ignore-next-change.js +12 -0
  34. package/dist/start-studio.js +3 -0
  35. package/package.json +6 -6
@@ -1,9 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.logUpdate = void 0;
3
+ exports.logUpdate = exports.formatPropChange = exports.normalizeQuotes = exports.warnAboutPrettierOnce = void 0;
4
4
  const renderer_1 = require("@remotion/renderer");
5
5
  const make_link_1 = require("../../hyperlinks/make-link");
6
6
  let warnedAboutPrettier = false;
7
+ const warnAboutPrettierOnce = (logLevel) => {
8
+ if (warnedAboutPrettier) {
9
+ return;
10
+ }
11
+ warnedAboutPrettier = true;
12
+ renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.yellow('Could not format with Prettier. File will need to be formatted manually.'));
13
+ };
14
+ exports.warnAboutPrettierOnce = warnAboutPrettierOnce;
7
15
  const normalizeQuotes = (str) => {
8
16
  if (str.length >= 2 &&
9
17
  ((str.startsWith("'") && str.endsWith("'")) ||
@@ -12,45 +20,71 @@ const normalizeQuotes = (str) => {
12
20
  }
13
21
  return str;
14
22
  };
15
- const formatValueChange = ({ oldValueString, newValueString, defaultValueString, }) => {
16
- // Changed to default value (prop gets deleted) → show only old value in red
17
- if (defaultValueString !== null && newValueString === defaultValueString) {
18
- return renderer_1.RenderInternals.chalk.red(oldValueString);
23
+ exports.normalizeQuotes = normalizeQuotes;
24
+ // 24-bit ANSI helpers
25
+ const fg = (r, g, b, str) => `\u001b[38;2;${r};${g};${b}m${str}\u001b[39m`;
26
+ const bg = (r, g, b, str) => `\u001b[48;2;${r};${g};${b}m${str}\u001b[49m`;
27
+ // Monokai-inspired syntax colors
28
+ const attrName = (str) => fg(166, 226, 46, str);
29
+ const equals = (str) => fg(249, 38, 114, str);
30
+ const punctuation = (str) => fg(248, 248, 242, str);
31
+ const stringValue = (str) => fg(230, 219, 116, str);
32
+ const numberValue = (str) => fg(174, 129, 255, str);
33
+ const colorValue = (str) => {
34
+ if ((str.startsWith("'") && str.endsWith("'")) ||
35
+ (str.startsWith('"') && str.endsWith('"'))) {
36
+ return stringValue(str);
19
37
  }
20
- // Changed from default value (prop gets added) → show only new value in green
21
- if (defaultValueString !== null && oldValueString === defaultValueString) {
22
- return renderer_1.RenderInternals.chalk.green(newValueString);
38
+ if (/^-?\d+(\.\d+)?$/.test(str)) {
39
+ return numberValue(str);
23
40
  }
24
- return `${renderer_1.RenderInternals.chalk.red(oldValueString)} \u2192 ${renderer_1.RenderInternals.chalk.green(newValueString)}`;
41
+ return punctuation(str);
42
+ };
43
+ // Subtle background tints
44
+ const removedBg = (str) => bg(80, 20, 20, str);
45
+ const addedBg = (str) => bg(30, 80, 30, str);
46
+ const colorEnabled = () => renderer_1.RenderInternals.chalk.enabled();
47
+ // Format key={value} with Monokai syntax highlighting
48
+ const formatSimpleProp = (key, value) => {
49
+ return `${attrName(key)}${equals('=')}${punctuation('{')}${colorValue(value)}${punctuation('}')}`;
50
+ };
51
+ // Format parentKey={{childKey: value}} with Monokai syntax highlighting
52
+ const formatNestedProp = (parentKey, childKey, value) => {
53
+ return `${attrName(parentKey)}${equals('=')}${punctuation('{{')}${punctuation(childKey)}${punctuation(':')} ${colorValue(value)}${punctuation('}}')}`;
25
54
  };
26
55
  const formatPropChange = ({ key, oldValueString, newValueString, defaultValueString, }) => {
56
+ if (!colorEnabled()) {
57
+ const dotIdx = key.indexOf('.');
58
+ if (dotIdx === -1) {
59
+ return `${key}={${oldValueString}} \u2192 ${key}={${newValueString}}`;
60
+ }
61
+ const parent = key.slice(0, dotIdx);
62
+ const child = key.slice(dotIdx + 1);
63
+ return `${parent}={{${child}: ${oldValueString}}} \u2192 ${parent}={{${child}: ${newValueString}}}`;
64
+ }
27
65
  const isResetToDefault = defaultValueString !== null && newValueString === defaultValueString;
28
66
  const isChangeFromDefault = defaultValueString !== null && oldValueString === defaultValueString;
29
- const valueChange = formatValueChange({
30
- oldValueString,
31
- newValueString,
32
- defaultValueString,
33
- });
34
67
  const dotIndex = key.indexOf('.');
35
68
  if (dotIndex === -1) {
36
69
  if (isResetToDefault) {
37
- return renderer_1.RenderInternals.chalk.red(`${key}={${oldValueString}}`);
70
+ return removedBg(formatSimpleProp(key, oldValueString));
38
71
  }
39
72
  if (isChangeFromDefault) {
40
- return renderer_1.RenderInternals.chalk.green(`${key}={${newValueString}}`);
73
+ return addedBg(formatSimpleProp(key, newValueString));
41
74
  }
42
- return `${key}={${valueChange}}`;
75
+ return `${removedBg(formatSimpleProp(key, oldValueString))} \u2192 ${addedBg(formatSimpleProp(key, newValueString))}`;
43
76
  }
44
77
  const parentKey = key.slice(0, dotIndex);
45
78
  const childKey = key.slice(dotIndex + 1);
46
79
  if (isResetToDefault) {
47
- return `${parentKey}={{${renderer_1.RenderInternals.chalk.red(`${childKey}: ${oldValueString}`)}}}`;
80
+ return removedBg(formatNestedProp(parentKey, childKey, oldValueString));
48
81
  }
49
82
  if (isChangeFromDefault) {
50
- return `${parentKey}={{${renderer_1.RenderInternals.chalk.green(`${childKey}: ${newValueString}`)}}}`;
83
+ return addedBg(formatNestedProp(parentKey, childKey, newValueString));
51
84
  }
52
- return `${parentKey}={{${childKey}: ${valueChange}}}`;
85
+ return `${removedBg(formatNestedProp(parentKey, childKey, oldValueString))} \u2192 ${addedBg(formatNestedProp(parentKey, childKey, newValueString))}`;
53
86
  };
87
+ exports.formatPropChange = formatPropChange;
54
88
  const logUpdate = ({ absolutePath, fileRelativeToRoot, key, oldValueString, newValueString, defaultValueString, formatted, logLevel, }) => {
55
89
  const locationLabel = `${fileRelativeToRoot}`;
56
90
  const fileLink = (0, make_link_1.makeHyperlink)({
@@ -58,16 +92,15 @@ const logUpdate = ({ absolutePath, fileRelativeToRoot, key, oldValueString, newV
58
92
  text: locationLabel,
59
93
  fallback: locationLabel,
60
94
  });
61
- const propChange = formatPropChange({
95
+ const propChange = (0, exports.formatPropChange)({
62
96
  key,
63
- oldValueString: normalizeQuotes(oldValueString),
64
- newValueString: normalizeQuotes(newValueString),
65
- defaultValueString: defaultValueString !== null ? normalizeQuotes(defaultValueString) : null,
97
+ oldValueString: (0, exports.normalizeQuotes)(oldValueString),
98
+ newValueString: (0, exports.normalizeQuotes)(newValueString),
99
+ defaultValueString: defaultValueString !== null ? (0, exports.normalizeQuotes)(defaultValueString) : null,
66
100
  });
67
101
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(`${fileLink}:`)} ${propChange}`);
68
- if (!formatted && !warnedAboutPrettier) {
69
- warnedAboutPrettier = true;
70
- renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.yellow('Could not format with Prettier. File will need to be formatted manually.'));
102
+ if (!formatted) {
103
+ (0, exports.warnAboutPrettierOnce)(logLevel);
71
104
  }
72
105
  };
73
106
  exports.logUpdate = logUpdate;
@@ -6,11 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.saveSequencePropsHandler = void 0;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ const renderer_1 = require("@remotion/renderer");
9
10
  const update_sequence_props_1 = require("../../codemods/update-sequence-props");
10
- const hmr_suppression_1 = require("../hmr-suppression");
11
+ const file_watcher_1 = require("../../file-watcher");
12
+ const undo_stack_1 = require("../undo-stack");
13
+ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
14
+ const can_update_sequence_props_1 = require("./can-update-sequence-props");
11
15
  const log_update_1 = require("./log-update");
12
- const saveSequencePropsHandler = async ({ input: { fileName, nodePath, key, value, defaultValue }, remotionRoot, logLevel, }) => {
16
+ const saveSequencePropsHandler = async ({ input: { fileName, nodePath, key, value, defaultValue, observedKeys }, remotionRoot, logLevel, }) => {
13
17
  try {
18
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-sequence-props] Received request for fileName="${fileName}" key="${key}"`);
14
19
  const absolutePath = node_path_1.default.resolve(remotionRoot, fileName);
15
20
  const fileRelativeToRoot = node_path_1.default.relative(remotionRoot, absolutePath);
16
21
  if (fileRelativeToRoot.startsWith('..')) {
@@ -24,28 +29,65 @@ const saveSequencePropsHandler = async ({ input: { fileName, nodePath, key, valu
24
29
  value: JSON.parse(value),
25
30
  defaultValue: defaultValue !== null ? JSON.parse(defaultValue) : null,
26
31
  });
27
- (0, hmr_suppression_1.suppressHmrForFile)(absolutePath);
28
- (0, node_fs_1.writeFileSync)(absolutePath, output);
29
32
  const newValueString = JSON.stringify(JSON.parse(value));
30
33
  const parsedDefault = defaultValue !== null ? JSON.parse(defaultValue) : null;
34
+ const defaultValueString = parsedDefault !== null ? JSON.stringify(parsedDefault) : null;
35
+ const normalizedOld = (0, log_update_1.normalizeQuotes)(oldValueString);
36
+ const normalizedNew = (0, log_update_1.normalizeQuotes)(newValueString);
37
+ const normalizedDefault = defaultValueString !== null ? (0, log_update_1.normalizeQuotes)(defaultValueString) : null;
38
+ const undoPropChange = (0, log_update_1.formatPropChange)({
39
+ key,
40
+ oldValueString: normalizedNew,
41
+ newValueString: normalizedOld,
42
+ defaultValueString: normalizedDefault,
43
+ });
44
+ const redoPropChange = (0, log_update_1.formatPropChange)({
45
+ key,
46
+ oldValueString: normalizedOld,
47
+ newValueString: normalizedNew,
48
+ defaultValueString: normalizedDefault,
49
+ });
50
+ (0, undo_stack_1.pushToUndoStack)({
51
+ filePath: absolutePath,
52
+ oldContents: fileContents,
53
+ logLevel,
54
+ remotionRoot,
55
+ description: {
56
+ undoMessage: `Undid ${undoPropChange}`,
57
+ redoMessage: `Redid ${redoPropChange}`,
58
+ },
59
+ entryType: 'sequence-props',
60
+ });
61
+ (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
62
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
63
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output);
31
64
  (0, log_update_1.logUpdate)({
32
65
  absolutePath,
33
66
  fileRelativeToRoot,
34
67
  key,
35
68
  oldValueString,
36
69
  newValueString,
37
- defaultValueString: parsedDefault !== null ? JSON.stringify(parsedDefault) : null,
70
+ defaultValueString,
38
71
  formatted,
39
72
  logLevel,
40
73
  });
74
+ (0, undo_stack_1.printUndoHint)(logLevel);
75
+ const newStatus = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
76
+ fileName,
77
+ keys: observedKeys,
78
+ nodePath,
79
+ remotionRoot,
80
+ });
41
81
  return {
42
82
  success: true,
83
+ newStatus,
43
84
  };
44
85
  }
45
86
  catch (err) {
46
87
  return {
47
88
  success: false,
48
89
  reason: err.message,
90
+ stack: err.stack,
49
91
  };
50
92
  }
51
93
  };
@@ -1,24 +1,61 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.updateDefaultPropsHandler = void 0;
4
7
  const node_fs_1 = require("node:fs");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const renderer_1 = require("@remotion/renderer");
5
10
  const update_default_props_1 = require("../../codemods/update-default-props");
11
+ const file_watcher_1 = require("../../file-watcher");
12
+ const make_link_1 = require("../../hyperlinks/make-link");
6
13
  const project_info_1 = require("../project-info");
14
+ const undo_stack_1 = require("../undo-stack");
15
+ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
7
16
  const can_update_default_props_1 = require("./can-update-default-props");
8
- const updateDefaultPropsHandler = async ({ input: { compositionId, defaultProps, enumPaths }, remotionRoot, entryPoint, }) => {
17
+ const log_update_1 = require("./log-update");
18
+ const updateDefaultPropsHandler = async ({ input: { compositionId, defaultProps, enumPaths }, remotionRoot, entryPoint, logLevel, }) => {
9
19
  try {
20
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[update-default-props] Received request for compositionId="${compositionId}"`);
10
21
  const projectInfo = await (0, project_info_1.getProjectInfo)(remotionRoot, entryPoint);
11
22
  if (!projectInfo.rootFile) {
12
23
  throw new Error('Cannot find root file in project');
13
24
  }
14
25
  (0, can_update_default_props_1.checkIfTypeScriptFile)(projectInfo.rootFile);
15
- const updated = await (0, update_default_props_1.updateDefaultProps)({
26
+ const fileContents = (0, node_fs_1.readFileSync)(projectInfo.rootFile, 'utf-8');
27
+ const { output, formatted } = await (0, update_default_props_1.updateDefaultProps)({
16
28
  compositionId,
17
- input: (0, node_fs_1.readFileSync)(projectInfo.rootFile, 'utf-8'),
29
+ input: fileContents,
18
30
  newDefaultProps: JSON.parse(defaultProps),
19
31
  enumPaths,
20
32
  });
21
- (0, node_fs_1.writeFileSync)(projectInfo.rootFile, updated);
33
+ (0, undo_stack_1.pushToUndoStack)({
34
+ filePath: projectInfo.rootFile,
35
+ oldContents: fileContents,
36
+ logLevel,
37
+ remotionRoot,
38
+ description: {
39
+ undoMessage: `Undid default props update for "${compositionId}"`,
40
+ redoMessage: `Redid default props update for "${compositionId}"`,
41
+ },
42
+ entryType: 'default-props',
43
+ });
44
+ (0, undo_stack_1.suppressUndoStackInvalidation)(projectInfo.rootFile);
45
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(projectInfo.rootFile);
46
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(projectInfo.rootFile, output);
47
+ const fileRelativeToRoot = node_path_1.default.relative(remotionRoot, projectInfo.rootFile);
48
+ const locationLabel = `${fileRelativeToRoot}`;
49
+ const fileLink = (0, make_link_1.makeHyperlink)({
50
+ url: `file://${projectInfo.rootFile}`,
51
+ text: locationLabel,
52
+ fallback: locationLabel,
53
+ });
54
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(`${fileLink}:`)} Updated default props for "${compositionId}"`);
55
+ if (!formatted) {
56
+ (0, log_update_1.warnAboutPrettierOnce)(logLevel);
57
+ }
58
+ (0, undo_stack_1.printUndoHint)(logLevel);
22
59
  return {
23
60
  success: true,
24
61
  };
@@ -33,18 +33,13 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, keys, remotionRoot,
33
33
  }
34
34
  const { unwatch } = (0, file_watcher_1.installFileWatcher)({
35
35
  file: absolutePath,
36
- onChange: (type) => {
37
- if (type === 'deleted') {
36
+ onChange: (event) => {
37
+ if (event.type === 'deleted') {
38
38
  return;
39
39
  }
40
- const result = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
41
- fileName,
42
- nodePath,
43
- keys,
44
- remotionRoot,
45
- });
40
+ const result = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)(event.content, nodePath, keys);
46
41
  (0, live_events_1.waitForLiveEventsListener)().then((listener) => {
47
- listener.sendEventToClient({
42
+ listener.sendEventToClientId(clientId, {
48
43
  type: 'sequence-props-updated',
49
44
  fileName,
50
45
  nodePath,
@@ -12,6 +12,8 @@ const routes_1 = require("../routes");
12
12
  const dev_middleware_1 = require("./dev-middleware");
13
13
  const hot_middleware_1 = require("./hot-middleware");
14
14
  const live_events_1 = require("./live-events");
15
+ const undo_stack_1 = require("./undo-stack");
16
+ const watch_ignore_next_change_1 = require("./watch-ignore-next-change");
15
17
  const startServer = async (options) => {
16
18
  var _a, _b, _c;
17
19
  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;
@@ -26,6 +28,9 @@ const startServer = async (options) => {
26
28
  });
27
29
  return detection.type === 'match' ? 'stop' : 'continue';
28
30
  };
31
+ const watchIgnorePlugin = new bundler_1.WatchIgnoreNextChangePlugin((...args) => {
32
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel: options.logLevel }, ...args);
33
+ });
29
34
  const configArgs = {
30
35
  entry: options.entry,
31
36
  userDefinedComponent: options.userDefinedComponent,
@@ -40,6 +45,7 @@ const startServer = async (options) => {
40
45
  poll: options.poll,
41
46
  bufferStateDelayInMilliseconds: options.bufferStateDelayInMilliseconds,
42
47
  askAIEnabled: options.askAIEnabled,
48
+ extraPlugins: [watchIgnorePlugin],
43
49
  };
44
50
  let compiler;
45
51
  if (options.rspack) {
@@ -50,9 +56,17 @@ const startServer = async (options) => {
50
56
  const [, webpackConf] = await bundler_1.BundlerInternals.webpackConfig(configArgs);
51
57
  compiler = (0, bundler_1.webpack)(webpackConf);
52
58
  }
59
+ (0, watch_ignore_next_change_1.setWatchIgnoreNextChangePlugin)(watchIgnorePlugin);
53
60
  const wdmMiddleware = (0, dev_middleware_1.wdm)(compiler, options.logLevel);
54
61
  const whm = (0, hot_middleware_1.webpackHotMiddleware)(compiler, options.logLevel);
55
- const liveEventsServer = (0, live_events_1.makeLiveEventsRouter)(options.logLevel);
62
+ const liveEventsServer = (0, live_events_1.makeLiveEventsRouter)(options.logLevel, () => {
63
+ const undoStack = (0, undo_stack_1.getUndoStack)();
64
+ const redoStack = (0, undo_stack_1.getRedoStack)();
65
+ return {
66
+ undoFile: undoStack.length > 0 ? undoStack[undoStack.length - 1].filePath : null,
67
+ redoFile: redoStack.length > 0 ? redoStack[redoStack.length - 1].filePath : null,
68
+ };
69
+ });
56
70
  const server = node_http_1.default.createServer((request, response) => {
57
71
  if (options.enableCrossSiteIsolation) {
58
72
  response.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
@@ -1,10 +1,30 @@
1
1
  import type { LogLevel } from '@remotion/renderer';
2
- interface UndoEntry {
2
+ export interface UndoEntryDescription {
3
+ undoMessage: string;
4
+ redoMessage: string;
5
+ }
6
+ type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props';
7
+ type UndoEntry = {
3
8
  filePath: string;
4
9
  oldContents: string;
5
- }
6
- export declare function pushToUndoStack(filePath: string, oldContents: string, logLevel: LogLevel): void;
7
- export declare function pushToRedoStack(filePath: string, oldContents: string): void;
10
+ description: UndoEntryDescription;
11
+ } & ({
12
+ entryType: 'visual-control';
13
+ } | {
14
+ entryType: 'default-props';
15
+ } | {
16
+ entryType: 'sequence-props';
17
+ });
18
+ export declare function pushToUndoStack({ filePath, oldContents, logLevel, remotionRoot, description, entryType }: {
19
+ filePath: string;
20
+ oldContents: string;
21
+ logLevel: LogLevel;
22
+ remotionRoot: string;
23
+ description: UndoEntryDescription;
24
+ entryType: UndoEntryType;
25
+ }): void;
26
+ export declare function printUndoHint(logLevel: LogLevel): void;
27
+ export declare function pushToRedoStack(filePath: string, oldContents: string, description: UndoEntryDescription, entryType: UndoEntryType): void;
8
28
  export declare function suppressUndoStackInvalidation(filePath: string): void;
9
29
  export declare function popUndo(): {
10
30
  success: true;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.pushToUndoStack = pushToUndoStack;
7
+ exports.printUndoHint = printUndoHint;
4
8
  exports.pushToRedoStack = pushToRedoStack;
5
9
  exports.suppressUndoStackInvalidation = suppressUndoStackInvalidation;
6
10
  exports.popUndo = popUndo;
@@ -8,16 +12,22 @@ exports.popRedo = popRedo;
8
12
  exports.getUndoStack = getUndoStack;
9
13
  exports.getRedoStack = getRedoStack;
10
14
  const node_fs_1 = require("node:fs");
15
+ const node_path_1 = __importDefault(require("node:path"));
11
16
  const renderer_1 = require("@remotion/renderer");
17
+ const parse_ast_1 = require("../codemods/parse-ast");
18
+ const read_visual_control_values_1 = require("../codemods/read-visual-control-values");
12
19
  const file_watcher_1 = require("../file-watcher");
13
- const hmr_suppression_1 = require("./hmr-suppression");
20
+ const make_link_1 = require("../hyperlinks/make-link");
14
21
  const live_events_1 = require("./live-events");
22
+ const watch_ignore_next_change_1 = require("./watch-ignore-next-change");
15
23
  const MAX_ENTRIES = 100;
16
24
  const undoStack = [];
17
25
  const redoStack = [];
18
26
  const suppressedWrites = new Map();
19
27
  const watchers = new Map();
20
28
  let storedLogLevel = 'info';
29
+ let storedRemotionRoot = null;
30
+ let printedUndoHint = false;
21
31
  function broadcastState() {
22
32
  const undoFile = undoStack.length > 0 ? undoStack[undoStack.length - 1].filePath : null;
23
33
  const redoFile = redoStack.length > 0 ? redoStack[redoStack.length - 1].filePath : null;
@@ -29,9 +39,10 @@ function broadcastState() {
29
39
  });
30
40
  });
31
41
  }
32
- function pushToUndoStack(filePath, oldContents, logLevel) {
42
+ function pushToUndoStack({ filePath, oldContents, logLevel, remotionRoot, description, entryType, }) {
33
43
  storedLogLevel = logLevel;
34
- undoStack.push({ filePath, oldContents });
44
+ storedRemotionRoot = remotionRoot;
45
+ undoStack.push({ filePath, oldContents, description, entryType });
35
46
  if (undoStack.length > MAX_ENTRIES) {
36
47
  undoStack.shift();
37
48
  }
@@ -40,8 +51,15 @@ function pushToUndoStack(filePath, oldContents, logLevel) {
40
51
  ensureWatching(filePath);
41
52
  broadcastState();
42
53
  }
43
- function pushToRedoStack(filePath, oldContents) {
44
- redoStack.push({ filePath, oldContents });
54
+ function printUndoHint(logLevel) {
55
+ if (!printedUndoHint) {
56
+ printedUndoHint = true;
57
+ const shortcut = process.platform === 'darwin' ? 'Cmd+Z' : 'Ctrl+Z';
58
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray(`Tip: ${shortcut} in Studio to undo`));
59
+ }
60
+ }
61
+ function pushToRedoStack(filePath, oldContents, description, entryType) {
62
+ redoStack.push({ filePath, oldContents, description, entryType });
45
63
  if (redoStack.length > MAX_ENTRIES) {
46
64
  redoStack.shift();
47
65
  }
@@ -121,17 +139,54 @@ function cleanupWatchers() {
121
139
  }
122
140
  }
123
141
  }
142
+ function emitVisualControlChanges(fileContents) {
143
+ try {
144
+ const ast = (0, parse_ast_1.parseAst)(fileContents);
145
+ const values = (0, read_visual_control_values_1.readVisualControlValues)(ast);
146
+ if (values.length > 0) {
147
+ (0, live_events_1.waitForLiveEventsListener)().then((listener) => {
148
+ listener.sendEventToClient({
149
+ type: 'visual-control-values-changed',
150
+ values,
151
+ });
152
+ });
153
+ }
154
+ }
155
+ catch (_a) {
156
+ // File might not contain visual controls or might not be parseable
157
+ }
158
+ }
159
+ function logFileAction(action, filePath) {
160
+ const locationLabel = storedRemotionRoot
161
+ ? node_path_1.default.relative(storedRemotionRoot, filePath)
162
+ : filePath;
163
+ const fileLink = (0, make_link_1.makeHyperlink)({
164
+ url: `file://${filePath}`,
165
+ text: locationLabel,
166
+ fallback: locationLabel,
167
+ });
168
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel: storedLogLevel }, `${renderer_1.RenderInternals.chalk.blueBright(`${fileLink}:`)} ${action}`);
169
+ }
124
170
  function popUndo() {
125
171
  const entry = undoStack.pop();
126
172
  if (!entry) {
127
173
  return { success: false, reason: 'Nothing to undo' };
128
174
  }
129
175
  const currentContents = (0, node_fs_1.readFileSync)(entry.filePath, 'utf-8');
130
- redoStack.push({ filePath: entry.filePath, oldContents: currentContents });
176
+ redoStack.push({
177
+ filePath: entry.filePath,
178
+ oldContents: currentContents,
179
+ description: entry.description,
180
+ entryType: entry.entryType,
181
+ });
131
182
  suppressUndoStackInvalidation(entry.filePath);
132
- (0, hmr_suppression_1.suppressHmrForFile)(entry.filePath);
133
- (0, node_fs_1.writeFileSync)(entry.filePath, entry.oldContents);
183
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(entry.filePath);
184
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(entry.filePath, entry.oldContents);
134
185
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: storedLogLevel }, renderer_1.RenderInternals.chalk.gray(`Undo: restored ${entry.filePath} (undo: ${undoStack.length}, redo: ${redoStack.length})`));
186
+ logFileAction(entry.description.undoMessage, entry.filePath);
187
+ if (entry.entryType === 'visual-control') {
188
+ emitVisualControlChanges(entry.oldContents);
189
+ }
135
190
  ensureWatching(entry.filePath);
136
191
  broadcastState();
137
192
  return { success: true };
@@ -142,11 +197,20 @@ function popRedo() {
142
197
  return { success: false, reason: 'Nothing to redo' };
143
198
  }
144
199
  const currentContents = (0, node_fs_1.readFileSync)(entry.filePath, 'utf-8');
145
- undoStack.push({ filePath: entry.filePath, oldContents: currentContents });
200
+ undoStack.push({
201
+ filePath: entry.filePath,
202
+ oldContents: currentContents,
203
+ description: entry.description,
204
+ entryType: entry.entryType,
205
+ });
146
206
  suppressUndoStackInvalidation(entry.filePath);
147
- (0, hmr_suppression_1.suppressHmrForFile)(entry.filePath);
148
- (0, node_fs_1.writeFileSync)(entry.filePath, entry.oldContents);
207
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(entry.filePath);
208
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(entry.filePath, entry.oldContents);
149
209
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: storedLogLevel }, renderer_1.RenderInternals.chalk.gray(`Redo: restored ${entry.filePath} (undo: ${undoStack.length}, redo: ${redoStack.length})`));
210
+ logFileAction(entry.description.redoMessage, entry.filePath);
211
+ if (entry.entryType === 'visual-control') {
212
+ emitVisualControlChanges(entry.oldContents);
213
+ }
150
214
  ensureWatching(entry.filePath);
151
215
  broadcastState();
152
216
  return { success: true };
@@ -0,0 +1,3 @@
1
+ import type { WatchIgnoreNextChangePlugin } from '@remotion/bundler';
2
+ export declare const setWatchIgnoreNextChangePlugin: (plugin: WatchIgnoreNextChangePlugin) => void;
3
+ export declare const suppressBundlerUpdateForFile: (absolutePath: string) => void;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.suppressBundlerUpdateForFile = exports.setWatchIgnoreNextChangePlugin = void 0;
4
+ let currentPlugin = null;
5
+ const setWatchIgnoreNextChangePlugin = (plugin) => {
6
+ currentPlugin = plugin;
7
+ };
8
+ exports.setWatchIgnoreNextChangePlugin = setWatchIgnoreNextChangePlugin;
9
+ const suppressBundlerUpdateForFile = (absolutePath) => {
10
+ currentPlugin === null || currentPlugin === void 0 ? void 0 : currentPlugin.ignoreNextChange(absolutePath);
11
+ };
12
+ exports.suppressBundlerUpdateForFile = suppressBundlerUpdateForFile;
@@ -8,6 +8,7 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const renderer_1 = require("@remotion/renderer");
11
+ const file_watcher_1 = require("./file-watcher");
11
12
  const get_network_address_1 = require("./get-network-address");
12
13
  const maybe_open_browser_1 = require("./maybe-open-browser");
13
14
  const close_and_restart_1 = require("./preview-server/close-and-restart");
@@ -30,6 +31,8 @@ const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEn
30
31
  }
31
32
  }
32
33
  catch (_a) { }
34
+ // Validate that the file watcher registry has been initialized
35
+ (0, file_watcher_1.getFileWatcherRegistry)();
33
36
  (0, watch_root_file_1.watchRootFile)(remotionRoot, previewEntry);
34
37
  const publicDir = (0, get_absolute_public_dir_1.getAbsolutePublicDir)({
35
38
  relativePublicDir,
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.438",
6
+ "version": "4.0.439",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "sideEffects": false,
@@ -27,11 +27,11 @@
27
27
  "@babel/parser": "7.24.1",
28
28
  "semver": "7.5.3",
29
29
  "prettier": "3.8.1",
30
- "remotion": "4.0.438",
30
+ "remotion": "4.0.439",
31
31
  "recast": "0.23.11",
32
- "@remotion/bundler": "4.0.438",
33
- "@remotion/renderer": "4.0.438",
34
- "@remotion/studio-shared": "4.0.438",
32
+ "@remotion/bundler": "4.0.439",
33
+ "@remotion/renderer": "4.0.439",
34
+ "@remotion/studio-shared": "4.0.439",
35
35
  "memfs": "3.4.3",
36
36
  "source-map": "0.7.3",
37
37
  "open": "^8.4.2"
@@ -41,7 +41,7 @@
41
41
  "react": "19.2.3",
42
42
  "@babel/types": "7.24.0",
43
43
  "@types/semver": "^7.3.4",
44
- "@remotion/eslint-config-internal": "4.0.438",
44
+ "@remotion/eslint-config-internal": "4.0.439",
45
45
  "eslint": "9.19.0",
46
46
  "@types/node": "20.12.14",
47
47
  "@typescript/native-preview": "7.0.0-dev.20260217.1"