webreel 0.0.0 → 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 +191 -1
- package/dist/commands/__tests__/record.test.d.ts +2 -0
- package/dist/commands/__tests__/record.test.d.ts.map +1 -0
- package/dist/commands/__tests__/record.test.js +89 -0
- package/dist/commands/__tests__/record.test.js.map +1 -0
- package/dist/commands/composite.d.ts +3 -0
- package/dist/commands/composite.d.ts.map +1 -0
- package/dist/commands/composite.js +40 -0
- package/dist/commands/composite.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +50 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/preview.d.ts +3 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +30 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/record.d.ts +5 -0
- package/dist/commands/record.d.ts.map +1 -0
- package/dist/commands/record.js +147 -0
- package/dist/commands/record.js.map +1 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +43 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/__tests__/config.test.d.ts +2 -0
- package/dist/lib/__tests__/config.test.d.ts.map +1 -0
- package/dist/lib/__tests__/config.test.js +581 -0
- package/dist/lib/__tests__/config.test.js.map +1 -0
- package/dist/lib/__tests__/examples.test.d.ts +2 -0
- package/dist/lib/__tests__/examples.test.d.ts.map +1 -0
- package/dist/lib/__tests__/examples.test.js +19 -0
- package/dist/lib/__tests__/examples.test.js.map +1 -0
- package/dist/lib/__tests__/runner.test.d.ts +2 -0
- package/dist/lib/__tests__/runner.test.d.ts.map +1 -0
- package/dist/lib/__tests__/runner.test.js +141 -0
- package/dist/lib/__tests__/runner.test.js.map +1 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +974 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/runner.d.ts +18 -0
- package/dist/lib/runner.d.ts.map +1 -0
- package/dist/lib/runner.js +387 -0
- package/dist/lib/runner.js.map +1 -0
- package/dist/lib/types.d.ts +173 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +16 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +51 -7
- package/index.js +0 -2
package/README.md
CHANGED
|
@@ -1,3 +1,193 @@
|
|
|
1
1
|
# webreel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI that records scripted browser videos as MP4, GIF, or WebM files from JSON configs.
|
|
4
|
+
|
|
5
|
+
Define steps (clicks, key presses, drags, pauses) and webreel drives a headless Chrome instance, captures screenshots at ~60fps, adds cursor animation, keystroke overlays, and sound effects, and encodes the result with ffmpeg.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install webreel
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx webreel init --name my-video --url https://example.com
|
|
17
|
+
npx webreel record
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
<!-- EXAMPLES:START -->
|
|
23
|
+
|
|
24
|
+
**[custom-theme](../../examples/custom-theme)** - Demonstrates fully customizing the cursor overlay and keystroke HUD appearance using a code editor page.
|
|
25
|
+
|
|
26
|
+
<video src="../../examples/custom-theme/videos/custom-theme.mp4" controls muted width="100%"></video>
|
|
27
|
+
|
|
28
|
+
**[drag-and-drop](../../examples/drag-and-drop)** - Demonstrates dragging elements between positions on a kanban board.
|
|
29
|
+
|
|
30
|
+
<video src="../../examples/drag-and-drop/videos/drag-and-drop.mp4" controls muted width="100%"></video>
|
|
31
|
+
|
|
32
|
+
**[form-filling](../../examples/form-filling)** - Demonstrates typing into form fields and clicking a submit button, simulating a login flow.
|
|
33
|
+
|
|
34
|
+
<video src="../../examples/form-filling/videos/form-filling.mp4" controls muted width="100%"></video>
|
|
35
|
+
|
|
36
|
+
**[gif-output](../../examples/gif-output)** - Demonstrates outputting the recording as an animated GIF instead of the default MP4.
|
|
37
|
+
|
|
38
|
+
<video src="../../examples/gif-output/videos/gif-output.gif" controls muted width="100%"></video>
|
|
39
|
+
|
|
40
|
+
**[hello-world](../../examples/hello-world)** - The simplest possible webreel example. Opens a landing page and clicks the call-to-action button.
|
|
41
|
+
|
|
42
|
+
<video src="../../examples/hello-world/videos/hello-world.mp4" controls muted width="100%"></video>
|
|
43
|
+
|
|
44
|
+
**[keyboard-shortcuts](../../examples/keyboard-shortcuts)** - Demonstrates pressing key combos and displaying them in the keystroke HUD overlay. Uses a code editor page as the target.
|
|
45
|
+
|
|
46
|
+
<video src="../../examples/keyboard-shortcuts/videos/keyboard-shortcuts.mp4" controls muted width="100%"></video>
|
|
47
|
+
|
|
48
|
+
**[mobile-viewport](../../examples/mobile-viewport)** - Demonstrates recording at mobile device dimensions using a finance app interface.
|
|
49
|
+
|
|
50
|
+
<video src="../../examples/mobile-viewport/videos/mobile-viewport.mp4" controls muted width="100%"></video>
|
|
51
|
+
|
|
52
|
+
**[modifier-clicks](../../examples/modifier-clicks)** - Demonstrates clicking elements with modifier keys held down, simulating multi-select in a file manager.
|
|
53
|
+
|
|
54
|
+
<video src="../../examples/modifier-clicks/videos/modifier-clicks.mp4" controls muted width="100%"></video>
|
|
55
|
+
|
|
56
|
+
**[multi-demo](../../examples/multi-demo)** - Demonstrates defining multiple videos in a single config file, each producing its own output from the same page.
|
|
57
|
+
|
|
58
|
+
<video src="../../examples/multi-demo/videos/homepage.mp4" controls muted width="100%"></video>
|
|
59
|
+
|
|
60
|
+
**[page-scrolling](../../examples/page-scrolling)** - Demonstrates scrolling the page and scrolling within a specific container element on a blog post layout.
|
|
61
|
+
|
|
62
|
+
<video src="../../examples/page-scrolling/videos/page-scrolling.mp4" controls muted width="100%"></video>
|
|
63
|
+
|
|
64
|
+
**[screenshots](../../examples/screenshots)** - Demonstrates capturing PNG screenshots at specific points during a recording. Useful for generating static marketing assets or documentation images alongside videos.
|
|
65
|
+
|
|
66
|
+
<video src="../../examples/screenshots/videos/screenshots.mp4" controls muted width="100%"></video>
|
|
67
|
+
|
|
68
|
+
**[shared-steps](../../examples/shared-steps)** - Demonstrates using `include` to share common setup steps across videos. The shared steps dismiss a cookie consent banner before the main video steps run.
|
|
69
|
+
|
|
70
|
+
<video src="../../examples/shared-steps/shared-steps.mp4" controls muted width="100%"></video>
|
|
71
|
+
|
|
72
|
+
**[webm-output](../../examples/webm-output)** - Demonstrates outputting the recording as a WebM video using VP9 encoding.
|
|
73
|
+
|
|
74
|
+
<video src="../../examples/webm-output/webm-output.webm" controls muted width="100%"></video>
|
|
75
|
+
|
|
76
|
+
<!-- EXAMPLES:END -->
|
|
77
|
+
|
|
78
|
+
## Commands
|
|
79
|
+
|
|
80
|
+
### `webreel init`
|
|
81
|
+
|
|
82
|
+
Scaffold a new config file.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
webreel init
|
|
86
|
+
webreel init --name login-flow --url https://myapp.com
|
|
87
|
+
webreel init --name hero -o hero.config.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Option | Default | Description |
|
|
91
|
+
| --------------------- | --------------------- | ---------------- |
|
|
92
|
+
| `--name <name>` | `my-video` | Video name |
|
|
93
|
+
| `--url <url>` | `https://example.com` | Starting URL |
|
|
94
|
+
| `-o, --output <file>` | `<name>.json` | Output file path |
|
|
95
|
+
|
|
96
|
+
### `webreel record`
|
|
97
|
+
|
|
98
|
+
Record videos.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
webreel record
|
|
102
|
+
webreel record hero login
|
|
103
|
+
webreel record -c custom.config.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
When run without arguments, webreel reads `webreel.config.json` from the current directory and records all videos. Provide video names to record specific videos only.
|
|
107
|
+
|
|
108
|
+
### `webreel preview`
|
|
109
|
+
|
|
110
|
+
Run a video in a visible browser window without recording.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
webreel preview
|
|
114
|
+
webreel preview hero
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `webreel validate`
|
|
118
|
+
|
|
119
|
+
Check config files for errors without running them.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
webreel validate
|
|
123
|
+
webreel validate -c custom.config.json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `webreel composite`
|
|
127
|
+
|
|
128
|
+
Re-composite videos from stored raw recordings and timelines without re-recording.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
webreel composite
|
|
132
|
+
webreel composite hero login
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Raw video and timeline data are saved in `.webreel/raw/` and `.webreel/timelines/` during `webreel record`. Use `composite` to re-apply cursor overlays, HUD, and sound effects without re-running the browser.
|
|
136
|
+
|
|
137
|
+
## Config format
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"$schema": "https://webreel.dev/schema/v1.json",
|
|
142
|
+
"videos": {
|
|
143
|
+
"my-video": {
|
|
144
|
+
"url": "https://example.com",
|
|
145
|
+
"viewport": { "width": 1080, "height": 1080 },
|
|
146
|
+
"defaultDelay": 500,
|
|
147
|
+
"steps": [
|
|
148
|
+
{ "action": "pause", "ms": 500 },
|
|
149
|
+
{ "action": "click", "text": "Get Started" },
|
|
150
|
+
{ "action": "key", "key": "cmd+a", "delay": 1000 }
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Config options
|
|
158
|
+
|
|
159
|
+
| Field | Default | Description |
|
|
160
|
+
| -------------- | ------------- | --------------------------------------------------------- |
|
|
161
|
+
| `url` | required | URL to navigate to |
|
|
162
|
+
| `baseUrl` | `""` | Prepended to relative URLs |
|
|
163
|
+
| `viewport` | 1080x1080 | Browser viewport dimensions |
|
|
164
|
+
| `zoom` | - | CSS zoom level applied to the page |
|
|
165
|
+
| `waitFor` | - | CSS selector to wait for before starting |
|
|
166
|
+
| `output` | `<name>.mp4` | Output file path (`.mp4`, `.gif`, or `.webm`) |
|
|
167
|
+
| `thumbnail` | `{ time: 0 }` | Object with `time` (seconds) or `enabled: false` |
|
|
168
|
+
| `theme` | - | Overlay theme (`cursor: { image, size, hotspot }`, `hud`) |
|
|
169
|
+
| `include` | - | Array of JSON file paths whose steps are prepended |
|
|
170
|
+
| `defaultDelay` | - | Default delay (ms) after each step |
|
|
171
|
+
|
|
172
|
+
### Actions
|
|
173
|
+
|
|
174
|
+
| Action | Fields | Description |
|
|
175
|
+
| ------------ | ------------------------------------------------------ | ------------------------------------- |
|
|
176
|
+
| `pause` | `ms` | Wait for a duration |
|
|
177
|
+
| `click` | `text` or `selector`, optional `within`, `modifiers` | Move cursor to an element and click |
|
|
178
|
+
| `key` | `key` (e.g. `"cmd+z"`), optional `label`, `target` | Press a key or key combo |
|
|
179
|
+
| `type` | `text`, optional `target`, `charDelay` | Type text character by character |
|
|
180
|
+
| `drag` | `from` and `to` (each with `text`/`selector`/`within`) | Drag from one element to another |
|
|
181
|
+
| `scroll` | optional `x`, `y`, `selector` | Scroll the page or a container |
|
|
182
|
+
| `wait` | `selector` or `text`, optional `timeout` | Wait for an element or text to appear |
|
|
183
|
+
| `moveTo` | `text` or `selector`, optional `within` | Move cursor to an element |
|
|
184
|
+
| `screenshot` | `output` | Save a PNG screenshot |
|
|
185
|
+
| `navigate` | `url` | Navigate to a new URL mid-video |
|
|
186
|
+
| `hover` | `text` or `selector`, optional `within` | Hover over an element (triggers CSS) |
|
|
187
|
+
| `select` | `selector`, `value` | Select a value in a dropdown |
|
|
188
|
+
|
|
189
|
+
All steps (except `pause`) accept an optional `delay` field (ms to wait after the step). Use `defaultDelay` at the top-level or per-video to set a default.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
Apache-2.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/record.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { collectIncludePaths } from "../record.js";
|
|
4
|
+
describe("collectIncludePaths", () => {
|
|
5
|
+
const configPath = "/project/webreel.config.json";
|
|
6
|
+
const configDir = dirname(configPath);
|
|
7
|
+
function makeConfig(overrides = {}) {
|
|
8
|
+
return {
|
|
9
|
+
videos: [
|
|
10
|
+
{
|
|
11
|
+
name: "test",
|
|
12
|
+
url: "https://example.com",
|
|
13
|
+
steps: [],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
it("returns empty array when no includes exist", () => {
|
|
20
|
+
const config = makeConfig();
|
|
21
|
+
expect(collectIncludePaths(config, configPath)).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
it("resolves top-level include paths relative to config dir", () => {
|
|
24
|
+
const config = makeConfig({ include: ["steps/setup.json"] });
|
|
25
|
+
const result = collectIncludePaths(config, configPath);
|
|
26
|
+
expect(result).toEqual([resolve(configDir, "steps/setup.json")]);
|
|
27
|
+
});
|
|
28
|
+
it("resolves per-video include paths", () => {
|
|
29
|
+
const config = makeConfig({
|
|
30
|
+
videos: [
|
|
31
|
+
{
|
|
32
|
+
name: "v1",
|
|
33
|
+
url: "https://example.com",
|
|
34
|
+
steps: [],
|
|
35
|
+
include: ["steps/v1-setup.json"],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
const result = collectIncludePaths(config, configPath);
|
|
40
|
+
expect(result).toEqual([resolve(configDir, "steps/v1-setup.json")]);
|
|
41
|
+
});
|
|
42
|
+
it("combines top-level and per-video includes", () => {
|
|
43
|
+
const config = makeConfig({
|
|
44
|
+
include: ["steps/shared.json"],
|
|
45
|
+
videos: [
|
|
46
|
+
{
|
|
47
|
+
name: "v1",
|
|
48
|
+
url: "https://example.com",
|
|
49
|
+
steps: [],
|
|
50
|
+
include: ["steps/v1.json"],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "v2",
|
|
54
|
+
url: "https://example.com",
|
|
55
|
+
steps: [],
|
|
56
|
+
include: ["steps/v2.json"],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
const result = collectIncludePaths(config, configPath);
|
|
61
|
+
expect(result).toEqual([
|
|
62
|
+
resolve(configDir, "steps/shared.json"),
|
|
63
|
+
resolve(configDir, "steps/v1.json"),
|
|
64
|
+
resolve(configDir, "steps/v2.json"),
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
it("deduplicates identical paths", () => {
|
|
68
|
+
const config = makeConfig({
|
|
69
|
+
include: ["steps/setup.json"],
|
|
70
|
+
videos: [
|
|
71
|
+
{
|
|
72
|
+
name: "v1",
|
|
73
|
+
url: "https://example.com",
|
|
74
|
+
steps: [],
|
|
75
|
+
include: ["steps/setup.json"],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "v2",
|
|
79
|
+
url: "https://example.com",
|
|
80
|
+
steps: [],
|
|
81
|
+
include: ["steps/setup.json"],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
const result = collectIncludePaths(config, configPath);
|
|
86
|
+
expect(result).toEqual([resolve(configDir, "steps/setup.json")]);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=record.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/record.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../src/commands/composite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,eAAO,MAAM,gBAAgB,SA6CzB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { resolve, dirname } from "node:path";
|
|
4
|
+
import { compose } from "@webreel/core";
|
|
5
|
+
import { loadWebreelConfig, resolveConfigPath, getConfigDir, filterVideosByName, } from "../lib/config.js";
|
|
6
|
+
import { extractThumbnailIfConfigured } from "../lib/runner.js";
|
|
7
|
+
export const compositeCommand = new Command("composite")
|
|
8
|
+
.description("Re-composite videos from stored raw recordings and timelines")
|
|
9
|
+
.argument("[videos...]", "Video names to composite (default: all)")
|
|
10
|
+
.option("-c, --config <path>", "Path to config file (default: webreel.config.json)")
|
|
11
|
+
.action(async (videoNames, opts) => {
|
|
12
|
+
const configPath = resolveConfigPath(opts.config);
|
|
13
|
+
const configDir = getConfigDir(configPath);
|
|
14
|
+
const webreelConfig = await loadWebreelConfig(configPath);
|
|
15
|
+
const videos = filterVideosByName(webreelConfig.videos, videoNames);
|
|
16
|
+
for (const video of videos) {
|
|
17
|
+
const rawPath = resolve(configDir, ".webreel", "raw", `${video.name}.mp4`);
|
|
18
|
+
const timelinePath = resolve(configDir, ".webreel", "timelines", `${video.name}.timeline.json`);
|
|
19
|
+
if (!existsSync(rawPath)) {
|
|
20
|
+
throw new Error(`Raw video not found: ${rawPath}. Run "webreel record" first.`);
|
|
21
|
+
}
|
|
22
|
+
if (!existsSync(timelinePath)) {
|
|
23
|
+
throw new Error(`Timeline not found: ${timelinePath}. Run "webreel record" first.`);
|
|
24
|
+
}
|
|
25
|
+
let timelineData;
|
|
26
|
+
try {
|
|
27
|
+
timelineData = JSON.parse(readFileSync(timelinePath, "utf-8"));
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
throw new Error(`Invalid timeline file: ${timelinePath}`, { cause: err });
|
|
31
|
+
}
|
|
32
|
+
const outputPath = video.output ?? resolve(configDir, "videos", `${video.name}.mp4`);
|
|
33
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
34
|
+
console.log(`Compositing: ${video.name}`);
|
|
35
|
+
await compose(rawPath, timelineData, outputPath, { sfx: video.sfx });
|
|
36
|
+
await extractThumbnailIfConfigured(video, outputPath);
|
|
37
|
+
console.log(`Done: ${outputPath}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=composite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composite.js","sourceRoot":"","sources":["../../src/commands/composite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAqB,MAAM,eAAe,CAAC;AAC3D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAEhE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC;KACrD,WAAW,CAAC,8DAA8D,CAAC;KAC3E,QAAQ,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAClE,MAAM,CAAC,qBAAqB,EAAE,oDAAoD,CAAC;KACnF,MAAM,CAAC,KAAK,EAAE,UAAoB,EAAE,IAAyB,EAAE,EAAE;IAChE,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,kBAAkB,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAEpE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,IAAI,MAAM,CAAC,CAAC;QAC3E,MAAM,YAAY,GAAG,OAAO,CAC1B,SAAS,EACT,UAAU,EACV,WAAW,EACX,GAAG,KAAK,CAAC,IAAI,gBAAgB,CAC9B,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,+BAA+B,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,uBAAuB,YAAY,+BAA+B,CACnE,CAAC;QACJ,CAAC;QAED,IAAI,YAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,0BAA0B,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,UAAU,GACd,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,IAAI,MAAM,CAAC,CAAC;QAEpE,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1C,MAAM,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAErE,MAAM,4BAA4B,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAEtD,OAAO,CAAC,GAAG,CAAC,SAAS,UAAU,EAAE,CAAC,CAAC;IACrC,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoCpC,eAAO,MAAM,WAAW,SAoBpB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { writeFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { DEFAULT_CONFIG_FILE } from "../lib/config.js";
|
|
5
|
+
const INIT_TEMPLATE = `{
|
|
6
|
+
// JSON Schema for IDE autocompletion (VS Code, Cursor, JetBrains).
|
|
7
|
+
// Full docs: https://webreel.dev/configuration
|
|
8
|
+
"$schema": "https://webreel.dev/schema/v1.json",
|
|
9
|
+
|
|
10
|
+
// Output directory for recorded videos (relative to this file).
|
|
11
|
+
"outDir": "./videos",
|
|
12
|
+
|
|
13
|
+
// Default delay (ms) after each step. Override per-step with "delay".
|
|
14
|
+
"defaultDelay": 500,
|
|
15
|
+
|
|
16
|
+
"videos": {
|
|
17
|
+
"VIDEO_NAME": {
|
|
18
|
+
"url": "VIDEO_URL",
|
|
19
|
+
"viewport": { "width": 1920, "height": 1080 },
|
|
20
|
+
|
|
21
|
+
// Optional: wait for an element before starting.
|
|
22
|
+
// "waitFor": "[data-ready]",
|
|
23
|
+
|
|
24
|
+
// Steps are executed in order. Each step is an action.
|
|
25
|
+
// Use "pause" for explicit waits; use "delay" on any step for post-step waits.
|
|
26
|
+
"steps": [
|
|
27
|
+
{ "action": "pause", "ms": 500 },
|
|
28
|
+
{ "action": "click", "text": "Get Started" },
|
|
29
|
+
{ "action": "key", "key": "mod+a", "delay": 1000 }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
export const initCommand = new Command("init")
|
|
36
|
+
.description("Scaffold a new webreel config file")
|
|
37
|
+
.option("--name <name>", "video name", "my-video")
|
|
38
|
+
.option("--url <url>", "starting URL", "https://example.com")
|
|
39
|
+
.option("-o, --output <file>", "output file path")
|
|
40
|
+
.action((opts) => {
|
|
41
|
+
const fileName = opts.output ?? DEFAULT_CONFIG_FILE;
|
|
42
|
+
const filePath = resolve(process.cwd(), fileName);
|
|
43
|
+
if (existsSync(filePath)) {
|
|
44
|
+
throw new Error(`File already exists: ${fileName}`);
|
|
45
|
+
}
|
|
46
|
+
const content = INIT_TEMPLATE.replace("VIDEO_NAME", opts.name).replace("VIDEO_URL", opts.url);
|
|
47
|
+
writeFileSync(filePath, content);
|
|
48
|
+
console.log(`Created ${fileName}`);
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BrB,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,eAAe,EAAE,YAAY,EAAE,UAAU,CAAC;KACjD,MAAM,CAAC,aAAa,EAAE,cAAc,EAAE,qBAAqB,CAAC;KAC5D,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,CAAC,IAAoD,EAAE,EAAE;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,IAAI,mBAAmB,CAAC;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;IAElD,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CACpE,WAAW,EACX,IAAI,CAAC,GAAG,CACT,CAAC;IAEF,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,WAAW,QAAQ,EAAE,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/commands/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,cAAc,SAkCxB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { loadWebreelConfig, resolveConfigPath, getConfigDir } from "../lib/config.js";
|
|
3
|
+
import { runVideo } from "../lib/runner.js";
|
|
4
|
+
export const previewCommand = new Command("preview")
|
|
5
|
+
.description("Run a video in a visible browser without recording")
|
|
6
|
+
.argument("[video]", "Video name to preview (default: first video)")
|
|
7
|
+
.option("-c, --config <path>", "Path to config file (default: webreel.config.json)")
|
|
8
|
+
.option("--verbose", "Log each step as it executes")
|
|
9
|
+
.action(async (videoName, opts) => {
|
|
10
|
+
const configPath = resolveConfigPath(opts.config);
|
|
11
|
+
const configDir = getConfigDir(configPath);
|
|
12
|
+
const verbose = opts.verbose ?? false;
|
|
13
|
+
const webreelConfig = await loadWebreelConfig(configPath);
|
|
14
|
+
let video;
|
|
15
|
+
if (videoName) {
|
|
16
|
+
video = webreelConfig.videos.find((v) => v.name === videoName);
|
|
17
|
+
if (!video) {
|
|
18
|
+
throw new Error(`Video "${videoName}" not found. Available: ${webreelConfig.videos.map((v) => v.name).join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
video = webreelConfig.videos[0];
|
|
23
|
+
if (!video) {
|
|
24
|
+
throw new Error("No videos defined in config.");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
console.log(`\nPreviewing: ${video.name}`);
|
|
28
|
+
await runVideo(video, { record: false, verbose, configDir });
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/commands/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;KACjD,WAAW,CAAC,oDAAoD,CAAC;KACjE,QAAQ,CAAC,SAAS,EAAE,8CAA8C,CAAC;KACnE,MAAM,CAAC,qBAAqB,EAAE,oDAAoD,CAAC;KACnF,MAAM,CAAC,WAAW,EAAE,8BAA8B,CAAC;KACnD,MAAM,CACL,KAAK,EACH,SAA6B,EAC7B,IAA4C,EAC5C,EAAE;IACF,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;IAEtC,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAE1D,IAAI,KAAK,CAAC;IACV,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,UAAU,SAAS,2BAA2B,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACnG,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,QAAQ,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;AAC/D,CAAC,CACF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { WebreelConfig } from "../lib/types.js";
|
|
3
|
+
export declare function collectIncludePaths(config: WebreelConfig, configPath: string): string[];
|
|
4
|
+
export declare const recordCommand: Command;
|
|
5
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/commands/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAavF;AA6CD,eAAO,MAAM,aAAa,SAkGvB,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { watch } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { loadWebreelConfig, resolveConfigPath, getConfigDir, filterVideosByName, } from "../lib/config.js";
|
|
5
|
+
import { runVideo } from "../lib/runner.js";
|
|
6
|
+
export function collectIncludePaths(config, configPath) {
|
|
7
|
+
const configDir = getConfigDir(configPath);
|
|
8
|
+
const paths = [];
|
|
9
|
+
const topIncludes = config.include ?? [];
|
|
10
|
+
for (const inc of topIncludes) {
|
|
11
|
+
paths.push(resolve(configDir, inc));
|
|
12
|
+
}
|
|
13
|
+
for (const video of config.videos) {
|
|
14
|
+
for (const inc of video.include ?? []) {
|
|
15
|
+
paths.push(resolve(configDir, inc));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return [...new Set(paths)];
|
|
19
|
+
}
|
|
20
|
+
function printResolvedConfig(config) {
|
|
21
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
22
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
23
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
24
|
+
console.log(bold("\nResolved configuration:\n"));
|
|
25
|
+
if (config.baseUrl)
|
|
26
|
+
console.log(` baseUrl: ${config.baseUrl}`);
|
|
27
|
+
if (config.outDir)
|
|
28
|
+
console.log(` outDir: ${config.outDir}`);
|
|
29
|
+
if (config.viewport)
|
|
30
|
+
console.log(` viewport: ${config.viewport.width}x${config.viewport.height}`);
|
|
31
|
+
if (config.defaultDelay !== undefined)
|
|
32
|
+
console.log(` defaultDelay: ${config.defaultDelay}ms`);
|
|
33
|
+
console.log(`\n ${bold(`${config.videos.length} video(s):`)}\n`);
|
|
34
|
+
for (const video of config.videos) {
|
|
35
|
+
console.log(` ${cyan(video.name)}`);
|
|
36
|
+
console.log(` url: ${video.url}`);
|
|
37
|
+
if (video.viewport)
|
|
38
|
+
console.log(` viewport: ${video.viewport.width}x${video.viewport.height}`);
|
|
39
|
+
if (video.zoom)
|
|
40
|
+
console.log(` zoom: ${video.zoom}x`);
|
|
41
|
+
if (video.waitFor)
|
|
42
|
+
console.log(` waitFor: ${video.waitFor}`);
|
|
43
|
+
if (video.output)
|
|
44
|
+
console.log(` output: ${video.output}`);
|
|
45
|
+
console.log(` steps: ${video.steps.length} step(s)`);
|
|
46
|
+
for (let i = 0; i < video.steps.length; i++) {
|
|
47
|
+
const step = video.steps[i];
|
|
48
|
+
const parts = [`${step.action}`];
|
|
49
|
+
if ("text" in step && step.text && step.action !== "type")
|
|
50
|
+
parts.push(`text="${step.text}"`);
|
|
51
|
+
if ("selector" in step && step.selector)
|
|
52
|
+
parts.push(`selector="${step.selector}"`);
|
|
53
|
+
if ("key" in step && step.key)
|
|
54
|
+
parts.push(`"${step.key}"`);
|
|
55
|
+
if ("ms" in step && step.ms !== undefined)
|
|
56
|
+
parts.push(`${step.ms}ms`);
|
|
57
|
+
if ("url" in step && step.url && step.action === "navigate")
|
|
58
|
+
parts.push(step.url);
|
|
59
|
+
const desc = "description" in step && step.description ? dim(`: ${step.description}`) : "";
|
|
60
|
+
console.log(` ${dim(`${i}:`)} ${parts.join(" ")}${desc}`);
|
|
61
|
+
}
|
|
62
|
+
console.log();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export const recordCommand = new Command("record")
|
|
66
|
+
.description("Record videos")
|
|
67
|
+
.argument("[videos...]", "Video names to record (default: all)")
|
|
68
|
+
.option("-c, --config <path>", "Path to config file (default: webreel.config.json)")
|
|
69
|
+
.option("--verbose", "Log each step as it executes")
|
|
70
|
+
.option("--watch", "Re-record when config files change")
|
|
71
|
+
.option("--dry-run", "Print the resolved config and step list without recording")
|
|
72
|
+
.option("--frames", "Save raw frames as JPEGs in .webreel/frames/")
|
|
73
|
+
.action(async (videoNames, opts) => {
|
|
74
|
+
const configPath = resolveConfigPath(opts.config);
|
|
75
|
+
const configDir = getConfigDir(configPath);
|
|
76
|
+
const verbose = opts.verbose ?? false;
|
|
77
|
+
const webreelConfig = await loadWebreelConfig(configPath);
|
|
78
|
+
const videos = filterVideosByName(webreelConfig.videos, videoNames);
|
|
79
|
+
if (opts.dryRun) {
|
|
80
|
+
const filtered = { ...webreelConfig, videos };
|
|
81
|
+
printResolvedConfig(filtered);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const video of videos) {
|
|
85
|
+
await runVideo(video, { record: true, verbose, configDir, frames: opts.frames });
|
|
86
|
+
}
|
|
87
|
+
if (opts.watch) {
|
|
88
|
+
console.log("\nWatching for changes...");
|
|
89
|
+
let timer = null;
|
|
90
|
+
let recordingInProgress = null;
|
|
91
|
+
const watchers = [];
|
|
92
|
+
const closeAllWatchers = () => {
|
|
93
|
+
for (const w of watchers)
|
|
94
|
+
w.close();
|
|
95
|
+
watchers.length = 0;
|
|
96
|
+
};
|
|
97
|
+
const setupWatchers = (cfg) => {
|
|
98
|
+
closeAllWatchers();
|
|
99
|
+
watchers.push(watch(configPath, onFileChange));
|
|
100
|
+
for (const p of collectIncludePaths(cfg, configPath)) {
|
|
101
|
+
watchers.push(watch(p, onFileChange));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const onFileChange = () => {
|
|
105
|
+
if (timer)
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
timer = setTimeout(async () => {
|
|
108
|
+
timer = null;
|
|
109
|
+
console.log("\nRe-recording...");
|
|
110
|
+
let latestConfig = null;
|
|
111
|
+
const run = (async () => {
|
|
112
|
+
try {
|
|
113
|
+
latestConfig = await loadWebreelConfig(configPath);
|
|
114
|
+
const updatedVideos = filterVideosByName(latestConfig.videos, videoNames);
|
|
115
|
+
for (const video of updatedVideos) {
|
|
116
|
+
await runVideo(video, {
|
|
117
|
+
record: true,
|
|
118
|
+
verbose,
|
|
119
|
+
configDir,
|
|
120
|
+
frames: opts.frames,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error(`Error re-recording:`, err);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
recordingInProgress = null;
|
|
129
|
+
setupWatchers(latestConfig ?? webreelConfig);
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
recordingInProgress = run;
|
|
133
|
+
await run;
|
|
134
|
+
}, 300);
|
|
135
|
+
};
|
|
136
|
+
setupWatchers(webreelConfig);
|
|
137
|
+
process.on("SIGINT", async () => {
|
|
138
|
+
closeAllWatchers();
|
|
139
|
+
if (recordingInProgress) {
|
|
140
|
+
console.log("\nWaiting for current recording to finish...");
|
|
141
|
+
await recordingInProgress;
|
|
142
|
+
}
|
|
143
|
+
process.exit(0);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/commands/record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAepC,eAAO,MAAM,eAAe,SAyCxB,CAAC"}
|