@remotion/studio-server 4.0.539 → 4.0.541

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 (126) hide show
  1. package/dist/codemods/get-node-path-remappings.d.ts +5 -2
  2. package/dist/codemods/get-node-path-remappings.js +7 -1
  3. package/dist/file-watcher.d.ts +7 -1
  4. package/dist/file-watcher.js +33 -5
  5. package/dist/index.d.ts +6 -2
  6. package/dist/index.js +4 -0
  7. package/dist/preview-server/api-routes.js +4 -0
  8. package/dist/preview-server/commit-node-codemod.d.ts +27 -0
  9. package/dist/preview-server/commit-node-codemod.js +68 -0
  10. package/dist/preview-server/hot-middleware/index.js +11 -7
  11. package/dist/preview-server/live-events.js +8 -1
  12. package/dist/preview-server/prerendered-scene.d.ts +25 -0
  13. package/dist/preview-server/prerendered-scene.js +86 -0
  14. package/dist/preview-server/resolve-compiled-sequence-paths.d.ts +12 -0
  15. package/dist/preview-server/resolve-compiled-sequence-paths.js +155 -0
  16. package/dist/preview-server/routes/add-effect-keyframe.js +1 -1
  17. package/dist/preview-server/routes/add-keyframes.js +17 -9
  18. package/dist/preview-server/routes/add-render.js +28 -2
  19. package/dist/preview-server/routes/add-sequence-keyframe.js +1 -1
  20. package/dist/preview-server/routes/can-update-sequence-props.js +17 -4
  21. package/dist/preview-server/routes/delete-keyframes.d.ts +2 -2
  22. package/dist/preview-server/routes/delete-keyframes.js +36 -4
  23. package/dist/preview-server/routes/delete-nodes.js +1 -1
  24. package/dist/preview-server/routes/duplicate-nodes.js +3 -1
  25. package/dist/preview-server/routes/get-keyframe-mutation-results.d.ts +7 -0
  26. package/dist/preview-server/routes/get-keyframe-mutation-results.js +24 -0
  27. package/dist/preview-server/routes/invalidate-bundle.js +1 -1
  28. package/dist/preview-server/routes/move-keyframes.d.ts +2 -2
  29. package/dist/preview-server/routes/move-keyframes.js +36 -4
  30. package/dist/preview-server/routes/precompose-jsx-nodes.js +14 -10
  31. package/dist/preview-server/routes/prerendered-scene-info.d.ts +3 -0
  32. package/dist/preview-server/routes/prerendered-scene-info.js +31 -0
  33. package/dist/preview-server/routes/reorder-sequence.js +1 -1
  34. package/dist/preview-server/routes/replace-video-source.js +25 -78
  35. package/dist/preview-server/routes/restore-prerendered-scene.d.ts +3 -0
  36. package/dist/preview-server/routes/restore-prerendered-scene.js +38 -0
  37. package/dist/preview-server/routes/save-effect-props.js +1 -1
  38. package/dist/preview-server/routes/save-sequence-props.js +3 -1
  39. package/dist/preview-server/routes/split-sequences.js +10 -2
  40. package/dist/preview-server/routes/split-video-from-audio.js +1 -1
  41. package/dist/preview-server/routes/subscribe-to-sequence-props.js +50 -15
  42. package/dist/preview-server/routes/unwrap-node.js +1 -1
  43. package/dist/preview-server/routes/update-effect-keyframe-settings.js +1 -1
  44. package/dist/preview-server/routes/update-sequence-keyframe-settings.js +1 -1
  45. package/dist/preview-server/routes/wrap-node.js +1 -1
  46. package/dist/preview-server/sequence-node-path-mutation-build.d.ts +2 -0
  47. package/dist/preview-server/sequence-node-path-mutation-build.js +221 -0
  48. package/dist/preview-server/sequence-node-path-mutation.d.ts +2 -1
  49. package/dist/preview-server/sequence-node-path-mutation.js +14 -2
  50. package/dist/preview-server/sequence-props-watchers.d.ts +2 -1
  51. package/dist/preview-server/sequence-props-watchers.js +11 -4
  52. package/dist/preview-server/serve-static.js +3 -0
  53. package/dist/preview-server/undo-stack.d.ts +1 -1
  54. package/dist/preview-server/undo-stack.js +14 -2
  55. package/dist/preview-server/watch-ignore-next-change.d.ts +1 -1
  56. package/dist/preview-server/watch-ignore-next-change.js +3 -3
  57. package/dist/remotion-skill-names.d.ts +1 -1
  58. package/dist/remotion-skill-names.js +1 -0
  59. package/dist/routes.js +91 -28
  60. package/package.json +10 -10
  61. package/dist/canvas-capture/generate-canvas-capture-composition.d.ts +0 -14
  62. package/dist/canvas-capture/generate-canvas-capture-composition.js +0 -239
  63. package/dist/codemods/add-effect.d.ts +0 -1
  64. package/dist/codemods/add-effect.js +0 -8
  65. package/dist/codemods/apply-codemod-to-file.d.ts +0 -7
  66. package/dist/codemods/apply-codemod-to-file.js +0 -171
  67. package/dist/codemods/apply-visual-control.d.ts +0 -8
  68. package/dist/codemods/apply-visual-control.js +0 -89
  69. package/dist/codemods/delete-effect.d.ts +0 -1
  70. package/dist/codemods/delete-effect.js +0 -6
  71. package/dist/codemods/delete-jsx-node.d.ts +0 -22
  72. package/dist/codemods/delete-jsx-node.js +0 -17
  73. package/dist/codemods/delete-jsx-nodes.d.ts +0 -13
  74. package/dist/codemods/delete-jsx-nodes.js +0 -10
  75. package/dist/codemods/duplicate-composition.d.ts +0 -2
  76. package/dist/codemods/duplicate-composition.js +0 -64
  77. package/dist/codemods/duplicate-effect.d.ts +0 -1
  78. package/dist/codemods/duplicate-effect.js +0 -6
  79. package/dist/codemods/duplicate-jsx-node.d.ts +0 -25
  80. package/dist/codemods/duplicate-jsx-node.js +0 -26
  81. package/dist/codemods/format-file-content.d.ts +0 -7
  82. package/dist/codemods/format-file-content.js +0 -80
  83. package/dist/codemods/format-inline-content.d.ts +0 -18
  84. package/dist/codemods/format-inline-content.js +0 -78
  85. package/dist/codemods/insert-video-layers.d.ts +0 -11
  86. package/dist/codemods/insert-video-layers.js +0 -5
  87. package/dist/codemods/recast-mods.d.ts +0 -13
  88. package/dist/codemods/recast-mods.js +0 -902
  89. package/dist/codemods/reorder-effect.d.ts +0 -1
  90. package/dist/codemods/reorder-effect.js +0 -5
  91. package/dist/codemods/reorder-sequence.d.ts +0 -1
  92. package/dist/codemods/reorder-sequence.js +0 -5
  93. package/dist/codemods/simple-diff.d.ts +0 -5
  94. package/dist/codemods/simple-diff.js +0 -39
  95. package/dist/codemods/split-jsx-sequence.d.ts +0 -27
  96. package/dist/codemods/split-jsx-sequence.js +0 -19
  97. package/dist/codemods/split-video-from-audio.d.ts +0 -1
  98. package/dist/codemods/split-video-from-audio.js +0 -5
  99. package/dist/codemods/update-default-props.d.ts +0 -1
  100. package/dist/codemods/update-default-props.js +0 -6
  101. package/dist/codemods/update-inline-caption-patches.d.ts +0 -1
  102. package/dist/codemods/update-inline-caption-patches.js +0 -5
  103. package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.d.ts +0 -1
  104. package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.js +0 -7
  105. package/dist/open-browser-shortcut.d.ts +0 -9
  106. package/dist/open-browser-shortcut.js +0 -121
  107. package/dist/preview-server/hot-middleware/types.d.ts +0 -2
  108. package/dist/preview-server/hot-middleware/types.js +0 -2
  109. package/dist/preview-server/routes/apply-codemod.d.ts +0 -4
  110. package/dist/preview-server/routes/apply-codemod.js +0 -272
  111. package/dist/preview-server/routes/delete-jsx-node.d.ts +0 -3
  112. package/dist/preview-server/routes/delete-jsx-node.js +0 -126
  113. package/dist/preview-server/routes/delete-jsx-nodes.d.ts +0 -3
  114. package/dist/preview-server/routes/delete-jsx-nodes.js +0 -137
  115. package/dist/preview-server/routes/duplicate-jsx-node.d.ts +0 -3
  116. package/dist/preview-server/routes/duplicate-jsx-node.js +0 -118
  117. package/dist/preview-server/routes/insert-jsx-element.d.ts +0 -3
  118. package/dist/preview-server/routes/insert-jsx-element.js +0 -231
  119. package/dist/preview-server/routes/insert-video-layers.d.ts +0 -3
  120. package/dist/preview-server/routes/insert-video-layers.js +0 -68
  121. package/dist/preview-server/routes/split-jsx-sequence.d.ts +0 -3
  122. package/dist/preview-server/routes/split-jsx-sequence.js +0 -117
  123. package/dist/preview-server/routes/update-public-license.d.ts +0 -11
  124. package/dist/preview-server/routes/update-public-license.js +0 -97
  125. package/dist/preview-server/routes/wrap-jsx-node.d.ts +0 -3
  126. package/dist/preview-server/routes/wrap-jsx-node.js +0 -97
@@ -0,0 +1,221 @@
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.trackSequenceNodePathMutationBuilds = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const file_watcher_1 = require("../file-watcher");
11
+ const sequence_node_path_mutation_1 = require("./sequence-node-path-mutation");
12
+ // Loader-runner and SWC remove a leading BOM before transforming a module.
13
+ const sourceHash = (contents) => (0, node_crypto_1.createHash)('sha256')
14
+ .update(contents.replace(/^\uFEFF/, ''))
15
+ .digest('hex');
16
+ const fastRefreshLoader = node_path_1.default.join(node_path_1.default.dirname(require.resolve('@remotion/bundler')), 'fast-refresh', 'loader.js');
17
+ const compiledSourceHash = (module, file, context) => {
18
+ var _a, _b, _c, _d;
19
+ var _e, _f;
20
+ let map;
21
+ try {
22
+ map = (_a = module.originalSource()) === null || _a === void 0 ? void 0 : _a.map();
23
+ }
24
+ catch (_g) {
25
+ // An override can supply a size-only source without a readable map.
26
+ return null;
27
+ }
28
+ if (!map || ((_b = map.sources) === null || _b === void 0 ? void 0 : _b.length) !== 1)
29
+ return null;
30
+ const name = map.sources[0].replace(/^webpack:\/\//, '');
31
+ if (node_path_1.default.resolve(context, (_e = map.sourceRoot) !== null && _e !== void 0 ? _e : '', name) !== file)
32
+ return null;
33
+ const contents = (_c = map.sourcesContent) === null || _c === void 0 ? void 0 : _c[0];
34
+ if (typeof contents !== 'string')
35
+ return null;
36
+ if ('loaders' in module &&
37
+ module.loaders.some((loader) => loader.loader === fastRefreshLoader)) {
38
+ return ((_f = (_d = contents
39
+ .slice(-96)
40
+ .match(/\/\* remotion-source-sha256:([a-f0-9]{64}) \*\/\s*$/)) === null || _d === void 0 ? void 0 : _d[1]) !== null && _f !== void 0 ? _f : null);
41
+ }
42
+ return sourceHash(contents);
43
+ };
44
+ const trackSequenceNodePathMutationBuilds = (compiler) => {
45
+ let sourceMutationId = null;
46
+ const pending = [];
47
+ const files = new Map();
48
+ const writesThisTurn = new Map();
49
+ let order = 0;
50
+ let building = [];
51
+ const inputs = new Map();
52
+ let resourceHashes = new WeakMap();
53
+ const recordWrite = (state, contents) => {
54
+ state.hashes.set(sourceHash(contents), state.order);
55
+ state.written = state.order;
56
+ };
57
+ const unsubscribeWrites = (0, file_watcher_1.subscribeToStudioSourceWrites)((file, contents) => {
58
+ if (writesThisTurn.size === 0)
59
+ queueMicrotask(() => writesThisTurn.clear());
60
+ writesThisTurn.set(file, contents);
61
+ const state = files.get(file);
62
+ if (state)
63
+ recordWrite(state, contents);
64
+ });
65
+ const unsubscribeMutations = (0, sequence_node_path_mutation_1.subscribeToSequenceNodePathMutations)((mutation) => {
66
+ var _a;
67
+ var _b;
68
+ const affected = mutation.applyWithoutFastRefresh
69
+ ? []
70
+ : mutation.files.map(({ absolutePath }) => absolutePath);
71
+ const next = ++order;
72
+ if (affected.length === 0 && ((_a = pending.at(-1)) === null || _a === void 0 ? void 0 : _a.files.length) === 0)
73
+ pending.pop();
74
+ pending.push({ id: mutation.mutationId, order: next, files: affected });
75
+ for (const file of affected) {
76
+ const state = (_b = files.get(file)) !== null && _b !== void 0 ? _b : {
77
+ order: next,
78
+ written: 0,
79
+ compiled: 0,
80
+ hashes: new Map(),
81
+ };
82
+ state.order = next;
83
+ files.set(file, state);
84
+ // Precompose broadcasts after writing; most structural edits broadcast first.
85
+ const written = writesThisTurn.get(file);
86
+ if (written !== undefined)
87
+ queueMicrotask(() => {
88
+ if (state.order === next && state.written !== next)
89
+ recordWrite(state, written);
90
+ });
91
+ }
92
+ });
93
+ compiler.hooks.shutdown.tap('remotion-source-receipts', () => {
94
+ unsubscribeWrites();
95
+ unsubscribeMutations();
96
+ });
97
+ compiler.hooks.watchRun.tap('remotion-source-receipts', () => {
98
+ var _a;
99
+ var _b;
100
+ building = pending.slice();
101
+ inputs.clear();
102
+ resourceHashes = new WeakMap();
103
+ for (const [file, state] of files) {
104
+ const changed = (_b = (_a = compiler.modifiedFiles) === null || _a === void 0 ? void 0 : _a.has(file)) !== null && _b !== void 0 ? _b : false;
105
+ const hashes = new Map(state.hashes);
106
+ // External edits can supersede a Studio write before compilation begins.
107
+ if (changed && state.written === state.order) {
108
+ try {
109
+ hashes.set(sourceHash((0, node_fs_1.readFileSync)(file, 'utf8')), state.order);
110
+ }
111
+ catch (_c) {
112
+ // The resource can disappear between invalidation and compilation.
113
+ }
114
+ }
115
+ inputs.set(file, { ...state, changed, hashes, modules: [] });
116
+ }
117
+ });
118
+ compiler.hooks.thisCompilation.tap('remotion-source-receipts', (compilation) => {
119
+ if (inputs.size === 0)
120
+ return;
121
+ if (!('rspackVersion' in compiler.webpack)) {
122
+ // FileUriPlugin reads through Webpack's input cache. These exact bytes
123
+ // are available even when the project's loader omits its source map.
124
+ const { readResource } = compiler.webpack.NormalModule.getCompilationHooks(compilation);
125
+ readResource.for(undefined).intercept({
126
+ register(tap) {
127
+ if (tap.name !== 'FileUriPlugin' || tap.type !== 'async')
128
+ return tap;
129
+ const read = tap.fn;
130
+ return {
131
+ ...tap,
132
+ fn: (context, callback) => {
133
+ const module = context._module;
134
+ if (!module || !inputs.has(context.resourcePath))
135
+ return read(context, callback);
136
+ read(context, (error, value) => {
137
+ if (!error && value !== null && value !== undefined)
138
+ resourceHashes.set(module, sourceHash(value.toString()));
139
+ callback(error, value);
140
+ });
141
+ },
142
+ };
143
+ },
144
+ });
145
+ }
146
+ compilation.hooks.succeedModule.tap('remotion-source-receipts', (module) => {
147
+ var _a;
148
+ const file = module.nameForCondition();
149
+ if (file !== null)
150
+ (_a = inputs.get(file)) === null || _a === void 0 ? void 0 : _a.modules.push(module);
151
+ });
152
+ });
153
+ return (statsResult) => {
154
+ var _a;
155
+ if (building.length > 0 && !statsResult.hasErrors()) {
156
+ const { compilation } = statsResult;
157
+ const covered = new Map();
158
+ const cached = new Set();
159
+ for (const [file, input] of inputs) {
160
+ if (input.modules.length === 0 &&
161
+ input.changed &&
162
+ compilation.fileDependencies.has(file))
163
+ cached.add(file);
164
+ }
165
+ // A build can reuse a cached module when opposing writes restore its input.
166
+ if (cached.size > 0)
167
+ for (const module of compilation.modules) {
168
+ const file = module.nameForCondition();
169
+ if (file !== null && cached.has(file))
170
+ (_a = inputs.get(file)) === null || _a === void 0 ? void 0 : _a.modules.push(module);
171
+ }
172
+ for (const [file, input] of inputs) {
173
+ if (input.modules.length === 0) {
174
+ covered.set(file, compilation.fileDependencies.has(file)
175
+ ? input.compiled
176
+ : (0, node_fs_1.existsSync)(file)
177
+ ? input.written
178
+ : input.order);
179
+ continue;
180
+ }
181
+ const revisions = input.modules.map((module) => {
182
+ var _a, _b;
183
+ const hash = (_a = resourceHashes.get(module)) !== null && _a !== void 0 ? _a : compiledSourceHash(module, file, compiler.context);
184
+ if (hash !== null)
185
+ return (_b = input.hashes.get(hash)) !== null && _b !== void 0 ? _b : 0;
186
+ // An opaque override without either authored source signal retains
187
+ // compatibility only for a directly modified, actually rebuilt target.
188
+ return input.changed && !cached.has(file) ? input.written : 0;
189
+ });
190
+ covered.set(file, Math.min(...revisions));
191
+ }
192
+ for (const [file, revision] of covered) {
193
+ const state = files.get(file);
194
+ if (state)
195
+ state.compiled = revision;
196
+ }
197
+ let confirmed = 0;
198
+ for (const mutation of building) {
199
+ if (!mutation.files.every((file) => { var _a; return ((_a = covered.get(file)) !== null && _a !== void 0 ? _a : 0) >= mutation.order; }))
200
+ break;
201
+ sourceMutationId = mutation.id;
202
+ confirmed = mutation.order;
203
+ }
204
+ while (pending[0] && pending[0].order <= confirmed)
205
+ pending.shift();
206
+ if (confirmed > 0)
207
+ for (const [file, state] of files) {
208
+ if (state.order <= confirmed)
209
+ files.delete(file);
210
+ else
211
+ for (const [hash, revision] of state.hashes)
212
+ if (revision <= confirmed)
213
+ state.hashes.delete(hash);
214
+ }
215
+ }
216
+ building = [];
217
+ inputs.clear();
218
+ return sourceMutationId;
219
+ };
220
+ };
221
+ exports.trackSequenceNodePathMutationBuilds = trackSequenceNodePathMutationBuilds;
@@ -1,5 +1,6 @@
1
1
  import type { SequenceNodePathMutation, SequenceNodePathRemapping } from '@remotion/studio-shared';
2
+ export declare const subscribeToSequenceNodePathMutations: (listener: (mutation: SequenceNodePathMutation) => void) => () => void;
2
3
  export declare const broadcastSequenceNodePathMutation: (files: Array<{
3
4
  absolutePath: string;
4
5
  remappings: SequenceNodePathRemapping[];
5
- }>, timelineSelection: SequenceNodePathMutation['timelineSelection']) => SequenceNodePathMutation;
6
+ }>, timelineSelection: SequenceNodePathMutation['timelineSelection'], applyWithoutFastRefresh?: boolean) => SequenceNodePathMutation;
@@ -1,15 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.broadcastSequenceNodePathMutation = void 0;
3
+ exports.broadcastSequenceNodePathMutation = exports.subscribeToSequenceNodePathMutations = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const live_events_1 = require("./live-events");
6
6
  const mutationSessionId = (0, node_crypto_1.randomUUID)();
7
7
  let mutationCounter = 0;
8
- const broadcastSequenceNodePathMutation = (files, timelineSelection) => {
8
+ const listeners = new Set();
9
+ const subscribeToSequenceNodePathMutations = (listener) => {
10
+ listeners.add(listener);
11
+ return () => {
12
+ listeners.delete(listener);
13
+ };
14
+ };
15
+ exports.subscribeToSequenceNodePathMutations = subscribeToSequenceNodePathMutations;
16
+ const broadcastSequenceNodePathMutation = (files, timelineSelection, applyWithoutFastRefresh = false) => {
9
17
  var _a;
10
18
  mutationCounter++;
11
19
  const mutation = {
12
20
  mutationId: `${mutationSessionId}:${mutationCounter}`,
21
+ ...(applyWithoutFastRefresh ? { applyWithoutFastRefresh: true } : {}),
13
22
  timelineSelection,
14
23
  files,
15
24
  };
@@ -17,6 +26,9 @@ const broadcastSequenceNodePathMutation = (files, timelineSelection) => {
17
26
  type: 'sequence-node-paths-remapped',
18
27
  mutation,
19
28
  });
29
+ for (const listener of listeners) {
30
+ listener(mutation);
31
+ }
20
32
  return mutation;
21
33
  };
22
34
  exports.broadcastSequenceNodePathMutation = broadcastSequenceNodePathMutation;
@@ -1,11 +1,12 @@
1
1
  import type { LogLevel } from '@remotion/renderer';
2
2
  import { type SubscribeToSequencePropsResponse } from '@remotion/studio-shared';
3
3
  import type { JsxComponentIdentity, SequenceNodePath } from 'remotion';
4
- export declare const subscribeToSequencePropsWatchers: ({ fileName, line, column, nodePath: preferredNodePath, resolvedNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, fileContentsCache, }: {
4
+ export declare const subscribeToSequencePropsWatchers: ({ fileName, line, column, nodePath: preferredNodePath, requireExactNodePath, resolvedNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, fileContentsCache, }: {
5
5
  fileName: string;
6
6
  line: number;
7
7
  column: number;
8
8
  nodePath: SequenceNodePath | null;
9
+ requireExactNodePath: boolean;
9
10
  resolvedNodePath?: SequenceNodePath | null;
10
11
  componentIdentity: JsxComponentIdentity | null;
11
12
  keys: string[];
@@ -15,7 +15,7 @@ const node_path_cache_1 = require("./node-path-cache");
15
15
  const can_update_sequence_props_1 = require("./routes/can-update-sequence-props");
16
16
  const sequencePropsWatchers = {};
17
17
  const getWatcherKey = (nodePath, assetKeys) => `${(0, studio_shared_1.stringifySequenceSubscriptionKey)(nodePath)}:${assetKeys.join('\0')}`;
18
- const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, resolvedNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, logLevel, fileContentsCache, }) => {
18
+ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, requireExactNodePath, resolvedNodePath, componentIdentity, keys, assetKeys, effects, remotionRoot, logLevel, fileContentsCache, }) => {
19
19
  if (preferredNodePath) {
20
20
  try {
21
21
  const fromNodePath = (0, can_update_sequence_props_1.computeSequencePropsStatus)({
@@ -47,6 +47,9 @@ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, res
47
47
  }
48
48
  }
49
49
  }
50
+ if (requireExactNodePath) {
51
+ return { success: false, status: { canUpdate: false, reason: 'not-found' } };
52
+ }
50
53
  // Try cached nodePath first (handles stale source maps after suppressed rebuilds)
51
54
  const cachedNodePath = (0, node_path_cache_1.getCachedNodePath)(fileName, line, column);
52
55
  if (cachedNodePath) {
@@ -129,13 +132,14 @@ const getSequencePropsStatus = ({ fileName, line, column, preferredNodePath, res
129
132
  });
130
133
  return status;
131
134
  };
132
- const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: preferredNodePath, resolvedNodePath = null, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, fileContentsCache, }) => {
135
+ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: preferredNodePath, requireExactNodePath, resolvedNodePath = null, componentIdentity, keys, assetKeys, effects, remotionRoot, clientId, logLevel, fileContentsCache, }) => {
133
136
  var _a;
134
137
  const initialResult = getSequencePropsStatus({
135
138
  fileName,
136
139
  line,
137
140
  column,
138
141
  preferredNodePath,
142
+ requireExactNodePath,
139
143
  resolvedNodePath,
140
144
  componentIdentity,
141
145
  keys,
@@ -181,9 +185,10 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: pr
181
185
  return;
182
186
  }
183
187
  try {
188
+ const sourceNodePath = watcherInfo.currentNodePath;
184
189
  const result = (0, can_update_sequence_props_1.computeSequencePropsStatusFromContent)({
185
190
  fileContents: event.content,
186
- nodePath: watcherInfo.currentNodePath.nodePath,
191
+ nodePath: sourceNodePath.nodePath,
187
192
  componentIdentity,
188
193
  keys,
189
194
  assetKeys,
@@ -200,7 +205,8 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: pr
200
205
  listener.sendEventToClientId(clientId, {
201
206
  type: 'sequence-props-updated',
202
207
  fileName,
203
- nodePath: watcherInfo.currentNodePath,
208
+ nodePath: sourceNodePath,
209
+ sourceVersion: event.sourceVersion,
204
210
  result,
205
211
  });
206
212
  });
@@ -214,6 +220,7 @@ const subscribeToSequencePropsWatchers = ({ fileName, line, column, nodePath: pr
214
220
  fileName,
215
221
  line,
216
222
  column,
223
+ sourceVersion: event.sourceVersion,
217
224
  });
218
225
  });
219
226
  return;
@@ -89,6 +89,9 @@ const serveStatic = async function ({ root, path, req, res, allowOutsidePublicFo
89
89
  if (!hasRange) {
90
90
  res.setHeader('content-type', renderer_1.RenderInternals.mimeLookup(path) || 'application/octet-stream');
91
91
  res.setHeader('content-length', lstat.size);
92
+ if (req.method === 'HEAD') {
93
+ res.setHeader('x-remotion-file-mtime', lstat.mtimeMs);
94
+ }
92
95
  res.writeHead(200);
93
96
  if (req.method === 'HEAD') {
94
97
  res.end();
@@ -4,7 +4,7 @@ export interface UndoEntryDescription {
4
4
  undoMessage: string;
5
5
  redoMessage: string;
6
6
  }
7
- type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props' | 'effect-props' | 'keyframe-add' | 'keyframe-delete' | 'add-effect' | 'delete-effect' | 'duplicate-effect' | 'paste-effects' | 'reorder-effect' | 'reorder-sequence' | 'delete-nodes' | 'duplicate-nodes' | 'wrap-node' | 'unwrap-node' | 'precompose-jsx-nodes' | 'split-sequences' | 'split-video-from-audio' | 'insert-basic-captions' | 'replace-video-source' | 'insert-composition-element' | 'delete-composition' | 'rename-composition' | 'update-composition-metadata' | 'new-composition' | 'duplicate-composition' | 'move-composition-or-folder' | 'new-folder' | 'delete-folder' | 'rename-folder';
7
+ export type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props' | 'effect-props' | 'keyframe-add' | 'keyframe-delete' | 'add-effect' | 'delete-effect' | 'duplicate-effect' | 'paste-effects' | 'reorder-effect' | 'reorder-sequence' | 'delete-nodes' | 'duplicate-nodes' | 'wrap-node' | 'unwrap-node' | 'precompose-jsx-nodes' | 'split-sequences' | 'split-video-from-audio' | 'insert-basic-captions' | 'replace-video-source' | 'prerender' | 'restore-prerendered-scene' | 'insert-composition-element' | 'delete-composition' | 'rename-composition' | 'update-composition-metadata' | 'new-composition' | 'duplicate-composition' | 'move-composition-or-folder' | 'new-folder' | 'delete-folder' | 'rename-folder';
8
8
  type UndoEntrySnapshot = {
9
9
  filePath: string;
10
10
  oldContents: string | null;
@@ -281,11 +281,21 @@ function popUndo() {
281
281
  ...(remapping.oldJsxName === undefined
282
282
  ? {}
283
283
  : { newJsxName: remapping.oldJsxName }),
284
+ ...(remapping.sourceLocations
285
+ ? {
286
+ sourceLocations: {
287
+ before: remapping.sourceLocations.after,
288
+ after: remapping.sourceLocations.before,
289
+ },
290
+ }
291
+ : {}),
284
292
  })),
285
293
  },
286
294
  ];
287
295
  });
288
- const nodePathMutation = files.length === 0 ? null : (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(files, null);
296
+ const nodePathMutation = files.length === 0
297
+ ? null
298
+ : (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(files, null, entry.suppressHmrOnFileRestore);
289
299
  for (const snapshot of entry.snapshots) {
290
300
  suppressUndoStackInvalidation(snapshot.filePath);
291
301
  if (entry.suppressHmrOnFileRestore) {
@@ -365,7 +375,9 @@ function popRedo() {
365
375
  },
366
376
  ];
367
377
  });
368
- const nodePathMutation = files.length === 0 ? null : (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(files, null);
378
+ const nodePathMutation = files.length === 0
379
+ ? null
380
+ : (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(files, null, entry.suppressHmrOnFileRestore);
369
381
  for (const snapshot of snapshotsWithNewContents) {
370
382
  suppressUndoStackInvalidation(snapshot.filePath);
371
383
  if (entry.suppressHmrOnFileRestore) {
@@ -2,5 +2,5 @@ import type { WatchIgnoreNextChangePlugin } from '@remotion/bundler';
2
2
  export declare const setWatchIgnoreNextChangePlugin: (plugin: WatchIgnoreNextChangePlugin) => void;
3
3
  export declare const suppressBundlerUpdateForFile: (absolutePath: string) => void;
4
4
  export declare const prepareBundlerForFileWrite: (absolutePath: string) => void;
5
- export declare const invalidatePreviouslySuppressedFiles: () => Promise<boolean>;
5
+ export declare const invalidatePreviouslySuppressedFiles: (onlyFile: string | null) => Promise<boolean>;
6
6
  export declare const reloadPreviouslySuppressedFiles: () => Promise<void>;
@@ -24,11 +24,11 @@ const prepareBundlerForFileWrite = (absolutePath) => {
24
24
  }
25
25
  };
26
26
  exports.prepareBundlerForFileWrite = prepareBundlerForFileWrite;
27
- const invalidatePreviouslySuppressedFiles = async () => {
27
+ const invalidatePreviouslySuppressedFiles = async (onlyFile) => {
28
28
  if (!currentPlugin) {
29
29
  return false;
30
30
  }
31
- const files = currentPlugin.consumeSuppressedFilesForRebuild();
31
+ const files = currentPlugin.consumeSuppressedFilesForRebuild(onlyFile);
32
32
  const now = new Date();
33
33
  const touchedFiles = await Promise.all(files.map(async (file) => {
34
34
  try {
@@ -52,6 +52,6 @@ exports.invalidatePreviouslySuppressedFiles = invalidatePreviouslySuppressedFile
52
52
  // The same happens again - infinite loop!
53
53
  // --> When reloading the Studio, it is a good idea to reset Webpack to a non-hacked state.
54
54
  const reloadPreviouslySuppressedFiles = async () => {
55
- await (0, exports.invalidatePreviouslySuppressedFiles)();
55
+ await (0, exports.invalidatePreviouslySuppressedFiles)(null);
56
56
  };
57
57
  exports.reloadPreviouslySuppressedFiles = reloadPreviouslySuppressedFiles;
@@ -1,2 +1,2 @@
1
- export declare const remotionSkillNames: readonly ['remotion-best-practices', 'remotion-captions', 'remotion-create', 'remotion-docs', 'remotion-interactivity', 'remotion-maps', 'remotion-markup', 'remotion-multimedia', 'remotion-render', 'remotion-saas', 'remotion-studio', 'remotion-upgrade'];
1
+ export declare const remotionSkillNames: readonly ['remotion-best-practices', 'remotion-captions', 'remotion-create', 'remotion-docs', 'remotion-interactivity', 'remotion-license', 'remotion-maps', 'remotion-markup', 'remotion-multimedia', 'remotion-render', 'remotion-saas', 'remotion-studio', 'remotion-upgrade'];
2
2
  export type RemotionSkillName = (typeof remotionSkillNames)[number];
@@ -7,6 +7,7 @@ exports.remotionSkillNames = [
7
7
  'remotion-create',
8
8
  'remotion-docs',
9
9
  'remotion-interactivity',
10
+ 'remotion-license',
10
11
  'remotion-maps',
11
12
  'remotion-markup',
12
13
  'remotion-multimedia',
package/dist/routes.js CHANGED
@@ -34,12 +34,15 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleRoutes = void 0;
37
+ const node_crypto_1 = require("node:crypto");
37
38
  const node_fs_1 = require("node:fs");
38
39
  const node_path_1 = __importStar(require("node:path"));
40
+ const promises_1 = require("node:stream/promises");
39
41
  const node_url_1 = require("node:url");
40
42
  const bundler_1 = require("@remotion/bundler");
41
43
  const renderer_1 = require("@remotion/renderer");
42
44
  const studio_shared_1 = require("@remotion/studio-shared");
45
+ const remotion_1 = require("remotion");
43
46
  const better_opn_1 = require("./better-opn");
44
47
  const client_render_queue_1 = require("./client-render-queue");
45
48
  const get_file_source_1 = require("./helpers/get-file-source");
@@ -228,47 +231,87 @@ const handleAddAsset = ({ req, res, search, publicDir, }) => {
228
231
  }
229
232
  return Promise.resolve();
230
233
  };
231
- const handleUploadOutput = ({ req, res, search, remotionRoot, }) => {
234
+ const validateOutputDestination = (rootDirectory, absolutePath) => {
235
+ (0, open_file_for_writing_without_symlinks_1.assertNoSymlinks)({ rootDirectory, absolutePath });
236
+ const existing = (0, node_fs_1.lstatSync)(absolutePath, { throwIfNoEntry: false });
237
+ if (existing && (!existing.isFile() || existing.nlink !== 1)) {
238
+ throw new Error('Not allowed to replace a non-regular or multiply linked output file');
239
+ }
240
+ };
241
+ const handleUploadOutput = async ({ req, res, search, remotionRoot, liveEventsServer, }) => {
232
242
  try {
243
+ if (req.method !== 'POST') {
244
+ res.setHeader('Allow', 'POST');
245
+ res.statusCode = 405;
246
+ res.end(JSON.stringify({ error: 'Output uploads require POST' }));
247
+ return;
248
+ }
233
249
  (0, validate_same_origin_1.validateSameOrigin)(req);
234
250
  const query = new node_url_1.URLSearchParams(search);
235
- const filePath = query.get('filePath');
251
+ let filePath = query.get('filePath');
236
252
  if (typeof filePath !== 'string') {
237
253
  throw new Error('No `filePath` provided');
238
254
  }
255
+ const compositionId = query.get('artifactCompositionId');
256
+ if (compositionId !== null) {
257
+ if (!remotion_1.Internals.isCompositionIdValid(compositionId)) {
258
+ throw new Error('Invalid artifact composition ID');
259
+ }
260
+ (0, studio_shared_1.validateStudioArtifactFilename)(compositionId);
261
+ (0, studio_shared_1.validateStudioArtifactFilename)(filePath);
262
+ filePath = `out/${compositionId}/${filePath}`;
263
+ const outputName = query.get('outputName');
264
+ if (!outputName) {
265
+ throw new Error('No `outputName` provided for artifact upload');
266
+ }
267
+ const outputPath = (0, resolve_output_path_1.resolveOutputPath)(remotionRoot, outputName.replace(/\\/g, '/'));
268
+ const relativeOutput = node_path_1.default
269
+ .relative(remotionRoot, outputPath)
270
+ .split(node_path_1.default.sep)
271
+ .join('/');
272
+ (0, studio_shared_1.validateStudioArtifactFilename)(relativeOutput);
273
+ if ((0, studio_shared_1.getConflictingOutputPaths)([relativeOutput, filePath])) {
274
+ throw new Error('The artifact path conflicts with the main render output');
275
+ }
276
+ }
239
277
  const absolutePath = (0, resolve_output_path_1.resolveOutputPath)(remotionRoot, filePath);
240
- const extension = node_path_1.default.extname(absolutePath).slice(1).toLowerCase();
241
- if (!clientRenderOutputExtensions.has(extension)) {
242
- throw new Error(`Not allowed to upload a .${extension || 'unknown'} file`);
278
+ if (compositionId === null) {
279
+ const extension = node_path_1.default.extname(absolutePath).slice(1).toLowerCase();
280
+ if (!clientRenderOutputExtensions.has(extension)) {
281
+ throw new Error(`Not allowed to upload a .${extension || 'unknown'} file`);
282
+ }
243
283
  }
284
+ validateOutputDestination(remotionRoot, absolutePath);
285
+ const temporaryPath = (0, node_path_1.join)(node_path_1.default.dirname(absolutePath), `.remotion-output-${(0, node_crypto_1.randomUUID)()}.tmp`);
244
286
  const fileDescriptor = (0, open_file_for_writing_without_symlinks_1.openFileForWritingWithoutSymlinks)({
245
287
  rootDirectory: remotionRoot,
246
- absolutePath,
247
- exclusive: false,
248
- });
249
- const writeStream = (0, node_fs_1.createWriteStream)(absolutePath, {
250
- fd: fileDescriptor,
251
- autoClose: true,
252
- });
253
- writeStream.on('close', () => {
254
- res.end(JSON.stringify({ success: true }));
255
- });
256
- writeStream.on('error', (err) => {
257
- res.statusCode = 500;
258
- res.end(JSON.stringify({ error: err.message }));
288
+ absolutePath: temporaryPath,
289
+ exclusive: true,
259
290
  });
260
- req.on('error', (err) => {
261
- writeStream.destroy();
262
- res.statusCode = 500;
263
- res.end(JSON.stringify({ error: err.message }));
291
+ try {
292
+ await (0, promises_1.pipeline)(req, (0, node_fs_1.createWriteStream)(temporaryPath, { fd: fileDescriptor }));
293
+ validateOutputDestination(remotionRoot, temporaryPath);
294
+ validateOutputDestination(remotionRoot, absolutePath);
295
+ (0, node_fs_1.renameSync)(temporaryPath, absolutePath);
296
+ }
297
+ catch (err) {
298
+ (0, open_file_for_writing_without_symlinks_1.assertNoSymlinks)({
299
+ rootDirectory: remotionRoot,
300
+ absolutePath: temporaryPath,
301
+ });
302
+ (0, node_fs_1.rmSync)(temporaryPath, { force: true });
303
+ throw err;
304
+ }
305
+ liveEventsServer.sendEventToClient({
306
+ type: 'render-output-written',
307
+ path: filePath,
264
308
  });
265
- req.pipe(writeStream);
309
+ res.end(JSON.stringify({ success: true }));
266
310
  }
267
311
  catch (err) {
268
312
  res.statusCode = 500;
269
313
  res.end(JSON.stringify({ error: err.message }));
270
314
  }
271
- return Promise.resolve();
272
315
  };
273
316
  const handleFavicon = (_, response) => {
274
317
  const filePath = node_path_1.default.join(__dirname, '..', 'web', 'favicon.png');
@@ -317,6 +360,7 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
317
360
  res: response,
318
361
  search: url.search,
319
362
  remotionRoot,
363
+ liveEventsServer,
320
364
  });
321
365
  }
322
366
  if (url.pathname.startsWith('/api/app-icon/')) {
@@ -453,15 +497,34 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
453
497
  if (url.pathname.startsWith(staticHashPrefix)) {
454
498
  return static404(response);
455
499
  }
456
- if (url.pathname.startsWith(outputHash)) {
457
- const filename = new URL(request.url, 'http://localhost').pathname.replace(new RegExp(`^${outputHash}`), '');
458
- const filePath = (0, node_path_1.join)(remotionRoot, decodeURIComponent(filename));
500
+ if (url.pathname.startsWith(`${outputHash}/`)) {
501
+ let filename;
502
+ try {
503
+ filename = decodeURIComponent(url.pathname.slice(outputHash.length + 1));
504
+ }
505
+ catch (_a) {
506
+ response.writeHead(400);
507
+ response.end('Invalid output path');
508
+ return Promise.resolve();
509
+ }
510
+ const filePath = node_path_1.default.resolve(remotionRoot, filename);
511
+ const outsideProject = !renderer_1.RenderInternals.isPathInside(filePath, remotionRoot);
512
+ const isCompletedOutput = outsideProject &&
513
+ getRenderQueue().some((job) => job.status === 'done' &&
514
+ (job.type === 'video' || job.type === 'still') &&
515
+ !job.deletedOutputLocation &&
516
+ node_path_1.default.resolve(remotionRoot, job.outName) === filePath);
517
+ if (outsideProject && !isCompletedOutput) {
518
+ response.writeHead(404);
519
+ response.end('Output file not found');
520
+ return Promise.resolve();
521
+ }
459
522
  return (0, serve_static_1.serveStatic)({
460
523
  root: remotionRoot,
461
524
  path: filePath,
462
525
  req: request,
463
526
  res: response,
464
- allowOutsidePublicFolder: false,
527
+ allowOutsidePublicFolder: isCompletedOutput,
465
528
  allowRemotionConvertCors: false,
466
529
  });
467
530
  }