@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.
Files changed (50) 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 +1 -0
  4. package/dist/file-watcher.js +9 -1
  5. package/dist/index.d.ts +4 -0
  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/prerendered-scene.d.ts +25 -0
  12. package/dist/preview-server/prerendered-scene.js +86 -0
  13. package/dist/preview-server/resolve-compiled-sequence-paths.d.ts +12 -0
  14. package/dist/preview-server/resolve-compiled-sequence-paths.js +155 -0
  15. package/dist/preview-server/routes/add-effect-keyframe.js +1 -1
  16. package/dist/preview-server/routes/add-keyframes.js +17 -9
  17. package/dist/preview-server/routes/add-render.js +24 -2
  18. package/dist/preview-server/routes/add-sequence-keyframe.js +1 -1
  19. package/dist/preview-server/routes/can-update-sequence-props.js +17 -4
  20. package/dist/preview-server/routes/delete-nodes.js +1 -1
  21. package/dist/preview-server/routes/duplicate-nodes.js +1 -1
  22. package/dist/preview-server/routes/invalidate-bundle.js +1 -1
  23. package/dist/preview-server/routes/prerendered-scene-info.d.ts +3 -0
  24. package/dist/preview-server/routes/prerendered-scene-info.js +31 -0
  25. package/dist/preview-server/routes/reorder-sequence.js +1 -1
  26. package/dist/preview-server/routes/replace-video-source.js +25 -78
  27. package/dist/preview-server/routes/restore-prerendered-scene.d.ts +3 -0
  28. package/dist/preview-server/routes/restore-prerendered-scene.js +38 -0
  29. package/dist/preview-server/routes/save-effect-props.js +1 -1
  30. package/dist/preview-server/routes/save-sequence-props.js +3 -1
  31. package/dist/preview-server/routes/split-sequences.js +1 -1
  32. package/dist/preview-server/routes/split-video-from-audio.js +1 -1
  33. package/dist/preview-server/routes/subscribe-to-sequence-props.js +50 -15
  34. package/dist/preview-server/routes/unwrap-node.js +1 -1
  35. package/dist/preview-server/routes/update-effect-keyframe-settings.js +1 -1
  36. package/dist/preview-server/routes/update-sequence-keyframe-settings.js +1 -1
  37. package/dist/preview-server/routes/wrap-node.js +1 -1
  38. package/dist/preview-server/sequence-node-path-mutation-build.d.ts +2 -0
  39. package/dist/preview-server/sequence-node-path-mutation-build.js +221 -0
  40. package/dist/preview-server/sequence-node-path-mutation.d.ts +2 -1
  41. package/dist/preview-server/sequence-node-path-mutation.js +14 -2
  42. package/dist/preview-server/sequence-props-watchers.d.ts +2 -1
  43. package/dist/preview-server/sequence-props-watchers.js +6 -2
  44. package/dist/preview-server/serve-static.js +3 -0
  45. package/dist/preview-server/undo-stack.d.ts +1 -1
  46. package/dist/preview-server/undo-stack.js +14 -2
  47. package/dist/preview-server/watch-ignore-next-change.d.ts +1 -1
  48. package/dist/preview-server/watch-ignore-next-change.js +3 -3
  49. package/dist/routes.js +91 -28
  50. package/package.json +8 -8
@@ -1,6 +1,9 @@
1
- import type { File } from '@babel/types';
2
1
  import type { SequenceNodePath } from 'remotion';
3
2
  export type CapturedJsxNodePath = {
4
3
  nodePath: SequenceNodePath;
4
+ location: {
5
+ line: number;
6
+ column: number;
7
+ } | null;
5
8
  };
6
- export declare const captureJsxNodePaths: (ast: File) => CapturedJsxNodePath[];
9
+ export declare const captureJsxNodePaths: (source: string) => CapturedJsxNodePath[];
@@ -34,14 +34,20 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.captureJsxNodePaths = void 0;
37
+ const codemods_1 = require("@remotion/codemods");
37
38
  const recast = __importStar(require("recast"));
38
39
  const can_update_sequence_props_1 = require("../preview-server/routes/can-update-sequence-props");
39
- const captureJsxNodePaths = (ast) => {
40
+ const parse_ast_1 = require("./parse-ast");
41
+ const captureJsxNodePaths = (source) => {
42
+ const ast = (0, parse_ast_1.parseAst)(source);
40
43
  const captured = [];
41
44
  recast.visit(ast, {
42
45
  visitJSXOpeningElement(path) {
43
46
  captured.push({
44
47
  nodePath: (0, can_update_sequence_props_1.getNodePathForRecastPath)(path, ast),
48
+ location: path.node.loc
49
+ ? codemods_1.CodemodsInternals.getJsxSourceLocation(source, path.node.loc.start)
50
+ : null,
45
51
  });
46
52
  return this.traverse(path);
47
53
  },
@@ -23,6 +23,7 @@ export type FileChangeEvent = {
23
23
  sourceVersion: SourceFileVersion | null;
24
24
  };
25
25
  type OnChange = (event: FileChangeEvent) => void;
26
+ export declare const subscribeToStudioSourceWrites: (listener: (file: string, contents: string) => void) => () => boolean;
26
27
  export type FileWatcherRegistry = {
27
28
  readonly sourceVersionSessionId: string;
28
29
  installFileWatcher: (options: {
@@ -33,10 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.writeFileAndNotifyFileWatchers = exports.installFileWatcher = exports.getFileWatcherRegistry = exports.setFileWatcherRegistry = exports.getSourceVersionSessionId = exports.createFileWatcherRegistry = void 0;
36
+ exports.writeFileAndNotifyFileWatchers = exports.installFileWatcher = exports.getFileWatcherRegistry = exports.setFileWatcherRegistry = exports.getSourceVersionSessionId = exports.createFileWatcherRegistry = exports.subscribeToStudioSourceWrites = void 0;
37
37
  const node_crypto_1 = require("node:crypto");
38
38
  const node_fs_1 = __importStar(require("node:fs"));
39
39
  const watch_ignore_next_change_1 = require("./preview-server/watch-ignore-next-change");
40
+ const sourceWriteListeners = new Set();
41
+ const subscribeToStudioSourceWrites = (listener) => {
42
+ sourceWriteListeners.add(listener);
43
+ return () => sourceWriteListeners.delete(listener);
44
+ };
45
+ exports.subscribeToStudioSourceWrites = subscribeToStudioSourceWrites;
40
46
  const getRegistryKey = (file, existenceOnly) => existenceOnly ? `${file}\0existence-only` : file;
41
47
  const createFileWatcherRegistry = () => {
42
48
  const sharedWatchers = new Map();
@@ -148,6 +154,8 @@ const createFileWatcherRegistry = () => {
148
154
  (0, watch_ignore_next_change_1.prepareBundlerForFileWrite)(file);
149
155
  (0, node_fs_1.writeFileSync)(file, content);
150
156
  const sourceVersion = nextSourceVersion();
157
+ for (const listener of sourceWriteListeners)
158
+ listener(file, content);
151
159
  const shared = sharedWatchers.get(getRegistryKey(file, false));
152
160
  if (!shared) {
153
161
  return sourceVersion;
package/dist/index.d.ts CHANGED
@@ -12,7 +12,9 @@ import { getPackageManagerSpawnOptions } from './helpers/package-manager-spawn-o
12
12
  import { getConfiguredMaxTimelineTracks, getMaxTimelineTracks, resetMaxTimelineTracks, setMaxTimelineTracks } from './max-timeline-tracks';
13
13
  import { getPackageManager } from './preview-server/get-package-manager';
14
14
  import { waitForLiveEventsListener } from './preview-server/live-events';
15
+ import { replaceWithPrerenderedVideo } from './preview-server/prerendered-scene';
15
16
  import { getEditorName } from './preview-server/routes/open-in-editor';
17
+ import { subscribeToSequenceNodePathMutations } from './preview-server/sequence-node-path-mutation';
16
18
  import { getRemotionVersion } from './preview-server/update-available';
17
19
  import { remotionSkillNames } from './remotion-skill-names';
18
20
  import { startStudio } from './start-studio';
@@ -71,4 +73,6 @@ export declare const StudioServerInternals: {
71
73
  parseRemotionSkillVersion: typeof parseRemotionSkillVersion;
72
74
  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"];
73
75
  getEditorName: typeof getEditorName;
76
+ replaceWithPrerenderedVideo: typeof replaceWithPrerenderedVideo;
77
+ subscribeToSequenceNodePathMutations: typeof subscribeToSequenceNodePathMutations;
74
78
  };
package/dist/index.js CHANGED
@@ -18,7 +18,9 @@ const package_manager_spawn_options_1 = require("./helpers/package-manager-spawn
18
18
  const max_timeline_tracks_1 = require("./max-timeline-tracks");
19
19
  const get_package_manager_1 = require("./preview-server/get-package-manager");
20
20
  const live_events_1 = require("./preview-server/live-events");
21
+ const prerendered_scene_1 = require("./preview-server/prerendered-scene");
21
22
  const open_in_editor_1 = require("./preview-server/routes/open-in-editor");
23
+ const sequence_node_path_mutation_1 = require("./preview-server/sequence-node-path-mutation");
22
24
  const update_available_1 = require("./preview-server/update-available");
23
25
  const remotion_skill_names_1 = require("./remotion-skill-names");
24
26
  Object.defineProperty(exports, "remotionSkillNames", { enumerable: true, get: function () { return remotion_skill_names_1.remotionSkillNames; } });
@@ -53,4 +55,6 @@ exports.StudioServerInternals = {
53
55
  parseRemotionSkillVersion: detect_outdated_remotion_skills_1.parseRemotionSkillVersion,
54
56
  remotionSkillNames: remotion_skill_names_1.remotionSkillNames,
55
57
  getEditorName: open_in_editor_1.getEditorName,
58
+ replaceWithPrerenderedVideo: prerendered_scene_1.replaceWithPrerenderedVideo,
59
+ subscribeToSequenceNodePathMutations: sequence_node_path_mutation_1.subscribeToSequenceNodePathMutations,
56
60
  };
@@ -42,6 +42,7 @@ const open_in_terminal_1 = require("./routes/open-in-terminal");
42
42
  const paste_effects_1 = require("./routes/paste-effects");
43
43
  const precompose_jsx_nodes_1 = require("./routes/precompose-jsx-nodes");
44
44
  const prepare_element_install_1 = require("./routes/prepare-element-install");
45
+ const prerendered_scene_info_1 = require("./routes/prerendered-scene-info");
45
46
  const project_info_1 = require("./routes/project-info");
46
47
  const redo_1 = require("./routes/redo");
47
48
  const register_client_render_1 = require("./routes/register-client-render");
@@ -55,6 +56,7 @@ const reorder_effect_1 = require("./routes/reorder-effect");
55
56
  const reorder_sequence_1 = require("./routes/reorder-sequence");
56
57
  const replace_video_source_1 = require("./routes/replace-video-source");
57
58
  const restart_studio_1 = require("./routes/restart-studio");
59
+ const restore_prerendered_scene_1 = require("./routes/restore-prerendered-scene");
58
60
  const save_effect_props_1 = require("./routes/save-effect-props");
59
61
  const save_multiple_effect_props_1 = require("./routes/save-multiple-effect-props");
60
62
  const save_sequence_props_1 = require("./routes/save-sequence-props");
@@ -141,6 +143,8 @@ exports.allApiRoutes = {
141
143
  '/api/split-video-from-audio': split_video_from_audio_1.splitVideoFromAudioHandler,
142
144
  '/api/insert-basic-captions': insert_basic_captions_1.insertBasicCaptionsHandler,
143
145
  '/api/replace-video-source': replace_video_source_1.replaceVideoSourceHandler,
146
+ '/api/prerendered-scene-info': prerendered_scene_info_1.prerenderedSceneInfoHandler,
147
+ '/api/restore-prerendered-scene': restore_prerendered_scene_1.restorePrerenderedSceneHandler,
144
148
  '/api/update-available': update_available_1.handleUpdate,
145
149
  '/api/release-notes': release_notes_1.getReleaseNotesHandler,
146
150
  '/api/remotion-skills-info': remotion_skills_info_1.remotionSkillsInfoHandler,
@@ -0,0 +1,27 @@
1
+ import type { CodemodProject, NodeReference } from '@remotion/codemods';
2
+ import type { LogLevel } from '@remotion/renderer';
3
+ import type { SequenceNodePathRemapping } from '@remotion/studio-shared';
4
+ import type { SequenceNodePath } from 'remotion';
5
+ import { type UndoEntryType } from './undo-stack';
6
+ export declare const commitNodeCodemod: <Result extends {
7
+ changes: {
8
+ nextContents: string | null;
9
+ }[];
10
+ nodePathRemappings: SequenceNodePathRemapping[];
11
+ logLine: number;
12
+ }>({ remotionRoot, logLevel, fileName, nodePath, codemod, label, entryType, }: {
13
+ remotionRoot: string;
14
+ logLevel: LogLevel;
15
+ fileName: string;
16
+ nodePath: SequenceNodePath;
17
+ codemod: (options: {
18
+ project: CodemodProject;
19
+ node: NodeReference;
20
+ }) => Result;
21
+ label: string;
22
+ entryType: UndoEntryType;
23
+ }) => Promise<{
24
+ absolutePath: string;
25
+ nodePathMutation: import("@remotion/studio-shared").SequenceNodePathMutation;
26
+ result: Result;
27
+ }>;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.commitNodeCodemod = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const renderer_1 = require("@remotion/renderer");
6
+ const file_watcher_1 = require("../file-watcher");
7
+ const resolve_file_inside_project_1 = require("../helpers/resolve-file-inside-project");
8
+ const format_log_file_location_1 = require("./format-log-file-location");
9
+ const source_file_write_queue_1 = require("./routes/source-file-write-queue");
10
+ const sequence_node_path_mutation_1 = require("./sequence-node-path-mutation");
11
+ const undo_stack_1 = require("./undo-stack");
12
+ const commitNodeCodemod = ({ remotionRoot, logLevel, fileName, nodePath, codemod, label, entryType, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(() => {
13
+ var _a;
14
+ var _b;
15
+ const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
16
+ remotionRoot,
17
+ fileName,
18
+ action: 'modify',
19
+ });
20
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
21
+ const result = codemod({
22
+ project: {
23
+ files: { [absolutePath]: fileContents },
24
+ rootDir: remotionRoot,
25
+ },
26
+ node: { filePath: absolutePath, nodePath },
27
+ });
28
+ const output = (_b = (_a = result.changes[0]) === null || _a === void 0 ? void 0 : _a.nextContents) !== null && _b !== void 0 ? _b : fileContents;
29
+ const { logLine } = result;
30
+ const nodePathRemappings = result.nodePathRemappings.map(({ oldNodePath, newNodePath, oldJsxName, newJsxName }) => ({
31
+ oldNodePath,
32
+ newNodePath,
33
+ oldJsxName,
34
+ newJsxName,
35
+ }));
36
+ const nodePathMutation = (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)([{ absolutePath, remappings: nodePathRemappings }], null);
37
+ (0, undo_stack_1.pushToUndoStack)({
38
+ filePath: absolutePath,
39
+ oldContents: fileContents,
40
+ newContents: output,
41
+ logLevel,
42
+ remotionRoot,
43
+ logLine,
44
+ description: {
45
+ undoMessage: `↩️ ${label}`,
46
+ redoMessage: `↪️ ${label}`,
47
+ },
48
+ entryType,
49
+ suppressHmrOnFileRestore: false,
50
+ nodePathRemappings,
51
+ });
52
+ (0, undo_stack_1.suppressUndoStackInvalidation)(absolutePath);
53
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)({
54
+ file: absolutePath,
55
+ content: output,
56
+ originatorClientId: undefined,
57
+ metadata: { skipSequencePropsUpdate: true },
58
+ });
59
+ const locationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
60
+ remotionRoot,
61
+ absolutePath,
62
+ line: logLine,
63
+ });
64
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${(0, source_file_write_queue_1.getCodemodTimingPrefix)(logLevel)}${renderer_1.RenderInternals.chalk.blueBright(locationLabel)} ${label}`);
65
+ (0, undo_stack_1.printUndoHint)(logLevel);
66
+ return { absolutePath, nodePathMutation, result };
67
+ });
68
+ exports.commitNodeCodemod = commitNodeCodemod;
@@ -8,8 +8,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.setupWebpackHmr = void 0;
9
9
  const renderer_1 = require("@remotion/renderer");
10
10
  const hmr_timing_1 = require("../hmr-timing");
11
+ const sequence_node_path_mutation_build_1 = require("../sequence-node-path-mutation-build");
11
12
  const setupWebpackHmr = (compiler, logLevel, liveEventsServer) => {
12
- let latestStats = null;
13
+ let latestBuild = null;
14
+ const getSourceMutationId = (0, sequence_node_path_mutation_build_1.trackSequenceNodePathMutationBuilds)(compiler);
13
15
  const publishHmr = (hmrEvent) => {
14
16
  liveEventsServer.sendEventToClient({ type: 'hmr', hmrEvent });
15
17
  };
@@ -24,7 +26,7 @@ const setupWebpackHmr = (compiler, logLevel, liveEventsServer) => {
24
26
  logLevel,
25
27
  stage: 'compiler-invalid',
26
28
  });
27
- latestStats = null;
29
+ latestBuild = null;
28
30
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, 'Building...');
29
31
  publishHmr({
30
32
  action: 'building',
@@ -36,19 +38,20 @@ const setupWebpackHmr = (compiler, logLevel, liveEventsServer) => {
36
38
  logLevel,
37
39
  stage: 'compiler-done',
38
40
  });
41
+ const sourceMutationId = getSourceMutationId(statsResult);
39
42
  // Keep hold of latest stats so they can be propagated to new clients
40
- latestStats = statsResult;
41
- publishStats('built', latestStats, publishHmr);
43
+ latestBuild = { stats: statsResult, sourceMutationId };
44
+ publishStats('built', statsResult, sourceMutationId, publishHmr);
42
45
  (0, hmr_timing_1.logHmrTiming)({ detail: null, logLevel, stage: 'hmr-stats-published' });
43
46
  }
44
47
  liveEventsServer.addNewClientListener(() => {
45
- if (latestStats) {
46
- publishStats('sync', latestStats, publishHmr);
48
+ if (latestBuild) {
49
+ publishStats('sync', latestBuild.stats, latestBuild.sourceMutationId, publishHmr);
47
50
  }
48
51
  });
49
52
  };
50
53
  exports.setupWebpackHmr = setupWebpackHmr;
51
- function publishStats(action, statsResult, publishHmr) {
54
+ function publishStats(action, statsResult, sourceMutationId, publishHmr) {
52
55
  var _a;
53
56
  // Studio uses a single compiler. Child compilations must not replace the
54
57
  // parent compilation's hash in the HMR message.
@@ -57,6 +60,7 @@ function publishStats(action, statsResult, publishHmr) {
57
60
  action,
58
61
  time: statsResult.endTime - statsResult.startTime,
59
62
  hash: statsResult.hash,
63
+ sourceMutationId,
60
64
  // Build diagnostics are printed by the dev middleware.
61
65
  warnings: [],
62
66
  errors: [],
@@ -0,0 +1,25 @@
1
+ import type { LogLevel } from '@remotion/renderer';
2
+ import type { SequenceNodePath } from 'remotion';
3
+ type PrerenderedSceneSidecar = {
4
+ compositionId: string;
5
+ source: string;
6
+ importStatements: string[];
7
+ sceneFile: string | null;
8
+ sceneFileHash: string | null;
9
+ };
10
+ export declare const getPrerenderedSceneSidecarFile: (videoFile: string) => string;
11
+ export declare const readPrerenderedSceneSidecar: ({ publicDir, src, }: {
12
+ publicDir: string;
13
+ src: string;
14
+ }) => PrerenderedSceneSidecar | null;
15
+ export declare const hashSceneFile: (file: string) => string;
16
+ export declare const replaceWithPrerenderedVideo: ({ remotionRoot, logLevel, fileName, nodePath, src, videoFile, compositionId, }: {
17
+ remotionRoot: string;
18
+ logLevel: LogLevel;
19
+ fileName: string;
20
+ nodePath: SequenceNodePath;
21
+ src: string;
22
+ videoFile: string;
23
+ compositionId: string;
24
+ }) => Promise<void>;
25
+ export {};
@@ -0,0 +1,86 @@
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.replaceWithPrerenderedVideo = exports.hashSceneFile = exports.readPrerenderedSceneSidecar = exports.getPrerenderedSceneSidecarFile = 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 codemods_1 = require("@remotion/codemods");
11
+ const commit_node_codemod_1 = require("./commit-node-codemod");
12
+ const getPrerenderedSceneSidecarFile = (videoFile) => `${videoFile}.json`;
13
+ exports.getPrerenderedSceneSidecarFile = getPrerenderedSceneSidecarFile;
14
+ const readPrerenderedSceneSidecar = ({ publicDir, src, }) => {
15
+ const videoFile = node_path_1.default.resolve(publicDir, src);
16
+ const relativeToPublicDir = node_path_1.default.relative(publicDir, videoFile);
17
+ if (relativeToPublicDir.startsWith('..') ||
18
+ node_path_1.default.isAbsolute(relativeToPublicDir)) {
19
+ return null;
20
+ }
21
+ const sidecarFile = (0, exports.getPrerenderedSceneSidecarFile)(videoFile);
22
+ if (!(0, node_fs_1.existsSync)(sidecarFile)) {
23
+ return null;
24
+ }
25
+ try {
26
+ return JSON.parse((0, node_fs_1.readFileSync)(sidecarFile, 'utf-8'));
27
+ }
28
+ catch (_a) {
29
+ return null;
30
+ }
31
+ };
32
+ exports.readPrerenderedSceneSidecar = readPrerenderedSceneSidecar;
33
+ const hashSceneFile = (file) => (0, node_crypto_1.createHash)('sha256')
34
+ .update((0, node_fs_1.readFileSync)(file, 'utf-8'))
35
+ .digest('hex')
36
+ .slice(0, 16);
37
+ exports.hashSceneFile = hashSceneFile;
38
+ const resolveSceneFile = (importingFile, importSource) => {
39
+ var _a;
40
+ if (!importSource.startsWith('.')) {
41
+ return null;
42
+ }
43
+ const base = node_path_1.default.resolve(node_path_1.default.dirname(importingFile), importSource);
44
+ const extensions = ['.tsx', '.ts', '.jsx', '.js', '.mjs'];
45
+ return ((_a = [
46
+ base,
47
+ ...extensions.map((extension) => base + extension),
48
+ ...extensions.map((extension) => node_path_1.default.join(base, `index${extension}`)),
49
+ ].find((file) => (0, node_fs_1.existsSync)(file) && (0, node_fs_1.statSync)(file).isFile())) !== null && _a !== void 0 ? _a : null);
50
+ };
51
+ const replaceWithPrerenderedVideo = async ({ remotionRoot, logLevel, fileName, nodePath, src, videoFile, compositionId, }) => {
52
+ const { absolutePath, result } = await (0, commit_node_codemod_1.commitNodeCodemod)({
53
+ remotionRoot,
54
+ logLevel,
55
+ fileName,
56
+ nodePath,
57
+ codemod: ({ project, node }) => codemods_1.CodemodsInternals.replaceNodeChild({
58
+ project,
59
+ node,
60
+ element: (0, codemods_1.createElement)({
61
+ component: 'Video',
62
+ importPath: '@remotion/media',
63
+ props: { src: (0, codemods_1.staticFileValue)(src) },
64
+ }),
65
+ }),
66
+ label: 'Rendered and replaced scene',
67
+ entryType: 'prerender',
68
+ });
69
+ const sceneFile = result.sceneImportSource === null
70
+ ? null
71
+ : resolveSceneFile(absolutePath, result.sceneImportSource);
72
+ const sidecar = {
73
+ compositionId,
74
+ source: result.replacedSource,
75
+ importStatements: result.replacedImports,
76
+ sceneFile: sceneFile === null
77
+ ? null
78
+ : node_path_1.default.relative(remotionRoot, sceneFile).split(node_path_1.default.sep).join('/'),
79
+ sceneFileHash: sceneFile === null ? null : (0, exports.hashSceneFile)(sceneFile),
80
+ };
81
+ // Written atomically so that a partially written sidecar is never read
82
+ const sidecarFile = (0, exports.getPrerenderedSceneSidecarFile)(videoFile);
83
+ (0, node_fs_1.writeFileSync)(`${sidecarFile}.tmp`, JSON.stringify(sidecar, null, 2) + '\n');
84
+ (0, node_fs_1.renameSync)(`${sidecarFile}.tmp`, sidecarFile);
85
+ };
86
+ exports.replaceWithPrerenderedVideo = replaceWithPrerenderedVideo;
@@ -0,0 +1,12 @@
1
+ import type { SequenceNodePath } from 'remotion';
2
+ export declare const resolveCompiledSequencePaths: ({ fileName, remotionRoot, fileContentsCache, targets, }: {
3
+ fileName: string;
4
+ remotionRoot: string;
5
+ fileContentsCache: Map<string, string>;
6
+ targets: {
7
+ index: number;
8
+ hash: string;
9
+ line: number;
10
+ column: number;
11
+ }[];
12
+ }) => Map<number, SequenceNodePath | null>;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveCompiledSequencePaths = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const codemods_1 = require("@remotion/codemods");
6
+ const read_source_file_1 = require("../helpers/read-source-file");
7
+ const resolve_file_inside_project_1 = require("../helpers/resolve-file-inside-project");
8
+ const undo_stack_1 = require("./undo-stack");
9
+ const resolveCompiledSequencePaths = ({ fileName, remotionRoot, fileContentsCache, targets, }) => {
10
+ var _a;
11
+ const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
12
+ remotionRoot,
13
+ fileName,
14
+ action: 'read',
15
+ });
16
+ const current = (0, read_source_file_1.readSourceFile)({ absolutePath, fileContentsCache });
17
+ const results = new Map();
18
+ const hashes = new Map();
19
+ const snapshots = new Map();
20
+ const inspect = (contents) => {
21
+ const cached = snapshots.get(contents);
22
+ if (cached !== undefined)
23
+ return cached;
24
+ try {
25
+ const nodes = (0, codemods_1.getNodes)({
26
+ project: { rootDir: remotionRoot, files: { [absolutePath]: contents } },
27
+ filePath: absolutePath,
28
+ });
29
+ const locations = new Map();
30
+ for (const { nodePath, location } of nodes) {
31
+ if (!location)
32
+ continue;
33
+ const key = `${location.line}:${location.column}`;
34
+ locations.set(key, locations.has(key) ? null : nodePath);
35
+ }
36
+ const snapshot = {
37
+ locations,
38
+ structure: JSON.stringify(nodes.map((n) => [n.nodePath, n.tagName, n.componentIdentity])),
39
+ };
40
+ snapshots.set(contents, snapshot);
41
+ return snapshot;
42
+ }
43
+ catch (_a) {
44
+ snapshots.set(contents, null);
45
+ return null;
46
+ }
47
+ };
48
+ const matchingTargets = new Map();
49
+ for (const target of targets) {
50
+ const group = (_a = matchingTargets.get(target.hash)) !== null && _a !== void 0 ? _a : [];
51
+ group.push(target);
52
+ matchingTargets.set(target.hash, group);
53
+ }
54
+ const acceptSnapshot = (contents, towardCurrent) => {
55
+ var _a;
56
+ var _b;
57
+ let hash = hashes.get(contents);
58
+ if (!hash) {
59
+ hash = (0, node_crypto_1.createHash)('sha256').update(contents).digest('hex');
60
+ hashes.set(contents, hash);
61
+ }
62
+ const group = matchingTargets.get(hash);
63
+ if (!group)
64
+ return;
65
+ const locations = (_a = inspect(contents)) === null || _a === void 0 ? void 0 : _a.locations;
66
+ const maps = towardCurrent.map((step) => {
67
+ if (step.remappings === null) {
68
+ // Props edits preserve JSX order, but pasted expressions can insert
69
+ // hooks. Without an explicit mapping every JSX path must still match.
70
+ const before = inspect(step.from);
71
+ const after = inspect(step.to);
72
+ return before !== null && before.structure === (after === null || after === void 0 ? void 0 : after.structure)
73
+ ? new Map()
74
+ : null;
75
+ }
76
+ const paths = new Map();
77
+ const destinations = new Set();
78
+ for (const { oldNodePath, newNodePath } of step.remappings) {
79
+ const from = step.reverse ? newNodePath : oldNodePath;
80
+ const to = step.reverse ? oldNodePath : newNodePath;
81
+ if (to !== null) {
82
+ const destination = JSON.stringify(to);
83
+ if (destinations.has(destination))
84
+ return null;
85
+ destinations.add(destination);
86
+ }
87
+ if (from === null)
88
+ continue;
89
+ const key = JSON.stringify(from);
90
+ if (paths.has(key))
91
+ return null;
92
+ paths.set(key, to);
93
+ }
94
+ // If a different node took a path, its former occupant cannot
95
+ // have survived there, even if the remapping omitted that occupant.
96
+ for (const key of destinations) {
97
+ if (!paths.has(key))
98
+ paths.set(key, null);
99
+ }
100
+ return paths;
101
+ });
102
+ for (const target of group) {
103
+ let nodePath = (_b = locations === null || locations === void 0 ? void 0 : locations.get(`${target.line}:${target.column}`)) !== null && _b !== void 0 ? _b : null;
104
+ for (let i = maps.length - 1; nodePath !== null && i >= 0; i--) {
105
+ const map = maps[i];
106
+ if (map === null) {
107
+ nodePath = null;
108
+ break;
109
+ }
110
+ const moved = map.get(JSON.stringify(nodePath));
111
+ if (moved !== undefined)
112
+ nodePath = moved;
113
+ }
114
+ // An identical source can occur on both sides of Undo. If those
115
+ // histories disagree, that hash cannot identify the live element.
116
+ const previous = results.get(target.index);
117
+ results.set(target.index, previous === undefined ||
118
+ JSON.stringify(previous) === JSON.stringify(nodePath)
119
+ ? nodePath
120
+ : null);
121
+ }
122
+ };
123
+ acceptSnapshot(current, []);
124
+ for (const [entries, reverse] of [
125
+ [(0, undo_stack_1.getUndoStack)(), false],
126
+ [(0, undo_stack_1.getRedoStack)(), true],
127
+ ]) {
128
+ let contents = current;
129
+ const changes = [];
130
+ for (let i = entries.length - 1; i >= 0; i--) {
131
+ const entry = entries[i];
132
+ const snapshot = entry.snapshots.find((s) => s.filePath === absolutePath);
133
+ if (!snapshot)
134
+ continue;
135
+ const from = reverse ? snapshot.newContents : snapshot.oldContents;
136
+ const to = reverse ? snapshot.oldContents : snapshot.newContents;
137
+ if (from === null ||
138
+ to !== contents ||
139
+ (snapshot.nodePathRemappings === null &&
140
+ entry.entryType !== 'sequence-props' &&
141
+ entry.entryType !== 'effect-props'))
142
+ break;
143
+ changes.push({
144
+ from,
145
+ to,
146
+ reverse,
147
+ remappings: snapshot.nodePathRemappings,
148
+ });
149
+ acceptSnapshot(from, changes);
150
+ contents = from;
151
+ }
152
+ }
153
+ return results;
154
+ };
155
+ exports.resolveCompiledSequencePaths = resolveCompiledSequencePaths;
@@ -48,7 +48,7 @@ const addEffectKeyframeHandler = ({ input: { fileName, sequenceNodePath, effectI
48
48
  (0, undo_stack_1.pushToUndoStack)({
49
49
  filePath: absolutePath,
50
50
  oldContents: fileContents,
51
- newContents: null,
51
+ newContents: output,
52
52
  logLevel,
53
53
  remotionRoot,
54
54
  logLine,
@@ -4,7 +4,6 @@ exports.addKeyframesHandler = exports.addKeyframes = void 0;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const renderer_1 = require("@remotion/renderer");
6
6
  const get_node_path_remappings_1 = require("../../codemods/get-node-path-remappings");
7
- const parse_ast_1 = require("../../codemods/parse-ast");
8
7
  const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
9
8
  const file_watcher_1 = require("../../file-watcher");
10
9
  const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
@@ -89,7 +88,7 @@ const addKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remo
89
88
  const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
90
89
  let output = fileContents;
91
90
  let firstLogLine = Number.POSITIVE_INFINITY;
92
- const originalNodes = (0, get_node_path_remappings_1.captureJsxNodePaths)((0, parse_ast_1.parseAst)(fileContents));
91
+ const originalNodes = (0, get_node_path_remappings_1.captureJsxNodePaths)(fileContents);
93
92
  // Keyframe codemods may wrap a concise arrow in a return statement.
94
93
  // JSX nodes remain in order, but their node paths can all change.
95
94
  const currentPathFor = (originalPath) => {
@@ -97,7 +96,7 @@ const addKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remo
97
96
  if (index === -1) {
98
97
  throw new Error('Could not find the original JSX node for keyframing');
99
98
  }
100
- const currentNodes = (0, get_node_path_remappings_1.captureJsxNodePaths)((0, parse_ast_1.parseAst)(output));
99
+ const currentNodes = (0, get_node_path_remappings_1.captureJsxNodePaths)(output);
101
100
  if (currentNodes.length !== originalNodes.length) {
102
101
  throw new Error('Could not remap JSX nodes after keyframing');
103
102
  }
@@ -169,7 +168,7 @@ const addKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remo
169
168
  });
170
169
  }
171
170
  }
172
- const finalNodes = (0, get_node_path_remappings_1.captureJsxNodePaths)((0, parse_ast_1.parseAst)(output));
171
+ const finalNodes = (0, get_node_path_remappings_1.captureJsxNodePaths)(output);
173
172
  if (finalNodes.length !== originalNodes.length) {
174
173
  throw new Error('Could not remap JSX nodes after keyframing');
175
174
  }
@@ -178,11 +177,20 @@ const addKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remo
178
177
  oldContents: fileContents,
179
178
  newContents: output,
180
179
  logLine: Number.isFinite(firstLogLine) ? firstLogLine : 1,
181
- nodePathRemappings: originalNodes.flatMap(({ nodePath }, index) => {
182
- const newNodePath = finalNodes[index].nodePath;
183
- return JSON.stringify(nodePath) === JSON.stringify(newNodePath)
180
+ nodePathRemappings: originalNodes.flatMap(({ location, nodePath }, index) => {
181
+ var _a, _b;
182
+ const next = finalNodes[index];
183
+ return (location === null || location === void 0 ? void 0 : location.line) === ((_a = next.location) === null || _a === void 0 ? void 0 : _a.line) &&
184
+ (location === null || location === void 0 ? void 0 : location.column) === ((_b = next.location) === null || _b === void 0 ? void 0 : _b.column) &&
185
+ JSON.stringify(nodePath) === JSON.stringify(next.nodePath)
184
186
  ? []
185
- : [{ oldNodePath: nodePath, newNodePath }];
187
+ : [
188
+ {
189
+ oldNodePath: nodePath,
190
+ newNodePath: next.nodePath,
191
+ sourceLocations: { before: location, after: next.location },
192
+ },
193
+ ];
186
194
  }),
187
195
  });
188
196
  }
@@ -231,7 +239,7 @@ const addKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remo
231
239
  });
232
240
  }
233
241
  const nodePathMutation = filesWithRemappings.length
234
- ? (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(filesWithRemappings, null)
242
+ ? (0, sequence_node_path_mutation_1.broadcastSequenceNodePathMutation)(filesWithRemappings, null, true)
235
243
  : null;
236
244
  for (const log of sequenceLogs) {
237
245
  (0, log_update_1.logUpdate)({