@remotion/studio-server 4.0.540 → 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.
- package/dist/codemods/get-node-path-remappings.d.ts +5 -2
- package/dist/codemods/get-node-path-remappings.js +7 -1
- package/dist/file-watcher.d.ts +1 -0
- package/dist/file-watcher.js +9 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/preview-server/api-routes.js +4 -0
- package/dist/preview-server/commit-node-codemod.d.ts +27 -0
- package/dist/preview-server/commit-node-codemod.js +68 -0
- package/dist/preview-server/hot-middleware/index.js +11 -7
- package/dist/preview-server/prerendered-scene.d.ts +25 -0
- package/dist/preview-server/prerendered-scene.js +86 -0
- package/dist/preview-server/resolve-compiled-sequence-paths.d.ts +12 -0
- package/dist/preview-server/resolve-compiled-sequence-paths.js +155 -0
- package/dist/preview-server/routes/add-effect-keyframe.js +1 -1
- package/dist/preview-server/routes/add-keyframes.js +17 -9
- package/dist/preview-server/routes/add-render.js +24 -2
- package/dist/preview-server/routes/add-sequence-keyframe.js +1 -1
- package/dist/preview-server/routes/can-update-sequence-props.js +17 -4
- package/dist/preview-server/routes/delete-nodes.js +1 -1
- package/dist/preview-server/routes/duplicate-nodes.js +1 -1
- package/dist/preview-server/routes/invalidate-bundle.js +1 -1
- package/dist/preview-server/routes/prerendered-scene-info.d.ts +3 -0
- package/dist/preview-server/routes/prerendered-scene-info.js +31 -0
- package/dist/preview-server/routes/reorder-sequence.js +1 -1
- package/dist/preview-server/routes/replace-video-source.js +25 -78
- package/dist/preview-server/routes/restore-prerendered-scene.d.ts +3 -0
- package/dist/preview-server/routes/restore-prerendered-scene.js +38 -0
- package/dist/preview-server/routes/save-effect-props.js +1 -1
- package/dist/preview-server/routes/save-sequence-props.js +3 -1
- package/dist/preview-server/routes/split-sequences.js +1 -1
- package/dist/preview-server/routes/split-video-from-audio.js +1 -1
- package/dist/preview-server/routes/subscribe-to-sequence-props.js +50 -15
- package/dist/preview-server/routes/unwrap-node.js +1 -1
- package/dist/preview-server/routes/update-effect-keyframe-settings.js +1 -1
- package/dist/preview-server/routes/update-sequence-keyframe-settings.js +1 -1
- package/dist/preview-server/routes/wrap-node.js +1 -1
- package/dist/preview-server/sequence-node-path-mutation-build.d.ts +2 -0
- package/dist/preview-server/sequence-node-path-mutation-build.js +221 -0
- package/dist/preview-server/sequence-node-path-mutation.d.ts +2 -1
- package/dist/preview-server/sequence-node-path-mutation.js +14 -2
- package/dist/preview-server/sequence-props-watchers.d.ts +2 -1
- package/dist/preview-server/sequence-props-watchers.js +6 -2
- package/dist/preview-server/serve-static.js +3 -0
- package/dist/preview-server/undo-stack.d.ts +1 -1
- package/dist/preview-server/undo-stack.js +14 -2
- package/dist/preview-server/watch-ignore-next-change.d.ts +1 -1
- package/dist/preview-server/watch-ignore-next-change.js +3 -3
- package/dist/routes.js +91 -28
- package/package.json +8 -8
|
@@ -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
|
|
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,
|
|
@@ -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
|
|
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
|
|
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;
|
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
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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:
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
458
|
-
|
|
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:
|
|
527
|
+
allowOutsidePublicFolder: isCompletedOutput,
|
|
465
528
|
allowRemotionConvertCors: false,
|
|
466
529
|
});
|
|
467
530
|
}
|
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.
|
|
6
|
+
"version": "4.0.541",
|
|
7
7
|
"description": "Run a Remotion Studio with a server backend",
|
|
8
8
|
"main": "dist",
|
|
9
9
|
"scripts": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@remotion/studio-protocol": "4.0.
|
|
26
|
+
"@remotion/studio-protocol": "4.0.541",
|
|
27
27
|
"@babel/types": "7.24.0",
|
|
28
28
|
"@babel/parser": "7.24.1",
|
|
29
29
|
"@svgr/core": "8.1.0",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"kiwi-schema": "0.5.0",
|
|
32
32
|
"semver": "7.5.3",
|
|
33
33
|
"zod": "4.6.7",
|
|
34
|
-
"remotion": "4.0.
|
|
34
|
+
"remotion": "4.0.541",
|
|
35
35
|
"recast": "0.23.21",
|
|
36
|
-
"@remotion/bundler": "4.0.
|
|
37
|
-
"@remotion/renderer": "4.0.
|
|
38
|
-
"@remotion/codemods": "4.0.
|
|
39
|
-
"@remotion/studio-shared": "4.0.
|
|
36
|
+
"@remotion/bundler": "4.0.541",
|
|
37
|
+
"@remotion/renderer": "4.0.541",
|
|
38
|
+
"@remotion/codemods": "4.0.541",
|
|
39
|
+
"@remotion/studio-shared": "4.0.541",
|
|
40
40
|
"memfs": "3.4.3",
|
|
41
41
|
"open": "8.4.2"
|
|
42
42
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"ast-types": "0.16.1",
|
|
45
45
|
"react": "19.3.1",
|
|
46
46
|
"@types/semver": "7.5.3",
|
|
47
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
47
|
+
"@remotion/eslint-config-internal": "4.0.541",
|
|
48
48
|
"eslint": "9.19.0",
|
|
49
49
|
"prettier": "3.8.1",
|
|
50
50
|
"@types/node": "20.12.14",
|