@remotion/studio-server 4.0.469 → 4.0.470

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 (48) hide show
  1. package/dist/codemods/delete-effect.d.ts +18 -0
  2. package/dist/codemods/delete-effect.js +95 -21
  3. package/dist/codemods/delete-jsx-node.d.ts +10 -0
  4. package/dist/codemods/delete-jsx-node.js +38 -14
  5. package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.d.ts +4 -0
  6. package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.js +152 -0
  7. package/dist/codemods/update-keyframes/update-keyframes.d.ts +8 -0
  8. package/dist/codemods/update-keyframes/update-keyframes.js +94 -24
  9. package/dist/codemods/update-sequence-props/update-sequence-props.d.ts +32 -10
  10. package/dist/codemods/update-sequence-props/update-sequence-props.js +39 -7
  11. package/dist/index.d.ts +2 -1
  12. package/dist/preview-server/api-routes.js +16 -0
  13. package/dist/preview-server/routes/add-effect-keyframe.d.ts +3 -0
  14. package/dist/preview-server/routes/add-effect-keyframe.js +91 -0
  15. package/dist/preview-server/routes/add-sequence-keyframe.d.ts +3 -0
  16. package/dist/preview-server/routes/add-sequence-keyframe.js +84 -0
  17. package/dist/preview-server/routes/apply-codemod.js +1 -0
  18. package/dist/preview-server/routes/apply-visual-control-change.js +1 -0
  19. package/dist/preview-server/routes/can-update-sequence-props.js +33 -13
  20. package/dist/preview-server/routes/composition-component-info.d.ts +3 -0
  21. package/dist/preview-server/routes/composition-component-info.js +26 -0
  22. package/dist/preview-server/routes/delete-effect-keyframe.d.ts +3 -0
  23. package/dist/preview-server/routes/delete-effect-keyframe.js +89 -0
  24. package/dist/preview-server/routes/delete-effect.js +76 -37
  25. package/dist/preview-server/routes/delete-jsx-node.js +67 -36
  26. package/dist/preview-server/routes/delete-sequence-keyframe.d.ts +3 -0
  27. package/dist/preview-server/routes/delete-sequence-keyframe.js +82 -0
  28. package/dist/preview-server/routes/duplicate-jsx-node.js +1 -0
  29. package/dist/preview-server/routes/open-in-editor.d.ts +4 -0
  30. package/dist/preview-server/routes/open-in-editor.js +40 -0
  31. package/dist/preview-server/routes/register-client-render.d.ts +3 -0
  32. package/dist/preview-server/routes/register-client-render.js +11 -0
  33. package/dist/preview-server/routes/save-effect-props.js +1 -0
  34. package/dist/preview-server/routes/save-sequence-props.js +158 -72
  35. package/dist/preview-server/routes/unregister-client-render.d.ts +4 -0
  36. package/dist/preview-server/routes/unregister-client-render.js +11 -0
  37. package/dist/preview-server/routes/update-default-props.js +1 -0
  38. package/dist/preview-server/start-server.d.ts +1 -0
  39. package/dist/preview-server/start-server.js +1 -0
  40. package/dist/preview-server/undo-stack.d.ts +26 -3
  41. package/dist/preview-server/undo-stack.js +130 -42
  42. package/dist/preview-server/validate-same-origin.d.ts +2 -0
  43. package/dist/preview-server/validate-same-origin.js +13 -0
  44. package/dist/routes.d.ts +2 -1
  45. package/dist/routes.js +9 -136
  46. package/dist/start-studio.d.ts +2 -1
  47. package/dist/start-studio.js +2 -1
  48. package/package.json +6 -6
@@ -14,87 +14,173 @@ const can_update_sequence_props_1 = require("./can-update-sequence-props");
14
14
  const format_prop_change_1 = require("./log-updates/format-prop-change");
15
15
  const log_update_1 = require("./log-updates/log-update");
16
16
  const save_props_mutex_1 = require("./save-props-mutex");
17
- const saveSequencePropsHandler = ({ input: { fileName, nodePath, key, value, defaultValue, schema, clientId }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
18
- renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-sequence-props] Received request for fileName="${fileName}" key="${key}"`);
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, formatted, logLine, removedProps } = await (0, update_sequence_props_1.updateSequenceProps)({
26
- input: fileContents,
27
- nodePath: nodePath.nodePath,
28
- updates: [
29
- {
30
- key,
31
- value: JSON.parse(value),
32
- defaultValue: defaultValue !== null ? JSON.parse(defaultValue) : null,
33
- },
34
- ],
35
- schema: no_react_1.NoReactInternals.sequenceSchema,
36
- });
37
- const oldValueString = oldValueStrings[0];
38
- const newValueString = JSON.stringify(JSON.parse(value));
39
- const parsedDefault = defaultValue !== null ? JSON.parse(defaultValue) : null;
40
- const defaultValueString = parsedDefault !== null ? JSON.stringify(parsedDefault) : null;
41
- const normalizedOld = (0, log_update_1.normalizeQuotes)(oldValueString);
42
- const normalizedNew = (0, log_update_1.normalizeQuotes)(newValueString);
43
- const normalizedDefault = defaultValueString !== null ? (0, log_update_1.normalizeQuotes)(defaultValueString) : null;
44
- const undoPropChange = (0, format_prop_change_1.formatPropChange)({
45
- key,
46
- oldValueString: normalizedNew,
47
- newValueString: normalizedOld,
48
- defaultValueString: normalizedDefault,
49
- removedProps: [],
50
- addedProps: removedProps,
51
- });
52
- const redoPropChange = (0, format_prop_change_1.formatPropChange)({
53
- key,
54
- oldValueString: normalizedOld,
55
- newValueString: normalizedNew,
56
- defaultValueString: normalizedDefault,
57
- removedProps,
58
- addedProps: [],
59
- });
60
- (0, undo_stack_1.pushToUndoStack)({
61
- filePath: absolutePath,
62
- oldContents: fileContents,
17
+ const saveSequencePropsHandler = ({ input: { edits, clientId, undoLabel, redoLabel }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
18
+ var _a;
19
+ if (edits.length === 0) {
20
+ throw new Error('No sequence prop edits to save');
21
+ }
22
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[save-sequence-props] Received request with ${edits.length} edit(s)`);
23
+ const editGroups = new Map();
24
+ for (const [index, edit] of edits.entries()) {
25
+ const parsedValue = JSON.parse(edit.value);
26
+ const parsedDefaultValue = edit.defaultValue !== null ? JSON.parse(edit.defaultValue) : null;
27
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
28
+ remotionRoot,
29
+ fileName: edit.fileName,
30
+ action: 'modify',
31
+ });
32
+ const group = (_a = editGroups.get(absolutePath)) !== null && _a !== void 0 ? _a : {
33
+ fileRelativeToRoot,
34
+ edits: [],
35
+ };
36
+ group.edits.push({
37
+ index,
38
+ fileName: edit.fileName,
39
+ nodePath: edit.nodePath,
40
+ key: edit.key,
41
+ value: parsedValue,
42
+ valueString: JSON.stringify(parsedValue),
43
+ defaultValue: parsedDefaultValue,
44
+ defaultValueString: parsedDefaultValue !== null
45
+ ? JSON.stringify(parsedDefaultValue)
46
+ : null,
47
+ schema: edit.schema,
48
+ });
49
+ editGroups.set(absolutePath, group);
50
+ }
51
+ const snapshots = [];
52
+ const outputByPath = new Map();
53
+ const resultByIndex = new Map();
54
+ for (const [absolutePath, group] of editGroups) {
55
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
56
+ const { output, formatted, results: updateResults, } = await (0, update_sequence_props_1.updateMultipleSequenceProps)({
57
+ input: fileContents,
58
+ changes: group.edits.map((edit) => {
59
+ return {
60
+ nodePath: edit.nodePath.nodePath,
61
+ updates: [
62
+ {
63
+ key: edit.key,
64
+ value: edit.value,
65
+ defaultValue: edit.defaultValue,
66
+ },
67
+ ],
68
+ schema: no_react_1.NoReactInternals.sequenceSchema,
69
+ };
70
+ }),
71
+ prettierConfigOverride: null,
72
+ });
73
+ const [{ logLine: firstLogLine }] = updateResults;
74
+ outputByPath.set(absolutePath, output);
75
+ snapshots.push({
76
+ filePath: absolutePath,
77
+ oldContents: fileContents,
78
+ newContents: output,
79
+ logLine: firstLogLine,
80
+ });
81
+ for (const [resultIndex, result] of updateResults.entries()) {
82
+ const edit = group.edits[resultIndex];
83
+ resultByIndex.set(edit.index, {
84
+ oldValueString: result.oldValueStrings[0],
85
+ logLine: result.logLine,
86
+ removedProps: result.removedProps,
87
+ formatted,
88
+ });
89
+ }
90
+ }
91
+ const [firstEdit] = edits;
92
+ const firstResult = resultByIndex.get(0);
93
+ if (!firstResult) {
94
+ throw new Error('Could not compute sequence prop edit result');
95
+ }
96
+ const undoMessage = undoLabel !== null
97
+ ? `↩️ ${undoLabel}`
98
+ : edits.length === 1
99
+ ? `↩️ ${(0, format_prop_change_1.formatPropChange)({
100
+ key: firstEdit.key,
101
+ oldValueString: (0, log_update_1.normalizeQuotes)(JSON.stringify(JSON.parse(firstEdit.value))),
102
+ newValueString: (0, log_update_1.normalizeQuotes)(firstResult.oldValueString),
103
+ defaultValueString: firstEdit.defaultValue !== null
104
+ ? (0, log_update_1.normalizeQuotes)(JSON.stringify(JSON.parse(firstEdit.defaultValue)))
105
+ : null,
106
+ removedProps: [],
107
+ addedProps: firstResult.removedProps,
108
+ })}`
109
+ : '↩️ Update selected sequence props';
110
+ const redoMessage = redoLabel !== null
111
+ ? `↪️ ${redoLabel}`
112
+ : edits.length === 1
113
+ ? `↪️ ${(0, format_prop_change_1.formatPropChange)({
114
+ key: firstEdit.key,
115
+ oldValueString: (0, log_update_1.normalizeQuotes)(firstResult.oldValueString),
116
+ newValueString: (0, log_update_1.normalizeQuotes)(JSON.stringify(JSON.parse(firstEdit.value))),
117
+ defaultValueString: firstEdit.defaultValue !== null
118
+ ? (0, log_update_1.normalizeQuotes)(JSON.stringify(JSON.parse(firstEdit.defaultValue)))
119
+ : null,
120
+ removedProps: firstResult.removedProps,
121
+ addedProps: [],
122
+ })}`
123
+ : '↪️ Update selected sequence props';
124
+ (0, undo_stack_1.pushTransactionToUndoStack)({
125
+ snapshots,
63
126
  logLevel,
64
127
  remotionRoot,
65
- logLine,
66
- description: {
67
- undoMessage: `↩️ ${undoPropChange}`,
68
- redoMessage: `↪️ ${redoPropChange}`,
69
- },
128
+ description: { undoMessage, redoMessage },
70
129
  entryType: 'sequence-props',
71
130
  suppressHmrOnFileRestore: true,
72
131
  });
73
- (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
74
- (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
75
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output, clientId);
76
- (0, log_update_1.logUpdate)({
77
- fileRelativeToRoot,
78
- line: logLine,
79
- key,
80
- oldValueString,
81
- newValueString,
82
- defaultValueString,
83
- formatted,
84
- logLevel,
85
- removedProps,
86
- addedProps: [],
87
- });
132
+ for (const [absolutePath, output] of outputByPath) {
133
+ (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
134
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(absolutePath);
135
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(absolutePath, output, clientId);
136
+ }
137
+ for (const { edits: groupEdits, fileRelativeToRoot } of editGroups.values()) {
138
+ for (const edit of groupEdits) {
139
+ const result = resultByIndex.get(edit.index);
140
+ if (!result) {
141
+ throw new Error('Could not compute sequence prop edit result');
142
+ }
143
+ (0, log_update_1.logUpdate)({
144
+ fileRelativeToRoot,
145
+ line: result.logLine,
146
+ key: edit.key,
147
+ oldValueString: result.oldValueString,
148
+ newValueString: edit.valueString,
149
+ defaultValueString: edit.defaultValueString,
150
+ formatted: result.formatted,
151
+ logLevel,
152
+ removedProps: result.removedProps,
153
+ addedProps: [],
154
+ });
155
+ }
156
+ }
88
157
  (0, undo_stack_1.printUndoHint)(logLevel);
89
- const newStatus = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
90
- fileContents: output,
91
- keys: (0, studio_shared_1.getAllSchemaKeys)(schema),
92
- nodePath: nodePath.nodePath,
93
- effects: [],
158
+ const results = edits.map((edit) => {
159
+ const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
160
+ remotionRoot,
161
+ fileName: edit.fileName,
162
+ action: 'modify',
163
+ });
164
+ const output = outputByPath.get(absolutePath);
165
+ if (!output) {
166
+ throw new Error('Could not compute sequence prop edit status');
167
+ }
168
+ const newStatus = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
169
+ fileContents: output,
170
+ keys: (0, studio_shared_1.getAllSchemaKeys)(edit.schema),
171
+ nodePath: edit.nodePath.nodePath,
172
+ effects: [],
173
+ });
174
+ return {
175
+ fileName: edit.fileName,
176
+ nodePath: edit.nodePath,
177
+ props: newStatus.props,
178
+ };
94
179
  });
95
180
  return {
96
181
  canUpdate: true,
97
- props: newStatus.props,
182
+ props: results[0].props,
183
+ results,
98
184
  };
99
185
  });
100
186
  exports.saveSequencePropsHandler = saveSequencePropsHandler;
@@ -0,0 +1,4 @@
1
+ import type { ApiHandler } from '../api-types';
2
+ export declare const unregisterClientRenderHandler: ApiHandler<{
3
+ id: string;
4
+ }, void>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unregisterClientRenderHandler = void 0;
4
+ const client_render_queue_1 = require("../../client-render-queue");
5
+ const validate_same_origin_1 = require("../validate-same-origin");
6
+ const unregisterClientRenderHandler = ({ input: { id }, request, }) => {
7
+ (0, validate_same_origin_1.validateSameOrigin)(request);
8
+ (0, client_render_queue_1.removeCompletedClientRender)(id);
9
+ return Promise.resolve();
10
+ };
11
+ exports.unregisterClientRenderHandler = unregisterClientRenderHandler;
@@ -33,6 +33,7 @@ const updateDefaultPropsHandler = async ({ input: { compositionId, defaultProps,
33
33
  (0, undo_stack_1.pushToUndoStack)({
34
34
  filePath: projectInfo.rootFile,
35
35
  oldContents: fileContents,
36
+ newContents: null,
36
37
  logLevel,
37
38
  remotionRoot,
38
39
  logLine,
@@ -38,6 +38,7 @@ export declare const startServer: (options: {
38
38
  binariesDirectory: string | null;
39
39
  forceIPv4: boolean;
40
40
  audioLatencyHint: AudioContextLatencyCategory | null;
41
+ previewSampleRate: number | null;
41
42
  enableCrossSiteIsolation: boolean;
42
43
  askAIEnabled: boolean;
43
44
  forceNew: boolean;
@@ -102,6 +102,7 @@ const startServer = async (options) => {
102
102
  gitSource: options.gitSource,
103
103
  binariesDirectory: options.binariesDirectory,
104
104
  audioLatencyHint: options.audioLatencyHint,
105
+ previewSampleRate: options.previewSampleRate,
105
106
  enableCrossSiteIsolation: options.enableCrossSiteIsolation,
106
107
  });
107
108
  })
@@ -4,11 +4,18 @@ export interface UndoEntryDescription {
4
4
  redoMessage: string;
5
5
  }
6
6
  type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props' | 'effect-props' | 'delete-effect' | 'delete-jsx-node' | 'duplicate-jsx-node' | 'delete-composition' | 'rename-composition' | 'duplicate-composition';
7
- type UndoEntry = {
7
+ type UndoEntrySnapshot = {
8
8
  filePath: string;
9
9
  oldContents: string;
10
+ newContents: string | null;
11
+ /** 1-based source line for terminal/IDE file links (e.g. path:line). */
12
+ logLine: number;
13
+ };
14
+ type UndoEntry = {
15
+ filePath: string;
10
16
  /** 1-based source line for terminal/IDE file links (e.g. path:line). */
11
17
  logLine: number;
18
+ snapshots: UndoEntrySnapshot[];
12
19
  description: UndoEntryDescription;
13
20
  /** When true, undo/redo file restores call `suppressBundlerUpdateForFile` (skip HMR refresh). */
14
21
  suppressHmrOnFileRestore: boolean;
@@ -33,9 +40,10 @@ type UndoEntry = {
33
40
  } | {
34
41
  entryType: 'duplicate-composition';
35
42
  });
36
- export declare function pushToUndoStack({ filePath, oldContents, logLevel, remotionRoot, logLine, description, entryType, suppressHmrOnFileRestore }: {
43
+ export declare function pushToUndoStack({ filePath, oldContents, newContents, logLevel, remotionRoot, logLine, description, entryType, suppressHmrOnFileRestore }: {
37
44
  filePath: string;
38
45
  oldContents: string;
46
+ newContents: string | null;
39
47
  logLevel: LogLevel;
40
48
  remotionRoot: string;
41
49
  logLine: number;
@@ -43,10 +51,24 @@ export declare function pushToUndoStack({ filePath, oldContents, logLevel, remot
43
51
  entryType: UndoEntryType;
44
52
  suppressHmrOnFileRestore: boolean;
45
53
  }): void;
54
+ export declare function pushTransactionToUndoStack({ snapshots, logLevel, remotionRoot, description, entryType, suppressHmrOnFileRestore }: {
55
+ snapshots: Array<{
56
+ filePath: string;
57
+ oldContents: string;
58
+ newContents: string | null;
59
+ logLine: number;
60
+ }>;
61
+ logLevel: LogLevel;
62
+ remotionRoot: string;
63
+ description: UndoEntryDescription;
64
+ entryType: UndoEntryType;
65
+ suppressHmrOnFileRestore: boolean;
66
+ }): void;
46
67
  export declare function printUndoHint(logLevel: LogLevel): void;
47
- export declare function pushToRedoStack({ filePath, oldContents, logLine, description, entryType, suppressHmrOnFileRestore }: {
68
+ export declare function pushToRedoStack({ filePath, oldContents, newContents, logLine, description, entryType, suppressHmrOnFileRestore }: {
48
69
  filePath: string;
49
70
  oldContents: string;
71
+ newContents: string | null;
50
72
  logLine: number;
51
73
  description: UndoEntryDescription;
52
74
  entryType: UndoEntryType;
@@ -67,4 +89,5 @@ export declare function popRedo(): {
67
89
  };
68
90
  export declare function getUndoStack(): readonly UndoEntry[];
69
91
  export declare function getRedoStack(): readonly UndoEntry[];
92
+ export declare function clearUndoStackForTests(): void;
70
93
  export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pushToUndoStack = pushToUndoStack;
4
+ exports.pushTransactionToUndoStack = pushTransactionToUndoStack;
4
5
  exports.printUndoHint = printUndoHint;
5
6
  exports.pushToRedoStack = pushToRedoStack;
6
7
  exports.suppressUndoStackInvalidation = suppressUndoStackInvalidation;
@@ -8,6 +9,7 @@ exports.popUndo = popUndo;
8
9
  exports.popRedo = popRedo;
9
10
  exports.getUndoStack = getUndoStack;
10
11
  exports.getRedoStack = getRedoStack;
12
+ exports.clearUndoStackForTests = clearUndoStackForTests;
11
13
  const node_fs_1 = require("node:fs");
12
14
  const renderer_1 = require("@remotion/renderer");
13
15
  const parse_ast_1 = require("../codemods/parse-ast");
@@ -35,23 +37,60 @@ function broadcastState() {
35
37
  });
36
38
  });
37
39
  }
38
- function pushToUndoStack({ filePath, oldContents, logLevel, remotionRoot, logLine, description, entryType, suppressHmrOnFileRestore, }) {
40
+ const entryTouchesFile = (entry, filePath) => {
41
+ return entry.snapshots.some((snapshot) => snapshot.filePath === filePath);
42
+ };
43
+ const getEntryFilePaths = (entry) => {
44
+ return entry.snapshots.map((snapshot) => snapshot.filePath);
45
+ };
46
+ const makeUndoEntry = ({ snapshots, description, entryType, suppressHmrOnFileRestore, }) => {
47
+ if (snapshots.length === 0) {
48
+ throw new Error('Cannot create an undo entry without snapshots');
49
+ }
50
+ return {
51
+ filePath: snapshots[0].filePath,
52
+ logLine: snapshots[0].logLine,
53
+ snapshots,
54
+ description,
55
+ entryType,
56
+ suppressHmrOnFileRestore,
57
+ };
58
+ };
59
+ function pushToUndoStack({ filePath, oldContents, newContents, logLevel, remotionRoot, logLine, description, entryType, suppressHmrOnFileRestore, }) {
60
+ pushTransactionToUndoStack({
61
+ snapshots: [
62
+ {
63
+ filePath,
64
+ oldContents,
65
+ newContents,
66
+ logLine,
67
+ },
68
+ ],
69
+ logLevel,
70
+ remotionRoot,
71
+ description,
72
+ entryType,
73
+ suppressHmrOnFileRestore,
74
+ });
75
+ }
76
+ function pushTransactionToUndoStack({ snapshots, logLevel, remotionRoot, description, entryType, suppressHmrOnFileRestore, }) {
39
77
  storedLogLevel = logLevel;
40
78
  storedRemotionRoot = remotionRoot;
41
- undoStack.push({
42
- filePath,
43
- oldContents,
44
- logLine,
79
+ const entry = makeUndoEntry({
80
+ snapshots,
45
81
  description,
46
82
  entryType,
47
83
  suppressHmrOnFileRestore,
48
84
  });
85
+ undoStack.push(entry);
49
86
  if (undoStack.length > MAX_ENTRIES) {
50
87
  undoStack.shift();
51
88
  }
52
89
  redoStack.length = 0;
53
- renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray(`Undo stack: added entry for ${filePath} (${undoStack.length} items)`));
54
- ensureWatching(filePath);
90
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray(`Undo stack: added entry for ${entry.filePath} (${undoStack.length} items)`));
91
+ for (const filePath of getEntryFilePaths(entry)) {
92
+ ensureWatching(filePath);
93
+ }
55
94
  broadcastState();
56
95
  }
57
96
  function printUndoHint(logLevel) {
@@ -61,20 +100,28 @@ function printUndoHint(logLevel) {
61
100
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray(`Tip: ${shortcut} in Studio to undo`));
62
101
  }
63
102
  }
64
- function pushToRedoStack({ filePath, oldContents, logLine, description, entryType, suppressHmrOnFileRestore, }) {
65
- redoStack.push({
66
- filePath,
67
- oldContents,
68
- logLine,
103
+ function pushToRedoStack({ filePath, oldContents, newContents, logLine, description, entryType, suppressHmrOnFileRestore, }) {
104
+ const entry = makeUndoEntry({
105
+ snapshots: [
106
+ {
107
+ filePath,
108
+ oldContents,
109
+ newContents,
110
+ logLine,
111
+ },
112
+ ],
69
113
  description,
70
114
  entryType,
71
115
  suppressHmrOnFileRestore,
72
116
  });
117
+ redoStack.push(entry);
73
118
  if (redoStack.length > MAX_ENTRIES) {
74
119
  redoStack.shift();
75
120
  }
76
- renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: storedLogLevel }, renderer_1.RenderInternals.chalk.gray(`Redo stack: added entry for ${filePath} (${redoStack.length} items)`));
77
- ensureWatching(filePath);
121
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: storedLogLevel }, renderer_1.RenderInternals.chalk.gray(`Redo stack: added entry for ${entry.filePath} (${redoStack.length} items)`));
122
+ for (const watchedFilePath of getEntryFilePaths(entry)) {
123
+ ensureWatching(watchedFilePath);
124
+ }
78
125
  broadcastState();
79
126
  }
80
127
  function suppressUndoStackInvalidation(filePath) {
@@ -109,7 +156,7 @@ function invalidateForFile(filePath) {
109
156
  let changed = false;
110
157
  let lastUndoIndex = -1;
111
158
  for (let i = undoStack.length - 1; i >= 0; i--) {
112
- if (undoStack[i].filePath === filePath) {
159
+ if (entryTouchesFile(undoStack[i], filePath)) {
113
160
  lastUndoIndex = i;
114
161
  break;
115
162
  }
@@ -122,7 +169,7 @@ function invalidateForFile(filePath) {
122
169
  }
123
170
  let lastRedoIndex = -1;
124
171
  for (let i = redoStack.length - 1; i >= 0; i--) {
125
- if (redoStack[i].filePath === filePath) {
172
+ if (entryTouchesFile(redoStack[i], filePath)) {
126
173
  lastRedoIndex = i;
127
174
  break;
128
175
  }
@@ -140,8 +187,8 @@ function invalidateForFile(filePath) {
140
187
  }
141
188
  function cleanupWatchers() {
142
189
  const filesInStacks = new Set([
143
- ...undoStack.map((e) => e.filePath),
144
- ...redoStack.map((e) => e.filePath),
190
+ ...undoStack.flatMap(getEntryFilePaths),
191
+ ...redoStack.flatMap(getEntryFilePaths),
145
192
  ]);
146
193
  for (const [filePath, watcher] of watchers) {
147
194
  if (!filesInStacks.has(filePath)) {
@@ -182,26 +229,39 @@ function popUndo() {
182
229
  if (!entry) {
183
230
  return { success: false, reason: 'Nothing to undo' };
184
231
  }
185
- const currentContents = (0, node_fs_1.readFileSync)(entry.filePath, 'utf-8');
186
- redoStack.push({
187
- filePath: entry.filePath,
188
- oldContents: currentContents,
189
- logLine: entry.logLine,
232
+ const redoSnapshots = entry.snapshots.map((snapshot) => {
233
+ var _a;
234
+ return {
235
+ ...snapshot,
236
+ newContents: (_a = snapshot.newContents) !== null && _a !== void 0 ? _a : (0, node_fs_1.readFileSync)(snapshot.filePath, 'utf-8'),
237
+ };
238
+ });
239
+ redoStack.push(makeUndoEntry({
240
+ snapshots: redoSnapshots,
190
241
  description: entry.description,
191
242
  entryType: entry.entryType,
192
243
  suppressHmrOnFileRestore: entry.suppressHmrOnFileRestore,
193
- });
194
- suppressUndoStackInvalidation(entry.filePath);
195
- if (entry.suppressHmrOnFileRestore) {
196
- (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(entry.filePath);
244
+ }));
245
+ if (redoStack.length > MAX_ENTRIES) {
246
+ redoStack.shift();
247
+ }
248
+ for (const snapshot of entry.snapshots) {
249
+ suppressUndoStackInvalidation(snapshot.filePath);
250
+ if (entry.suppressHmrOnFileRestore) {
251
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(snapshot.filePath);
252
+ }
253
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(snapshot.filePath, snapshot.oldContents, undefined);
197
254
  }
198
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)(entry.filePath, entry.oldContents, undefined);
199
255
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: storedLogLevel }, renderer_1.RenderInternals.chalk.gray(`Undo: restored ${entry.filePath} (undo: ${undoStack.length}, redo: ${redoStack.length})`));
200
256
  logFileAction(entry.description.undoMessage, entry.filePath, entry.logLine);
201
257
  if (entry.entryType === 'visual-control') {
202
- emitVisualControlChanges(entry.oldContents);
258
+ for (const snapshot of entry.snapshots) {
259
+ emitVisualControlChanges(snapshot.oldContents);
260
+ }
261
+ }
262
+ for (const filePath of getEntryFilePaths(entry)) {
263
+ ensureWatching(filePath);
203
264
  }
204
- ensureWatching(entry.filePath);
205
265
  broadcastState();
206
266
  return { success: true };
207
267
  }
@@ -210,32 +270,60 @@ function popRedo() {
210
270
  if (!entry) {
211
271
  return { success: false, reason: 'Nothing to redo' };
212
272
  }
213
- const currentContents = (0, node_fs_1.readFileSync)(entry.filePath, 'utf-8');
214
- undoStack.push({
215
- filePath: entry.filePath,
216
- oldContents: currentContents,
217
- logLine: entry.logLine,
273
+ const snapshotsWithNewContents = [];
274
+ for (const snapshot of entry.snapshots) {
275
+ if (snapshot.newContents === null) {
276
+ return { success: false, reason: 'Redo entry is incomplete' };
277
+ }
278
+ snapshotsWithNewContents.push({
279
+ ...snapshot,
280
+ newContents: snapshot.newContents,
281
+ });
282
+ }
283
+ undoStack.push(makeUndoEntry({
284
+ snapshots: snapshotsWithNewContents,
218
285
  description: entry.description,
219
286
  entryType: entry.entryType,
220
287
  suppressHmrOnFileRestore: entry.suppressHmrOnFileRestore,
221
- });
222
- suppressUndoStackInvalidation(entry.filePath);
223
- if (entry.suppressHmrOnFileRestore) {
224
- (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(entry.filePath);
288
+ }));
289
+ if (undoStack.length > MAX_ENTRIES) {
290
+ undoStack.shift();
291
+ }
292
+ for (const snapshot of snapshotsWithNewContents) {
293
+ suppressUndoStackInvalidation(snapshot.filePath);
294
+ if (entry.suppressHmrOnFileRestore) {
295
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(snapshot.filePath);
296
+ }
297
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(snapshot.filePath, snapshot.newContents, undefined);
225
298
  }
226
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)(entry.filePath, entry.oldContents, undefined);
227
299
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: storedLogLevel }, renderer_1.RenderInternals.chalk.gray(`Redo: restored ${entry.filePath} (undo: ${undoStack.length}, redo: ${redoStack.length})`));
228
300
  logFileAction(entry.description.redoMessage, entry.filePath, entry.logLine);
229
301
  if (entry.entryType === 'visual-control') {
230
- emitVisualControlChanges(entry.oldContents);
302
+ for (const snapshot of entry.snapshots) {
303
+ if (snapshot.newContents !== null) {
304
+ emitVisualControlChanges(snapshot.newContents);
305
+ }
306
+ }
307
+ }
308
+ for (const filePath of getEntryFilePaths(entry)) {
309
+ ensureWatching(filePath);
231
310
  }
232
- ensureWatching(entry.filePath);
233
311
  broadcastState();
234
312
  return { success: true };
235
313
  }
314
+ /*
315
+ * Keep stack accessors typed as readonly arrays so callers can only inspect
316
+ * whether undo/redo is available and which file represents the top entry.
317
+ */
236
318
  function getUndoStack() {
237
319
  return undoStack;
238
320
  }
239
321
  function getRedoStack() {
240
322
  return redoStack;
241
323
  }
324
+ function clearUndoStackForTests() {
325
+ undoStack.length = 0;
326
+ redoStack.length = 0;
327
+ suppressedWrites.clear();
328
+ cleanupWatchers();
329
+ }
@@ -0,0 +1,2 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ export declare const validateSameOrigin: (req: IncomingMessage) => void;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateSameOrigin = void 0;
4
+ const validateSameOrigin = (req) => {
5
+ const { origin, host } = req.headers;
6
+ if (origin) {
7
+ const originUrl = new URL(origin);
8
+ if (originUrl.host !== host) {
9
+ throw new Error('Request from different origin not allowed');
10
+ }
11
+ }
12
+ };
13
+ exports.validateSameOrigin = validateSameOrigin;
package/dist/routes.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import type { GitSource, RenderDefaults, RenderJob } from '@remotion/studio-shared';
3
3
  import type { QueueMethods } from './preview-server/api-types';
4
4
  import type { LiveEventsServer } from './preview-server/live-events';
5
- export declare const handleRoutes: ({ staticHash, staticHashPrefix, outputHash, outputHashPrefix, request, response, liveEventsServer, getCurrentInputProps, getEnvVariables, remotionRoot, entryPoint, publicDir, logLevel, getRenderQueue, getRenderDefaults, numberOfAudioTags, queueMethods: methods, gitSource, binariesDirectory, audioLatencyHint, enableCrossSiteIsolation, }: {
5
+ export declare const handleRoutes: ({ staticHash, staticHashPrefix, outputHash, outputHashPrefix, request, response, liveEventsServer, getCurrentInputProps, getEnvVariables, remotionRoot, entryPoint, publicDir, logLevel, getRenderQueue, getRenderDefaults, numberOfAudioTags, queueMethods: methods, gitSource, binariesDirectory, audioLatencyHint, previewSampleRate, enableCrossSiteIsolation, }: {
6
6
  staticHash: string;
7
7
  staticHashPrefix: string;
8
8
  outputHash: string;
@@ -23,5 +23,6 @@ export declare const handleRoutes: ({ staticHash, staticHashPrefix, outputHash,
23
23
  gitSource: GitSource | null;
24
24
  binariesDirectory: string | null;
25
25
  audioLatencyHint: AudioContextLatencyCategory | null;
26
+ previewSampleRate: number | null;
26
27
  enableCrossSiteIsolation: boolean;
27
28
  }) => Promise<void>;