videowright 0.1.0
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/README.md +91 -0
- package/dist/cli/argv.d.ts +28 -0
- package/dist/cli/argv.d.ts.map +1 -0
- package/dist/cli/argv.js +115 -0
- package/dist/cli/argv.js.map +1 -0
- package/dist/cli/bin.d.ts +7 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +10 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/dev.d.ts +19 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +104 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/discover.d.ts +29 -0
- package/dist/cli/discover.d.ts.map +1 -0
- package/dist/cli/discover.js +104 -0
- package/dist/cli/discover.js.map +1 -0
- package/dist/cli/discover_project.d.ts +29 -0
- package/dist/cli/discover_project.d.ts.map +1 -0
- package/dist/cli/discover_project.js +108 -0
- package/dist/cli/discover_project.js.map +1 -0
- package/dist/cli/errors.d.ts +10 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +13 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/ffmpeg.d.ts +57 -0
- package/dist/cli/ffmpeg.d.ts.map +1 -0
- package/dist/cli/ffmpeg.js +122 -0
- package/dist/cli/ffmpeg.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +152 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/playwright_check.d.ts +44 -0
- package/dist/cli/playwright_check.d.ts.map +1 -0
- package/dist/cli/playwright_check.js +20 -0
- package/dist/cli/playwright_check.js.map +1 -0
- package/dist/cli/prompt.d.ts +13 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +47 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/render.d.ts +60 -0
- package/dist/cli/render.d.ts.map +1 -0
- package/dist/cli/render.js +471 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/script_cmd.d.ts +26 -0
- package/dist/cli/script_cmd.d.ts.map +1 -0
- package/dist/cli/script_cmd.js +88 -0
- package/dist/cli/script_cmd.js.map +1 -0
- package/dist/cli/time_shim.d.ts +44 -0
- package/dist/cli/time_shim.d.ts.map +1 -0
- package/dist/cli/time_shim.js +390 -0
- package/dist/cli/time_shim.js.map +1 -0
- package/dist/cli/ts_loader.d.ts +28 -0
- package/dist/cli/ts_loader.d.ts.map +1 -0
- package/dist/cli/ts_loader.js +95 -0
- package/dist/cli/ts_loader.js.map +1 -0
- package/dist/cli/vite_helpers.d.ts +62 -0
- package/dist/cli/vite_helpers.d.ts.map +1 -0
- package/dist/cli/vite_helpers.js +273 -0
- package/dist/cli/vite_helpers.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/player/hash_router.d.ts +23 -0
- package/dist/player/hash_router.d.ts.map +1 -0
- package/dist/player/hash_router.js +49 -0
- package/dist/player/hash_router.js.map +1 -0
- package/dist/player/hud.d.ts +33 -0
- package/dist/player/hud.d.ts.map +1 -0
- package/dist/player/hud.js +357 -0
- package/dist/player/hud.js.map +1 -0
- package/dist/player/index.d.ts +123 -0
- package/dist/player/index.d.ts.map +1 -0
- package/dist/player/index.js +848 -0
- package/dist/player/index.js.map +1 -0
- package/dist/player/input.d.ts +14 -0
- package/dist/player/input.d.ts.map +1 -0
- package/dist/player/input.js +90 -0
- package/dist/player/input.js.map +1 -0
- package/dist/player/slot.d.ts +22 -0
- package/dist/player/slot.d.ts.map +1 -0
- package/dist/player/slot.js +43 -0
- package/dist/player/slot.js.map +1 -0
- package/dist/player/transitions/cut.d.ts +7 -0
- package/dist/player/transitions/cut.d.ts.map +1 -0
- package/dist/player/transitions/cut.js +9 -0
- package/dist/player/transitions/cut.js.map +1 -0
- package/dist/player/transitions/fade.d.ts +7 -0
- package/dist/player/transitions/fade.d.ts.map +1 -0
- package/dist/player/transitions/fade.js +18 -0
- package/dist/player/transitions/fade.js.map +1 -0
- package/dist/player/transitions/index.d.ts +4 -0
- package/dist/player/transitions/index.d.ts.map +1 -0
- package/dist/player/transitions/index.js +4 -0
- package/dist/player/transitions/index.js.map +1 -0
- package/dist/player/transitions/slide.d.ts +6 -0
- package/dist/player/transitions/slide.d.ts.map +1 -0
- package/dist/player/transitions/slide.js +35 -0
- package/dist/player/transitions/slide.js.map +1 -0
- package/dist/script/index.d.ts +2 -0
- package/dist/script/index.d.ts.map +1 -0
- package/dist/script/index.js +2 -0
- package/dist/script/index.js.map +1 -0
- package/dist/script/script.d.ts +10 -0
- package/dist/script/script.d.ts.map +1 -0
- package/dist/script/script.js +41 -0
- package/dist/script/script.js.map +1 -0
- package/dist/segment/SegmentRunner.d.ts +52 -0
- package/dist/segment/SegmentRunner.d.ts.map +1 -0
- package/dist/segment/SegmentRunner.js +187 -0
- package/dist/segment/SegmentRunner.js.map +1 -0
- package/dist/segment/defineConfig.d.ts +6 -0
- package/dist/segment/defineConfig.d.ts.map +1 -0
- package/dist/segment/defineConfig.js +7 -0
- package/dist/segment/defineConfig.js.map +1 -0
- package/dist/segment/defineSegment.d.ts +7 -0
- package/dist/segment/defineSegment.d.ts.map +1 -0
- package/dist/segment/defineSegment.js +25 -0
- package/dist/segment/defineSegment.js.map +1 -0
- package/dist/segment/index.d.ts +5 -0
- package/dist/segment/index.d.ts.map +1 -0
- package/dist/segment/index.js +4 -0
- package/dist/segment/index.js.map +1 -0
- package/dist/timeline/index.d.ts +73 -0
- package/dist/timeline/index.d.ts.map +1 -0
- package/dist/timeline/index.js +142 -0
- package/dist/timeline/index.js.map +1 -0
- package/dist/timeline/loadAudioTrack.d.ts +18 -0
- package/dist/timeline/loadAudioTrack.d.ts.map +1 -0
- package/dist/timeline/loadAudioTrack.js +44 -0
- package/dist/timeline/loadAudioTrack.js.map +1 -0
- package/dist/timeline/loadVoiceover.d.ts +18 -0
- package/dist/timeline/loadVoiceover.d.ts.map +1 -0
- package/dist/timeline/loadVoiceover.js +38 -0
- package/dist/timeline/loadVoiceover.js.map +1 -0
- package/dist/timeline/resolveTiming.d.ts +28 -0
- package/dist/timeline/resolveTiming.d.ts.map +1 -0
- package/dist/timeline/resolveTiming.js +63 -0
- package/dist/timeline/resolveTiming.js.map +1 -0
- package/dist/timeline/validateTiming.d.ts +29 -0
- package/dist/timeline/validateTiming.d.ts.map +1 -0
- package/dist/timeline/validateTiming.js +62 -0
- package/dist/timeline/validateTiming.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
- package/skill/SKILL.md +64 -0
- package/skill/assets/hello_world/PLAN.md +31 -0
- package/skill/assets/hello_world/README.md +27 -0
- package/skill/assets/hello_world/audio/audio_plan.md +14 -0
- package/skill/assets/hello_world/segments/hello_intro.ts +69 -0
- package/skill/assets/hello_world/segments/hello_outro.ts +71 -0
- package/skill/assets/hello_world/timeline.ts +15 -0
- package/skill/assets/hello_world/voiceover_script/script.md +10 -0
- package/skill/assets/install/package.json +10 -0
- package/skill/assets/install/tsconfig.json +23 -0
- package/skill/assets/styles/editorial-mono/STYLE.md +124 -0
- package/skill/assets/styles/editorial-mono/brand.md +85 -0
- package/skill/assets/styles/editorial-mono/reference/animations.jsx +752 -0
- package/skill/assets/styles/editorial-mono/reference/scenes.html +563 -0
- package/skill/assets/styles/editorial-mono/sample/bullet.ts +101 -0
- package/skill/assets/styles/editorial-mono/sample/content.ts +104 -0
- package/skill/assets/styles/editorial-mono/sample/cta.ts +113 -0
- package/skill/assets/styles/editorial-mono/sample/feature.ts +111 -0
- package/skill/assets/styles/editorial-mono/sample/grid.ts +97 -0
- package/skill/assets/styles/editorial-mono/sample/kinetic.ts +96 -0
- package/skill/assets/styles/editorial-mono/sample/section.ts +101 -0
- package/skill/assets/styles/editorial-mono/sample/stat.ts +128 -0
- package/skill/assets/styles/editorial-mono/sample/title.ts +97 -0
- package/skill/assets/styles/editorial-mono/sample/ui-showcase.ts +159 -0
- package/skill/assets/styles/editorial-mono/tokens.css +44 -0
- package/skill/assets/styles/iso-diagram/STYLE.md +109 -0
- package/skill/assets/styles/iso-diagram/brand.md +32 -0
- package/skill/assets/styles/iso-diagram/reference/animations.jsx +673 -0
- package/skill/assets/styles/iso-diagram/reference/scenes.html +427 -0
- package/skill/assets/styles/iso-diagram/sample/bullet.ts +144 -0
- package/skill/assets/styles/iso-diagram/sample/content.ts +192 -0
- package/skill/assets/styles/iso-diagram/sample/cta.ts +162 -0
- package/skill/assets/styles/iso-diagram/sample/feature.ts +205 -0
- package/skill/assets/styles/iso-diagram/sample/grid.ts +181 -0
- package/skill/assets/styles/iso-diagram/sample/kinetic.ts +102 -0
- package/skill/assets/styles/iso-diagram/sample/section.ts +149 -0
- package/skill/assets/styles/iso-diagram/sample/stat.ts +164 -0
- package/skill/assets/styles/iso-diagram/sample/title.ts +173 -0
- package/skill/assets/styles/iso-diagram/sample/ui-showcase.ts +162 -0
- package/skill/assets/styles/iso-diagram/tokens.css +40 -0
- package/skill/assets/styles/motion-engineering/STYLE.md +106 -0
- package/skill/assets/styles/motion-engineering/brand.md +29 -0
- package/skill/assets/styles/motion-engineering/reference/animations.jsx +673 -0
- package/skill/assets/styles/motion-engineering/reference/scenes.html +513 -0
- package/skill/assets/styles/motion-engineering/sample/bullet.ts +176 -0
- package/skill/assets/styles/motion-engineering/sample/content.ts +228 -0
- package/skill/assets/styles/motion-engineering/sample/cta.ts +209 -0
- package/skill/assets/styles/motion-engineering/sample/feature.ts +299 -0
- package/skill/assets/styles/motion-engineering/sample/grid.ts +190 -0
- package/skill/assets/styles/motion-engineering/sample/kinetic.ts +159 -0
- package/skill/assets/styles/motion-engineering/sample/section.ts +196 -0
- package/skill/assets/styles/motion-engineering/sample/stat.ts +230 -0
- package/skill/assets/styles/motion-engineering/sample/title.ts +219 -0
- package/skill/assets/styles/motion-engineering/sample/ui-showcase.ts +267 -0
- package/skill/assets/styles/motion-engineering/tokens.css +40 -0
- package/skill/assets/styles/neon-terminal/STYLE.md +105 -0
- package/skill/assets/styles/neon-terminal/brand.md +27 -0
- package/skill/assets/styles/neon-terminal/reference/animations.jsx +673 -0
- package/skill/assets/styles/neon-terminal/reference/scenes.html +387 -0
- package/skill/assets/styles/neon-terminal/sample/bullet.ts +113 -0
- package/skill/assets/styles/neon-terminal/sample/content.ts +117 -0
- package/skill/assets/styles/neon-terminal/sample/cta.ts +131 -0
- package/skill/assets/styles/neon-terminal/sample/feature.ts +112 -0
- package/skill/assets/styles/neon-terminal/sample/grid.ts +128 -0
- package/skill/assets/styles/neon-terminal/sample/kinetic.ts +105 -0
- package/skill/assets/styles/neon-terminal/sample/section.ts +96 -0
- package/skill/assets/styles/neon-terminal/sample/stat.ts +123 -0
- package/skill/assets/styles/neon-terminal/sample/title.ts +122 -0
- package/skill/assets/styles/neon-terminal/sample/ui-showcase.ts +127 -0
- package/skill/assets/styles/neon-terminal/tokens.css +39 -0
- package/skill/assets/styles/risograph/STYLE.md +110 -0
- package/skill/assets/styles/risograph/brand.md +26 -0
- package/skill/assets/styles/risograph/reference/animations.jsx +673 -0
- package/skill/assets/styles/risograph/reference/scenes.html +403 -0
- package/skill/assets/styles/risograph/sample/bullet.ts +124 -0
- package/skill/assets/styles/risograph/sample/content.ts +135 -0
- package/skill/assets/styles/risograph/sample/cta.ts +149 -0
- package/skill/assets/styles/risograph/sample/feature.ts +152 -0
- package/skill/assets/styles/risograph/sample/grid.ts +123 -0
- package/skill/assets/styles/risograph/sample/kinetic.ts +125 -0
- package/skill/assets/styles/risograph/sample/section.ts +130 -0
- package/skill/assets/styles/risograph/sample/stat.ts +145 -0
- package/skill/assets/styles/risograph/sample/title.ts +132 -0
- package/skill/assets/styles/risograph/sample/ui-showcase.ts +147 -0
- package/skill/assets/styles/risograph/tokens.css +39 -0
- package/skill/assets/styles/swiss-console/STYLE.md +107 -0
- package/skill/assets/styles/swiss-console/brand.md +37 -0
- package/skill/assets/styles/swiss-console/reference/animations.jsx +673 -0
- package/skill/assets/styles/swiss-console/reference/scenes.html +420 -0
- package/skill/assets/styles/swiss-console/sample/bullet.ts +122 -0
- package/skill/assets/styles/swiss-console/sample/content.ts +137 -0
- package/skill/assets/styles/swiss-console/sample/cta.ts +109 -0
- package/skill/assets/styles/swiss-console/sample/feature.ts +163 -0
- package/skill/assets/styles/swiss-console/sample/grid.ts +145 -0
- package/skill/assets/styles/swiss-console/sample/kinetic.ts +117 -0
- package/skill/assets/styles/swiss-console/sample/section.ts +127 -0
- package/skill/assets/styles/swiss-console/sample/stat.ts +148 -0
- package/skill/assets/styles/swiss-console/sample/title.ts +148 -0
- package/skill/assets/styles/swiss-console/sample/ui-showcase.ts +198 -0
- package/skill/assets/styles/swiss-console/tokens.css +39 -0
- package/skill/install/INSTALL.md +400 -0
- package/skill/references/audio/audio_plan.md +199 -0
- package/skill/references/audio/build.md +208 -0
- package/skill/references/audio/cue_template.md +219 -0
- package/skill/references/audio/ffmpeg_cookbook.md +267 -0
- package/skill/references/audio/music/music.md +171 -0
- package/skill/references/audio/music/providers/elevenlabs.md +170 -0
- package/skill/references/audio/music/providers/manual.md +140 -0
- package/skill/references/audio/music/providers/openverse.md +265 -0
- package/skill/references/audio/sfx/providers/elevenlabs.md +152 -0
- package/skill/references/audio/sfx/providers/manual.md +117 -0
- package/skill/references/audio/sfx/providers/openverse.md +243 -0
- package/skill/references/audio/sfx/sfx.md +149 -0
- package/skill/references/audio/styles.md +102 -0
- package/skill/references/audio/sync.md +237 -0
- package/skill/references/audio/voiceover/animation_sync.md +142 -0
- package/skill/references/audio/voiceover/provider_script.md +153 -0
- package/skill/references/audio/voiceover/providers/elevenlabs.md +288 -0
- package/skill/references/audio/voiceover/providers/manual.md +100 -0
- package/skill/references/audio/voiceover/script_writing.md +100 -0
- package/skill/references/audio/voiceover/style_intake.md +56 -0
- package/skill/references/audio/voiceover/sync_algorithm.md +167 -0
- package/skill/references/audio/voiceover.md +296 -0
- package/skill/references/audio.md +135 -0
- package/skill/references/authoring_segment.md +446 -0
- package/skill/references/create_or_edit_video.md +232 -0
- package/skill/references/dev_server.md +157 -0
- package/skill/references/export.md +145 -0
- package/skill/references/new_video.md +117 -0
- package/skill/references/project_structure.md +144 -0
- package/skill/references/setup.md +109 -0
- package/skill/references/setup_new_style.md +158 -0
- package/skill/references/styles.md +154 -0
- package/skill/references/testing.md +115 -0
- package/skill/references/types.md +240 -0
- package/src/cli/entry/components/copy_button.ts +42 -0
- package/src/cli/entry/components/download_modal.ts +204 -0
- package/src/cli/entry/components/empty_state.ts +55 -0
- package/src/cli/entry/components/hide_hud_tab.ts +37 -0
- package/src/cli/entry/components/icons.ts +31 -0
- package/src/cli/entry/components/top_bar.ts +69 -0
- package/src/cli/entry/components/video_card.ts +57 -0
- package/src/cli/entry/dev_frame.ts +189 -0
- package/src/cli/entry/entry_index.ts +16 -0
- package/src/cli/entry/entry_video.ts +24 -0
- package/src/cli/entry/index.html +12 -0
- package/src/cli/entry/parse_slug.ts +14 -0
- package/src/cli/entry/render.html +17 -0
- package/src/cli/entry/render_entry.ts +121 -0
- package/src/cli/entry/styles/base.css +45 -0
- package/src/cli/entry/styles/components.css +605 -0
- package/src/cli/entry/styles/tokens.css +44 -0
- package/src/cli/entry/video.html +22 -0
- package/src/cli/entry/views/homepage.ts +66 -0
- package/src/cli/entry/views/video_view.ts +286 -0
- package/src/cli/entry/virtual.d.ts +8 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `videowright script` — load a timeline and segments in Node, produce the
|
|
3
|
+
* voiceover script as Markdown.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { script } from "../script/script.js";
|
|
8
|
+
import { discoverProject } from "./discover_project.js";
|
|
9
|
+
import { loadModule } from "./ts_loader.js";
|
|
10
|
+
/**
|
|
11
|
+
* Build a SegmentLoaderMap by walking the cwd/segments/ directory in Node.
|
|
12
|
+
* Each segments/<id>/index.ts becomes an entry in the map.
|
|
13
|
+
* Directories without an index.ts are warned about.
|
|
14
|
+
*/
|
|
15
|
+
export function buildNodeSegmentLoaderMap(cwd) {
|
|
16
|
+
const segmentsDir = join(cwd, "segments");
|
|
17
|
+
const map = new Map();
|
|
18
|
+
if (!existsSync(segmentsDir))
|
|
19
|
+
return map;
|
|
20
|
+
let entries;
|
|
21
|
+
try {
|
|
22
|
+
entries = readdirSync(segmentsDir);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return map;
|
|
26
|
+
}
|
|
27
|
+
const skipped = [];
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
const entryPath = join(segmentsDir, entry);
|
|
30
|
+
// Only consider directories
|
|
31
|
+
try {
|
|
32
|
+
if (!statSync(entryPath).isDirectory())
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const indexPath = join(entryPath, "index.ts");
|
|
39
|
+
if (existsSync(indexPath)) {
|
|
40
|
+
const absPath = resolve(indexPath);
|
|
41
|
+
map.set(entry, async () => {
|
|
42
|
+
const mod = await loadModule(absPath);
|
|
43
|
+
return { default: mod.default };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
skipped.push(entry);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (skipped.length > 0) {
|
|
51
|
+
console.warn(`buildNodeSegmentLoaderMap: skipped directories without index.ts: ${skipped.join(", ")}`);
|
|
52
|
+
}
|
|
53
|
+
return map;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Run the script flow. Returns the markdown and optional write path.
|
|
57
|
+
*/
|
|
58
|
+
export async function runScript(opts) {
|
|
59
|
+
const { cwd, positional, write, verbose } = opts;
|
|
60
|
+
const { configPath, timelinePath } = discoverProject(cwd, positional, "script");
|
|
61
|
+
if (verbose) {
|
|
62
|
+
console.log(`config: ${configPath}`);
|
|
63
|
+
console.log(`timeline: ${timelinePath}`);
|
|
64
|
+
}
|
|
65
|
+
// 3. Load config + timeline (config loaded to validate; not used directly here)
|
|
66
|
+
await loadModule(configPath);
|
|
67
|
+
const timelineMod = await loadModule(timelinePath);
|
|
68
|
+
const timeline = timelineMod.default;
|
|
69
|
+
// 4. Build segment loader map from filesystem
|
|
70
|
+
const segmentLoaders = buildNodeSegmentLoaderMap(cwd);
|
|
71
|
+
// 5. Generate script
|
|
72
|
+
const markdown = await script(timeline, segmentLoaders);
|
|
73
|
+
// 6. Write or return
|
|
74
|
+
let writtenTo;
|
|
75
|
+
if (write) {
|
|
76
|
+
// Infer video folder from timeline path: parent dir of timeline.ts
|
|
77
|
+
const videoDir = dirname(timelinePath);
|
|
78
|
+
const voiceoverScriptDir = join(videoDir, "voiceover_script");
|
|
79
|
+
if (!existsSync(voiceoverScriptDir)) {
|
|
80
|
+
mkdirSync(voiceoverScriptDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
const outputPath = join(voiceoverScriptDir, "script.md");
|
|
83
|
+
writeFileSync(outputPath, markdown, "utf-8");
|
|
84
|
+
writtenTo = outputPath;
|
|
85
|
+
}
|
|
86
|
+
return { markdown, writtenTo };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=script_cmd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script_cmd.js","sourceRoot":"","sources":["../../src/cli/script_cmd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS time injection shim for deterministic rendering.
|
|
3
|
+
*
|
|
4
|
+
* This module exports the shim source code as a string. The render driver
|
|
5
|
+
* injects it into the page via `page.addInitScript()` BEFORE navigation,
|
|
6
|
+
* so it runs in every frame context before any user code.
|
|
7
|
+
*
|
|
8
|
+
* ## Two-mode design
|
|
9
|
+
*
|
|
10
|
+
* The shim operates in two modes:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Passthrough mode** (default, during boot): Virtual clock advances at
|
|
13
|
+
* wall-clock rate. setTimeout, setInterval, requestAnimationFrame all
|
|
14
|
+
* delegate to the REAL browser implementations so that Vite HMR, module
|
|
15
|
+
* loading, library init (lottie-web, echarts, three.js), and the render
|
|
16
|
+
* entry boot() function can complete normally. `performance.now()` and
|
|
17
|
+
* `Date.now()` return real elapsed time since shim install. Real timer
|
|
18
|
+
* IDs and metadata are tracked so they can be converted to virtual
|
|
19
|
+
* timers at mode switch (preserving pending callbacks).
|
|
20
|
+
*
|
|
21
|
+
* 2. **Driver-controlled mode** (during render capture): The driver controls
|
|
22
|
+
* advancement via `__VW_ADVANCE_CLOCK__(deltaMs)`. Timers and RAF
|
|
23
|
+
* callbacks only fire when the driver advances time. WAAPI animations
|
|
24
|
+
* are paused and driven by explicit currentTime updates.
|
|
25
|
+
*
|
|
26
|
+
* The shim starts in passthrough mode. When the Node driver is ready to
|
|
27
|
+
* start capture (after `__VW_RENDER_READY__` is set), it calls
|
|
28
|
+
* `window.__VW_ENGAGE_VIRTUAL_TIME__()` to switch to driver-controlled
|
|
29
|
+
* mode. From that point, real timers stop firing and the driver advances
|
|
30
|
+
* virtual time explicitly.
|
|
31
|
+
*
|
|
32
|
+
* The shim overrides:
|
|
33
|
+
* - Date (no-arg constructor), Date.now
|
|
34
|
+
* - performance.now
|
|
35
|
+
* - setTimeout, setInterval, clearTimeout, clearInterval
|
|
36
|
+
* - requestAnimationFrame, cancelAnimationFrame
|
|
37
|
+
* - Element.prototype.animate (for WAAPI determinism)
|
|
38
|
+
*
|
|
39
|
+
* It exposes:
|
|
40
|
+
* - window.__VW_ADVANCE_CLOCK__(deltaMs) — advance the virtual clock, fire timers/RAFs, update WAAPI
|
|
41
|
+
* - window.__VW_ENGAGE_VIRTUAL_TIME__() — switch from passthrough to driver-controlled mode
|
|
42
|
+
*/
|
|
43
|
+
export declare const TIME_SHIM_SOURCE = "\n(function() {\n // Preserve real implementations\n var RealDate = Date;\n var realDateNow = Date.now.bind(Date);\n var realPerfNow = performance.now.bind(performance);\n var realSetTimeout = window.setTimeout.bind(window);\n var realClearTimeout = window.clearTimeout.bind(window);\n var realSetInterval = window.setInterval.bind(window);\n var realClearInterval = window.clearInterval.bind(window);\n var realRAF = window.requestAnimationFrame.bind(window);\n var realCAF = window.cancelAnimationFrame.bind(window);\n var realElementAnimate = Element.prototype.animate;\n\n // ----- Mode state -----\n // 'passthrough' = boot phase, real timers run normally\n // 'driver' = render capture, driver controls time\n var mode = 'passthrough';\n\n // ----- Passthrough bookkeeping -----\n // Wall-clock reference captured at shim install time\n var shimInstallPerf = realPerfNow();\n var shimInstallDate = realDateNow();\n // Track real timer/RAF IDs with metadata so we can convert them at mode switch\n var realTimerMeta = new Map(); // real setTimeout ID -> { cb, args, fireAtRealMs }\n var realIntervalMeta = new Map(); // real setInterval ID -> { cb, args, intervalMs, nextFireRealMs }\n var realRafMeta = new Map(); // real RAF ID -> { cb }\n\n // ----- Driver-mode state -----\n var virtualMs = 0;\n var nextTimerId = 1;\n var timers = new Map(); // id -> { fireAt, cb, args, repeat? }\n var rafCallbacks = []; // [{ id, cb }]\n var trackedAnimations = []; // [{ animation, startVirtualMs }]\n\n // ----- performance.now -----\n performance.now = function() {\n if (mode === 'passthrough') {\n return realPerfNow() - shimInstallPerf;\n }\n return virtualMs;\n };\n\n // ----- Date -----\n function VirtualDate() {\n if (arguments.length === 0) {\n if (mode === 'passthrough') {\n return new RealDate(realDateNow());\n }\n return new RealDate(shimInstallDate + virtualMs);\n }\n switch (arguments.length) {\n case 1: return new RealDate(arguments[0]);\n case 2: return new RealDate(arguments[0], arguments[1]);\n case 3: return new RealDate(arguments[0], arguments[1], arguments[2]);\n case 4: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3]);\n case 5: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);\n case 6: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);\n default: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]);\n }\n }\n VirtualDate.now = function() {\n if (mode === 'passthrough') {\n return realDateNow();\n }\n return Math.floor(shimInstallDate + virtualMs);\n };\n VirtualDate.parse = RealDate.parse;\n VirtualDate.UTC = RealDate.UTC;\n VirtualDate.prototype = RealDate.prototype;\n window.Date = VirtualDate;\n\n // ----- setTimeout / clearTimeout -----\n window.setTimeout = function(cb, ms) {\n if (typeof cb !== 'function') return 0;\n var delay = (ms || 0);\n var args = Array.prototype.slice.call(arguments, 2);\n if (mode === 'passthrough') {\n var fireAtRealMs = realPerfNow() + delay;\n // Wrap the callback so that if the real timer fires before engagement,\n // its metadata is removed -- preventing a double-fire at engagement.\n var realId = realSetTimeout(function() { realTimerMeta.delete(realId); cb.apply(null, args); }, delay);\n realTimerMeta.set(realId, { cb: cb, args: args, fireAtRealMs: fireAtRealMs });\n return realId;\n }\n var id = nextTimerId++;\n timers.set(id, { fireAt: virtualMs + delay, cb: cb, args: args });\n return id;\n };\n window.clearTimeout = function(id) {\n if (mode === 'passthrough') {\n realTimerMeta.delete(id);\n realClearTimeout(id);\n return;\n }\n timers.delete(id);\n };\n\n // ----- setInterval / clearInterval -----\n window.setInterval = function(cb, ms) {\n if (typeof cb !== 'function') return 0;\n if (mode === 'passthrough') {\n var intervalMs = Math.max(ms || 0, 1);\n var args = Array.prototype.slice.call(arguments, 2);\n var meta = { cb: cb, args: args, intervalMs: intervalMs, nextFireRealMs: realPerfNow() + intervalMs };\n // Wrap the callback to update nextFireRealMs each time the real interval\n // fires, so that at engagement we compute an accurate remaining time\n // instead of using the stale initial registration time.\n var realId = realSetInterval(function() { meta.nextFireRealMs = realPerfNow() + intervalMs; cb.apply(null, args); }, intervalMs);\n realIntervalMeta.set(realId, meta);\n return realId;\n }\n var interval = Math.max(ms || 0, 1);\n var id = nextTimerId++;\n var args = Array.prototype.slice.call(arguments, 2);\n timers.set(id, { fireAt: virtualMs + interval, cb: cb, args: args, repeat: interval });\n return id;\n };\n window.clearInterval = function(id) {\n if (mode === 'passthrough') {\n realIntervalMeta.delete(id);\n realClearInterval(id);\n return;\n }\n timers.delete(id);\n };\n\n // ----- requestAnimationFrame / cancelAnimationFrame -----\n window.requestAnimationFrame = function(cb) {\n if (mode === 'passthrough') {\n // Wrap the callback so that if the real rAF fires before engagement,\n // its metadata is removed -- preventing a double-fire at engagement.\n var realId = realRAF(function(timestamp) { realRafMeta.delete(realId); cb(timestamp); });\n realRafMeta.set(realId, { cb: cb });\n return realId;\n }\n var id = nextTimerId++;\n rafCallbacks.push({ id: id, cb: cb });\n return id;\n };\n window.cancelAnimationFrame = function(id) {\n if (mode === 'passthrough') {\n realRafMeta.delete(id);\n realCAF(id);\n return;\n }\n for (var i = 0; i < rafCallbacks.length; i++) {\n if (rafCallbacks[i].id === id) {\n rafCallbacks.splice(i, 1);\n break;\n }\n }\n };\n\n // ----- WAAPI: Element.prototype.animate -----\n Element.prototype.animate = function(keyframes, options) {\n var anim = realElementAnimate.call(this, keyframes, options);\n if (mode === 'passthrough') {\n // Let the animation run normally during boot; it will be captured\n // at mode-switch time if still running.\n return anim;\n }\n // Driver mode: pause and drive via currentTime\n return trackAnimation(anim, options);\n };\n\n function trackAnimation(anim, options) {\n anim.pause();\n anim.currentTime = 0;\n var entry = { animation: anim, startVirtualMs: virtualMs };\n trackedAnimations.push(entry);\n var duration = 0;\n if (typeof options === 'number') {\n duration = options;\n } else if (options && typeof options.duration === 'number') {\n duration = options.duration;\n }\n var delay = 0;\n if (options && typeof options === 'object' && typeof options.delay === 'number') {\n delay = options.delay;\n }\n var totalDuration = delay + duration;\n entry.totalDuration = totalDuration;\n entry.resolved = false;\n var finishedPromise = new Promise(function(resolve) {\n entry.resolve = resolve;\n });\n Object.defineProperty(anim, 'finished', {\n get: function() { return finishedPromise; },\n configurable: true\n });\n return anim;\n }\n\n // ----- Mode switch: passthrough -> driver -----\n window.__VW_ENGAGE_VIRTUAL_TIME__ = function() {\n if (mode === 'driver') return; // already engaged\n\n // Set virtual time to current real elapsed so animations don't jump\n var nowReal = realPerfNow();\n virtualMs = nowReal - shimInstallPerf;\n\n // Convert pending real setTimeouts to virtual timers instead of discarding\n realTimerMeta.forEach(function(meta, realId) {\n realClearTimeout(realId);\n var remaining = Math.max(0, meta.fireAtRealMs - nowReal);\n var id = nextTimerId++;\n timers.set(id, { fireAt: virtualMs + remaining, cb: meta.cb, args: meta.args });\n });\n realTimerMeta.clear();\n\n // Convert pending real setIntervals to virtual intervals\n realIntervalMeta.forEach(function(meta, realId) {\n realClearInterval(realId);\n var remaining = Math.max(0, meta.nextFireRealMs - nowReal);\n var id = nextTimerId++;\n timers.set(id, { fireAt: virtualMs + remaining, cb: meta.cb, args: meta.args, repeat: meta.intervalMs });\n });\n realIntervalMeta.clear();\n\n // Convert pending real rAF callbacks into virtual RAF queue\n realRafMeta.forEach(function(meta, realId) {\n realCAF(realId);\n var id = nextTimerId++;\n rafCallbacks.push({ id: id, cb: meta.cb });\n });\n realRafMeta.clear();\n\n // Capture any WAAPI animations still running from boot and pause+track them\n try {\n var liveAnims = document.getAnimations();\n for (var i = 0; i < liveAnims.length; i++) {\n var a = liveAnims[i];\n if (a.playState === 'running' || a.playState === 'paused') {\n var ct = a.currentTime || 0;\n a.pause();\n a.currentTime = ct;\n var entry = {\n animation: a,\n startVirtualMs: virtualMs - ct,\n totalDuration: undefined,\n resolved: false,\n resolve: undefined\n };\n // Try to read duration from the animation effect\n try {\n var timing = a.effect && a.effect.getTiming ? a.effect.getTiming() : null;\n if (timing) {\n var dur = typeof timing.duration === 'number' ? timing.duration : 0;\n var del = typeof timing.delay === 'number' ? timing.delay : 0;\n entry.totalDuration = del + dur;\n }\n } catch(e) {}\n var finishedPromise = new Promise(function(resolve) {\n entry.resolve = resolve;\n });\n Object.defineProperty(a, 'finished', {\n get: function() { return finishedPromise; },\n configurable: true\n });\n trackedAnimations.push(entry);\n }\n }\n } catch(e) {\n // document.getAnimations may not be available in all contexts\n }\n\n mode = 'driver';\n };\n\n // ----- Clock advance driver -----\n window.__VW_ADVANCE_CLOCK__ = async function(deltaMs) {\n if (mode === 'passthrough') return; // no-op during boot\n\n var target = virtualMs + deltaMs;\n\n // Fire timers in chronological order up to target.\n // The function is async so that microtasks (e.g. Promise resolutions from\n // ctx.hold) drain between timer fires. This ensures awaiting code resumes\n // at virtualMs = fireAt (the timer's actual scheduled time) rather than at\n // the frame boundary (target), eliminating per-hold rounding drift.\n var safety = 10000;\n while (safety-- > 0) {\n var earliestId = null;\n var earliestTimer = null;\n timers.forEach(function(t, id) {\n if (t.fireAt <= target && (earliestTimer === null || t.fireAt < earliestTimer.fireAt)) {\n earliestId = id;\n earliestTimer = t;\n }\n });\n if (!earliestTimer) break;\n\n virtualMs = earliestTimer.fireAt;\n if (earliestTimer.repeat !== undefined) {\n timers.set(earliestId, {\n fireAt: virtualMs + earliestTimer.repeat,\n cb: earliestTimer.cb,\n args: earliestTimer.args,\n repeat: earliestTimer.repeat\n });\n } else {\n timers.delete(earliestId);\n }\n try { earliestTimer.cb.apply(null, earliestTimer.args || []); } catch(e) { console.error('[VW shim] timer error:', e); }\n\n // Drain microtasks so awaiting code (e.g. ctx.hold Promise resolution)\n // resumes at virtualMs = fireAt and registers its next timer relative to\n // fireAt, not target. Multiple awaits handle chained async continuations.\n for (var d = 0; d < 8; d++) await Promise.resolve();\n }\n\n // Final advance to target\n virtualMs = target;\n\n // Fire all queued RAF callbacks\n var queued = rafCallbacks.splice(0);\n for (var i = 0; i < queued.length; i++) {\n try { queued[i].cb(virtualMs); } catch(e) { console.error('[VW shim] RAF error:', e); }\n }\n\n // Update WAAPI animations\n for (var j = trackedAnimations.length - 1; j >= 0; j--) {\n var entry = trackedAnimations[j];\n var anim = entry.animation;\n // Skip cancelled animations\n if (anim.playState === 'idle') {\n trackedAnimations.splice(j, 1);\n continue;\n }\n var elapsed = virtualMs - entry.startVirtualMs;\n try {\n anim.currentTime = elapsed;\n } catch(e) {\n trackedAnimations.splice(j, 1);\n continue;\n }\n // Check if animation is complete\n if (entry.totalDuration !== undefined && elapsed >= entry.totalDuration && !entry.resolved) {\n entry.resolved = true;\n if (entry.resolve) entry.resolve(anim);\n trackedAnimations.splice(j, 1);\n }\n }\n };\n})();\n";
|
|
44
|
+
//# sourceMappingURL=time_shim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time_shim.d.ts","sourceRoot":"","sources":["../../src/cli/time_shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,eAAO,MAAM,gBAAgB,i7ZA0V5B,CAAC"}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS time injection shim for deterministic rendering.
|
|
3
|
+
*
|
|
4
|
+
* This module exports the shim source code as a string. The render driver
|
|
5
|
+
* injects it into the page via `page.addInitScript()` BEFORE navigation,
|
|
6
|
+
* so it runs in every frame context before any user code.
|
|
7
|
+
*
|
|
8
|
+
* ## Two-mode design
|
|
9
|
+
*
|
|
10
|
+
* The shim operates in two modes:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Passthrough mode** (default, during boot): Virtual clock advances at
|
|
13
|
+
* wall-clock rate. setTimeout, setInterval, requestAnimationFrame all
|
|
14
|
+
* delegate to the REAL browser implementations so that Vite HMR, module
|
|
15
|
+
* loading, library init (lottie-web, echarts, three.js), and the render
|
|
16
|
+
* entry boot() function can complete normally. `performance.now()` and
|
|
17
|
+
* `Date.now()` return real elapsed time since shim install. Real timer
|
|
18
|
+
* IDs and metadata are tracked so they can be converted to virtual
|
|
19
|
+
* timers at mode switch (preserving pending callbacks).
|
|
20
|
+
*
|
|
21
|
+
* 2. **Driver-controlled mode** (during render capture): The driver controls
|
|
22
|
+
* advancement via `__VW_ADVANCE_CLOCK__(deltaMs)`. Timers and RAF
|
|
23
|
+
* callbacks only fire when the driver advances time. WAAPI animations
|
|
24
|
+
* are paused and driven by explicit currentTime updates.
|
|
25
|
+
*
|
|
26
|
+
* The shim starts in passthrough mode. When the Node driver is ready to
|
|
27
|
+
* start capture (after `__VW_RENDER_READY__` is set), it calls
|
|
28
|
+
* `window.__VW_ENGAGE_VIRTUAL_TIME__()` to switch to driver-controlled
|
|
29
|
+
* mode. From that point, real timers stop firing and the driver advances
|
|
30
|
+
* virtual time explicitly.
|
|
31
|
+
*
|
|
32
|
+
* The shim overrides:
|
|
33
|
+
* - Date (no-arg constructor), Date.now
|
|
34
|
+
* - performance.now
|
|
35
|
+
* - setTimeout, setInterval, clearTimeout, clearInterval
|
|
36
|
+
* - requestAnimationFrame, cancelAnimationFrame
|
|
37
|
+
* - Element.prototype.animate (for WAAPI determinism)
|
|
38
|
+
*
|
|
39
|
+
* It exposes:
|
|
40
|
+
* - window.__VW_ADVANCE_CLOCK__(deltaMs) — advance the virtual clock, fire timers/RAFs, update WAAPI
|
|
41
|
+
* - window.__VW_ENGAGE_VIRTUAL_TIME__() — switch from passthrough to driver-controlled mode
|
|
42
|
+
*/
|
|
43
|
+
export const TIME_SHIM_SOURCE = `
|
|
44
|
+
(function() {
|
|
45
|
+
// Preserve real implementations
|
|
46
|
+
var RealDate = Date;
|
|
47
|
+
var realDateNow = Date.now.bind(Date);
|
|
48
|
+
var realPerfNow = performance.now.bind(performance);
|
|
49
|
+
var realSetTimeout = window.setTimeout.bind(window);
|
|
50
|
+
var realClearTimeout = window.clearTimeout.bind(window);
|
|
51
|
+
var realSetInterval = window.setInterval.bind(window);
|
|
52
|
+
var realClearInterval = window.clearInterval.bind(window);
|
|
53
|
+
var realRAF = window.requestAnimationFrame.bind(window);
|
|
54
|
+
var realCAF = window.cancelAnimationFrame.bind(window);
|
|
55
|
+
var realElementAnimate = Element.prototype.animate;
|
|
56
|
+
|
|
57
|
+
// ----- Mode state -----
|
|
58
|
+
// 'passthrough' = boot phase, real timers run normally
|
|
59
|
+
// 'driver' = render capture, driver controls time
|
|
60
|
+
var mode = 'passthrough';
|
|
61
|
+
|
|
62
|
+
// ----- Passthrough bookkeeping -----
|
|
63
|
+
// Wall-clock reference captured at shim install time
|
|
64
|
+
var shimInstallPerf = realPerfNow();
|
|
65
|
+
var shimInstallDate = realDateNow();
|
|
66
|
+
// Track real timer/RAF IDs with metadata so we can convert them at mode switch
|
|
67
|
+
var realTimerMeta = new Map(); // real setTimeout ID -> { cb, args, fireAtRealMs }
|
|
68
|
+
var realIntervalMeta = new Map(); // real setInterval ID -> { cb, args, intervalMs, nextFireRealMs }
|
|
69
|
+
var realRafMeta = new Map(); // real RAF ID -> { cb }
|
|
70
|
+
|
|
71
|
+
// ----- Driver-mode state -----
|
|
72
|
+
var virtualMs = 0;
|
|
73
|
+
var nextTimerId = 1;
|
|
74
|
+
var timers = new Map(); // id -> { fireAt, cb, args, repeat? }
|
|
75
|
+
var rafCallbacks = []; // [{ id, cb }]
|
|
76
|
+
var trackedAnimations = []; // [{ animation, startVirtualMs }]
|
|
77
|
+
|
|
78
|
+
// ----- performance.now -----
|
|
79
|
+
performance.now = function() {
|
|
80
|
+
if (mode === 'passthrough') {
|
|
81
|
+
return realPerfNow() - shimInstallPerf;
|
|
82
|
+
}
|
|
83
|
+
return virtualMs;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// ----- Date -----
|
|
87
|
+
function VirtualDate() {
|
|
88
|
+
if (arguments.length === 0) {
|
|
89
|
+
if (mode === 'passthrough') {
|
|
90
|
+
return new RealDate(realDateNow());
|
|
91
|
+
}
|
|
92
|
+
return new RealDate(shimInstallDate + virtualMs);
|
|
93
|
+
}
|
|
94
|
+
switch (arguments.length) {
|
|
95
|
+
case 1: return new RealDate(arguments[0]);
|
|
96
|
+
case 2: return new RealDate(arguments[0], arguments[1]);
|
|
97
|
+
case 3: return new RealDate(arguments[0], arguments[1], arguments[2]);
|
|
98
|
+
case 4: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3]);
|
|
99
|
+
case 5: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
|
|
100
|
+
case 6: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
|
|
101
|
+
default: return new RealDate(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
VirtualDate.now = function() {
|
|
105
|
+
if (mode === 'passthrough') {
|
|
106
|
+
return realDateNow();
|
|
107
|
+
}
|
|
108
|
+
return Math.floor(shimInstallDate + virtualMs);
|
|
109
|
+
};
|
|
110
|
+
VirtualDate.parse = RealDate.parse;
|
|
111
|
+
VirtualDate.UTC = RealDate.UTC;
|
|
112
|
+
VirtualDate.prototype = RealDate.prototype;
|
|
113
|
+
window.Date = VirtualDate;
|
|
114
|
+
|
|
115
|
+
// ----- setTimeout / clearTimeout -----
|
|
116
|
+
window.setTimeout = function(cb, ms) {
|
|
117
|
+
if (typeof cb !== 'function') return 0;
|
|
118
|
+
var delay = (ms || 0);
|
|
119
|
+
var args = Array.prototype.slice.call(arguments, 2);
|
|
120
|
+
if (mode === 'passthrough') {
|
|
121
|
+
var fireAtRealMs = realPerfNow() + delay;
|
|
122
|
+
// Wrap the callback so that if the real timer fires before engagement,
|
|
123
|
+
// its metadata is removed -- preventing a double-fire at engagement.
|
|
124
|
+
var realId = realSetTimeout(function() { realTimerMeta.delete(realId); cb.apply(null, args); }, delay);
|
|
125
|
+
realTimerMeta.set(realId, { cb: cb, args: args, fireAtRealMs: fireAtRealMs });
|
|
126
|
+
return realId;
|
|
127
|
+
}
|
|
128
|
+
var id = nextTimerId++;
|
|
129
|
+
timers.set(id, { fireAt: virtualMs + delay, cb: cb, args: args });
|
|
130
|
+
return id;
|
|
131
|
+
};
|
|
132
|
+
window.clearTimeout = function(id) {
|
|
133
|
+
if (mode === 'passthrough') {
|
|
134
|
+
realTimerMeta.delete(id);
|
|
135
|
+
realClearTimeout(id);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
timers.delete(id);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ----- setInterval / clearInterval -----
|
|
142
|
+
window.setInterval = function(cb, ms) {
|
|
143
|
+
if (typeof cb !== 'function') return 0;
|
|
144
|
+
if (mode === 'passthrough') {
|
|
145
|
+
var intervalMs = Math.max(ms || 0, 1);
|
|
146
|
+
var args = Array.prototype.slice.call(arguments, 2);
|
|
147
|
+
var meta = { cb: cb, args: args, intervalMs: intervalMs, nextFireRealMs: realPerfNow() + intervalMs };
|
|
148
|
+
// Wrap the callback to update nextFireRealMs each time the real interval
|
|
149
|
+
// fires, so that at engagement we compute an accurate remaining time
|
|
150
|
+
// instead of using the stale initial registration time.
|
|
151
|
+
var realId = realSetInterval(function() { meta.nextFireRealMs = realPerfNow() + intervalMs; cb.apply(null, args); }, intervalMs);
|
|
152
|
+
realIntervalMeta.set(realId, meta);
|
|
153
|
+
return realId;
|
|
154
|
+
}
|
|
155
|
+
var interval = Math.max(ms || 0, 1);
|
|
156
|
+
var id = nextTimerId++;
|
|
157
|
+
var args = Array.prototype.slice.call(arguments, 2);
|
|
158
|
+
timers.set(id, { fireAt: virtualMs + interval, cb: cb, args: args, repeat: interval });
|
|
159
|
+
return id;
|
|
160
|
+
};
|
|
161
|
+
window.clearInterval = function(id) {
|
|
162
|
+
if (mode === 'passthrough') {
|
|
163
|
+
realIntervalMeta.delete(id);
|
|
164
|
+
realClearInterval(id);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
timers.delete(id);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// ----- requestAnimationFrame / cancelAnimationFrame -----
|
|
171
|
+
window.requestAnimationFrame = function(cb) {
|
|
172
|
+
if (mode === 'passthrough') {
|
|
173
|
+
// Wrap the callback so that if the real rAF fires before engagement,
|
|
174
|
+
// its metadata is removed -- preventing a double-fire at engagement.
|
|
175
|
+
var realId = realRAF(function(timestamp) { realRafMeta.delete(realId); cb(timestamp); });
|
|
176
|
+
realRafMeta.set(realId, { cb: cb });
|
|
177
|
+
return realId;
|
|
178
|
+
}
|
|
179
|
+
var id = nextTimerId++;
|
|
180
|
+
rafCallbacks.push({ id: id, cb: cb });
|
|
181
|
+
return id;
|
|
182
|
+
};
|
|
183
|
+
window.cancelAnimationFrame = function(id) {
|
|
184
|
+
if (mode === 'passthrough') {
|
|
185
|
+
realRafMeta.delete(id);
|
|
186
|
+
realCAF(id);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (var i = 0; i < rafCallbacks.length; i++) {
|
|
190
|
+
if (rafCallbacks[i].id === id) {
|
|
191
|
+
rafCallbacks.splice(i, 1);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// ----- WAAPI: Element.prototype.animate -----
|
|
198
|
+
Element.prototype.animate = function(keyframes, options) {
|
|
199
|
+
var anim = realElementAnimate.call(this, keyframes, options);
|
|
200
|
+
if (mode === 'passthrough') {
|
|
201
|
+
// Let the animation run normally during boot; it will be captured
|
|
202
|
+
// at mode-switch time if still running.
|
|
203
|
+
return anim;
|
|
204
|
+
}
|
|
205
|
+
// Driver mode: pause and drive via currentTime
|
|
206
|
+
return trackAnimation(anim, options);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
function trackAnimation(anim, options) {
|
|
210
|
+
anim.pause();
|
|
211
|
+
anim.currentTime = 0;
|
|
212
|
+
var entry = { animation: anim, startVirtualMs: virtualMs };
|
|
213
|
+
trackedAnimations.push(entry);
|
|
214
|
+
var duration = 0;
|
|
215
|
+
if (typeof options === 'number') {
|
|
216
|
+
duration = options;
|
|
217
|
+
} else if (options && typeof options.duration === 'number') {
|
|
218
|
+
duration = options.duration;
|
|
219
|
+
}
|
|
220
|
+
var delay = 0;
|
|
221
|
+
if (options && typeof options === 'object' && typeof options.delay === 'number') {
|
|
222
|
+
delay = options.delay;
|
|
223
|
+
}
|
|
224
|
+
var totalDuration = delay + duration;
|
|
225
|
+
entry.totalDuration = totalDuration;
|
|
226
|
+
entry.resolved = false;
|
|
227
|
+
var finishedPromise = new Promise(function(resolve) {
|
|
228
|
+
entry.resolve = resolve;
|
|
229
|
+
});
|
|
230
|
+
Object.defineProperty(anim, 'finished', {
|
|
231
|
+
get: function() { return finishedPromise; },
|
|
232
|
+
configurable: true
|
|
233
|
+
});
|
|
234
|
+
return anim;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ----- Mode switch: passthrough -> driver -----
|
|
238
|
+
window.__VW_ENGAGE_VIRTUAL_TIME__ = function() {
|
|
239
|
+
if (mode === 'driver') return; // already engaged
|
|
240
|
+
|
|
241
|
+
// Set virtual time to current real elapsed so animations don't jump
|
|
242
|
+
var nowReal = realPerfNow();
|
|
243
|
+
virtualMs = nowReal - shimInstallPerf;
|
|
244
|
+
|
|
245
|
+
// Convert pending real setTimeouts to virtual timers instead of discarding
|
|
246
|
+
realTimerMeta.forEach(function(meta, realId) {
|
|
247
|
+
realClearTimeout(realId);
|
|
248
|
+
var remaining = Math.max(0, meta.fireAtRealMs - nowReal);
|
|
249
|
+
var id = nextTimerId++;
|
|
250
|
+
timers.set(id, { fireAt: virtualMs + remaining, cb: meta.cb, args: meta.args });
|
|
251
|
+
});
|
|
252
|
+
realTimerMeta.clear();
|
|
253
|
+
|
|
254
|
+
// Convert pending real setIntervals to virtual intervals
|
|
255
|
+
realIntervalMeta.forEach(function(meta, realId) {
|
|
256
|
+
realClearInterval(realId);
|
|
257
|
+
var remaining = Math.max(0, meta.nextFireRealMs - nowReal);
|
|
258
|
+
var id = nextTimerId++;
|
|
259
|
+
timers.set(id, { fireAt: virtualMs + remaining, cb: meta.cb, args: meta.args, repeat: meta.intervalMs });
|
|
260
|
+
});
|
|
261
|
+
realIntervalMeta.clear();
|
|
262
|
+
|
|
263
|
+
// Convert pending real rAF callbacks into virtual RAF queue
|
|
264
|
+
realRafMeta.forEach(function(meta, realId) {
|
|
265
|
+
realCAF(realId);
|
|
266
|
+
var id = nextTimerId++;
|
|
267
|
+
rafCallbacks.push({ id: id, cb: meta.cb });
|
|
268
|
+
});
|
|
269
|
+
realRafMeta.clear();
|
|
270
|
+
|
|
271
|
+
// Capture any WAAPI animations still running from boot and pause+track them
|
|
272
|
+
try {
|
|
273
|
+
var liveAnims = document.getAnimations();
|
|
274
|
+
for (var i = 0; i < liveAnims.length; i++) {
|
|
275
|
+
var a = liveAnims[i];
|
|
276
|
+
if (a.playState === 'running' || a.playState === 'paused') {
|
|
277
|
+
var ct = a.currentTime || 0;
|
|
278
|
+
a.pause();
|
|
279
|
+
a.currentTime = ct;
|
|
280
|
+
var entry = {
|
|
281
|
+
animation: a,
|
|
282
|
+
startVirtualMs: virtualMs - ct,
|
|
283
|
+
totalDuration: undefined,
|
|
284
|
+
resolved: false,
|
|
285
|
+
resolve: undefined
|
|
286
|
+
};
|
|
287
|
+
// Try to read duration from the animation effect
|
|
288
|
+
try {
|
|
289
|
+
var timing = a.effect && a.effect.getTiming ? a.effect.getTiming() : null;
|
|
290
|
+
if (timing) {
|
|
291
|
+
var dur = typeof timing.duration === 'number' ? timing.duration : 0;
|
|
292
|
+
var del = typeof timing.delay === 'number' ? timing.delay : 0;
|
|
293
|
+
entry.totalDuration = del + dur;
|
|
294
|
+
}
|
|
295
|
+
} catch(e) {}
|
|
296
|
+
var finishedPromise = new Promise(function(resolve) {
|
|
297
|
+
entry.resolve = resolve;
|
|
298
|
+
});
|
|
299
|
+
Object.defineProperty(a, 'finished', {
|
|
300
|
+
get: function() { return finishedPromise; },
|
|
301
|
+
configurable: true
|
|
302
|
+
});
|
|
303
|
+
trackedAnimations.push(entry);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch(e) {
|
|
307
|
+
// document.getAnimations may not be available in all contexts
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
mode = 'driver';
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// ----- Clock advance driver -----
|
|
314
|
+
window.__VW_ADVANCE_CLOCK__ = async function(deltaMs) {
|
|
315
|
+
if (mode === 'passthrough') return; // no-op during boot
|
|
316
|
+
|
|
317
|
+
var target = virtualMs + deltaMs;
|
|
318
|
+
|
|
319
|
+
// Fire timers in chronological order up to target.
|
|
320
|
+
// The function is async so that microtasks (e.g. Promise resolutions from
|
|
321
|
+
// ctx.hold) drain between timer fires. This ensures awaiting code resumes
|
|
322
|
+
// at virtualMs = fireAt (the timer's actual scheduled time) rather than at
|
|
323
|
+
// the frame boundary (target), eliminating per-hold rounding drift.
|
|
324
|
+
var safety = 10000;
|
|
325
|
+
while (safety-- > 0) {
|
|
326
|
+
var earliestId = null;
|
|
327
|
+
var earliestTimer = null;
|
|
328
|
+
timers.forEach(function(t, id) {
|
|
329
|
+
if (t.fireAt <= target && (earliestTimer === null || t.fireAt < earliestTimer.fireAt)) {
|
|
330
|
+
earliestId = id;
|
|
331
|
+
earliestTimer = t;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
if (!earliestTimer) break;
|
|
335
|
+
|
|
336
|
+
virtualMs = earliestTimer.fireAt;
|
|
337
|
+
if (earliestTimer.repeat !== undefined) {
|
|
338
|
+
timers.set(earliestId, {
|
|
339
|
+
fireAt: virtualMs + earliestTimer.repeat,
|
|
340
|
+
cb: earliestTimer.cb,
|
|
341
|
+
args: earliestTimer.args,
|
|
342
|
+
repeat: earliestTimer.repeat
|
|
343
|
+
});
|
|
344
|
+
} else {
|
|
345
|
+
timers.delete(earliestId);
|
|
346
|
+
}
|
|
347
|
+
try { earliestTimer.cb.apply(null, earliestTimer.args || []); } catch(e) { console.error('[VW shim] timer error:', e); }
|
|
348
|
+
|
|
349
|
+
// Drain microtasks so awaiting code (e.g. ctx.hold Promise resolution)
|
|
350
|
+
// resumes at virtualMs = fireAt and registers its next timer relative to
|
|
351
|
+
// fireAt, not target. Multiple awaits handle chained async continuations.
|
|
352
|
+
for (var d = 0; d < 8; d++) await Promise.resolve();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Final advance to target
|
|
356
|
+
virtualMs = target;
|
|
357
|
+
|
|
358
|
+
// Fire all queued RAF callbacks
|
|
359
|
+
var queued = rafCallbacks.splice(0);
|
|
360
|
+
for (var i = 0; i < queued.length; i++) {
|
|
361
|
+
try { queued[i].cb(virtualMs); } catch(e) { console.error('[VW shim] RAF error:', e); }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Update WAAPI animations
|
|
365
|
+
for (var j = trackedAnimations.length - 1; j >= 0; j--) {
|
|
366
|
+
var entry = trackedAnimations[j];
|
|
367
|
+
var anim = entry.animation;
|
|
368
|
+
// Skip cancelled animations
|
|
369
|
+
if (anim.playState === 'idle') {
|
|
370
|
+
trackedAnimations.splice(j, 1);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
var elapsed = virtualMs - entry.startVirtualMs;
|
|
374
|
+
try {
|
|
375
|
+
anim.currentTime = elapsed;
|
|
376
|
+
} catch(e) {
|
|
377
|
+
trackedAnimations.splice(j, 1);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
// Check if animation is complete
|
|
381
|
+
if (entry.totalDuration !== undefined && elapsed >= entry.totalDuration && !entry.resolved) {
|
|
382
|
+
entry.resolved = true;
|
|
383
|
+
if (entry.resolve) entry.resolve(anim);
|
|
384
|
+
trackedAnimations.splice(j, 1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
})();
|
|
389
|
+
`;
|
|
390
|
+
//# sourceMappingURL=time_shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time_shim.js","sourceRoot":"","sources":["../../src/cli/time_shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0V/B,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime TS module loader for Node using tsx.
|
|
3
|
+
* Used by the CLI to import .ts config and timeline files without a build step.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Dynamically import a TypeScript module from Node at runtime.
|
|
7
|
+
* Returns the module's namespace (use .default for default exports).
|
|
8
|
+
*
|
|
9
|
+
* ## tsx double-wrap heuristic
|
|
10
|
+
*
|
|
11
|
+
* When tsx loads a CommonJS-style module (or a module with `export default`),
|
|
12
|
+
* it may produce a namespace like:
|
|
13
|
+
*
|
|
14
|
+
* { default: { __esModule: true, default: <actual-value> } }
|
|
15
|
+
*
|
|
16
|
+
* This happens because tsx transpiles `export default X` to
|
|
17
|
+
* `module.exports.default = X` + `module.exports.__esModule = true`, then
|
|
18
|
+
* the ESM interop layer wraps the whole `module.exports` as the default export.
|
|
19
|
+
*
|
|
20
|
+
* We detect this pattern (an object default with both __esModule and default keys)
|
|
21
|
+
* and unwrap it so callers always see `{ default: <actual-value>, ... }`.
|
|
22
|
+
*
|
|
23
|
+
* If tsx changes this behavior in future versions, the detection is conservative:
|
|
24
|
+
* we only unwrap when both sentinel keys are present, so a legitimate object
|
|
25
|
+
* with those keys would be the only false positive (extremely unlikely in practice).
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadModule(modulePath: string): Promise<Record<string, unknown>>;
|
|
28
|
+
//# sourceMappingURL=ts_loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts_loader.d.ts","sourceRoot":"","sources":["../../src/cli/ts_loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyDH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmBrF"}
|