recordable 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/LICENSE +21 -0
- package/README.md +303 -0
- package/dist/actions.d.ts +140 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +184 -0
- package/dist/actions.js.map +1 -0
- package/dist/audio/track.d.ts +45 -0
- package/dist/audio/track.d.ts.map +1 -0
- package/dist/audio/track.js +61 -0
- package/dist/audio/track.js.map +1 -0
- package/dist/browser/cursor.d.ts +33 -0
- package/dist/browser/cursor.d.ts.map +1 -0
- package/dist/browser/cursor.js +118 -0
- package/dist/browser/cursor.js.map +1 -0
- package/dist/browser/dom.d.ts +31 -0
- package/dist/browser/dom.d.ts.map +1 -0
- package/dist/browser/dom.js +134 -0
- package/dist/browser/dom.js.map +1 -0
- package/dist/browser/play-button.d.ts +11 -0
- package/dist/browser/play-button.d.ts.map +1 -0
- package/dist/browser/play-button.js +87 -0
- package/dist/browser/play-button.js.map +1 -0
- package/dist/browser/runtime.d.ts +66 -0
- package/dist/browser/runtime.d.ts.map +1 -0
- package/dist/browser/runtime.js +271 -0
- package/dist/browser/runtime.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +131 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose/mix.d.ts +13 -0
- package/dist/compose/mix.d.ts.map +1 -0
- package/dist/compose/mix.js +50 -0
- package/dist/compose/mix.js.map +1 -0
- package/dist/compose/recordable.d.ts +149 -0
- package/dist/compose/recordable.d.ts.map +1 -0
- package/dist/compose/recordable.js +337 -0
- package/dist/compose/recordable.js.map +1 -0
- package/dist/compose/session.d.ts +38 -0
- package/dist/compose/session.d.ts.map +1 -0
- package/dist/compose/session.js +122 -0
- package/dist/compose/session.js.map +1 -0
- package/dist/config.d.ts +93 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/ffmpeg.d.ts +8 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +55 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/formats/json.d.ts +12 -0
- package/dist/formats/json.d.ts.map +1 -0
- package/dist/formats/json.js +20 -0
- package/dist/formats/json.js.map +1 -0
- package/dist/formats/markdown/method.d.ts +25 -0
- package/dist/formats/markdown/method.d.ts.map +1 -0
- package/dist/formats/markdown/method.js +48 -0
- package/dist/formats/markdown/method.js.map +1 -0
- package/dist/formats/markdown/parse.d.ts +44 -0
- package/dist/formats/markdown/parse.d.ts.map +1 -0
- package/dist/formats/markdown/parse.js +143 -0
- package/dist/formats/markdown/parse.js.map +1 -0
- package/dist/fs.d.ts +9 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +30 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +45 -0
- package/dist/logger.js.map +1 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +100 -0
- package/dist/schema.js.map +1 -0
- package/dist/script.d.ts +21 -0
- package/dist/script.d.ts.map +1 -0
- package/dist/script.js +26 -0
- package/dist/script.js.map +1 -0
- package/dist/targets.d.ts +6 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +13 -0
- package/dist/targets.js.map +1 -0
- package/dist/timing.d.ts +41 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +149 -0
- package/dist/timing.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +8 -0
- package/dist/utils.js.map +1 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +54 -0
- package/dist/validate.js.map +1 -0
- package/dist/video/recorder.d.ts +57 -0
- package/dist/video/recorder.d.ts.map +1 -0
- package/dist/video/recorder.js +238 -0
- package/dist/video/recorder.js.map +1 -0
- package/dist/video/stitch.d.ts +15 -0
- package/dist/video/stitch.d.ts.map +1 -0
- package/dist/video/stitch.js +111 -0
- package/dist/video/stitch.js.map +1 -0
- package/dist/voiceover/alignment.d.ts +14 -0
- package/dist/voiceover/alignment.d.ts.map +1 -0
- package/dist/voiceover/alignment.js +13 -0
- package/dist/voiceover/alignment.js.map +1 -0
- package/dist/voiceover/cache.d.ts +22 -0
- package/dist/voiceover/cache.d.ts.map +1 -0
- package/dist/voiceover/cache.js +55 -0
- package/dist/voiceover/cache.js.map +1 -0
- package/dist/voiceover/compile.d.ts +35 -0
- package/dist/voiceover/compile.d.ts.map +1 -0
- package/dist/voiceover/compile.js +194 -0
- package/dist/voiceover/compile.js.map +1 -0
- package/dist/voiceover/elevenlabs.d.ts +16 -0
- package/dist/voiceover/elevenlabs.d.ts.map +1 -0
- package/dist/voiceover/elevenlabs.js +66 -0
- package/dist/voiceover/elevenlabs.js.map +1 -0
- package/dist/voiceover/index.d.ts +7 -0
- package/dist/voiceover/index.d.ts.map +1 -0
- package/dist/voiceover/index.js +8 -0
- package/dist/voiceover/index.js.map +1 -0
- package/dist/voiceover/mock.d.ts +15 -0
- package/dist/voiceover/mock.d.ts.map +1 -0
- package/dist/voiceover/mock.js +41 -0
- package/dist/voiceover/mock.js.map +1 -0
- package/dist/voiceover/types.d.ts +31 -0
- package/dist/voiceover/types.d.ts.map +1 -0
- package/dist/voiceover/types.js +10 -0
- package/dist/voiceover/types.js.map +1 -0
- package/package.json +86 -0
- package/recordable.schema.json +738 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cam Parry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# recordable
|
|
2
|
+
|
|
3
|
+
Programmatic, repeatable browser screen recording. Describe a session as a fluent
|
|
4
|
+
chain of actions — `visit`, `click`, `type`, `zoom`, `scroll` — and `recordable`
|
|
5
|
+
drives a real [Puppeteer](https://pptr.dev/) browser and captures a clean MP4,
|
|
6
|
+
complete with an animated cursor, smooth zooming/scrolling, and human-like typing.
|
|
7
|
+
|
|
8
|
+
Because the recording is _code_, it's deterministic and re-runnable: regenerate the
|
|
9
|
+
exact same capture whenever the UI changes — for product demos, onboarding clips,
|
|
10
|
+
documentation GIFs, release notes, or visual regression footage.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Recordable } from "recordable";
|
|
14
|
+
|
|
15
|
+
await new Recordable({ typingSpeed: 120 })
|
|
16
|
+
.pause() // skip the initial page load
|
|
17
|
+
.visit("https://example.com")
|
|
18
|
+
.resume()
|
|
19
|
+
.zoom(1.5, { origin: "#newsletter" })
|
|
20
|
+
.type("#email", "hello@example.com")
|
|
21
|
+
.click("text:Sign up")
|
|
22
|
+
.scroll("bottom")
|
|
23
|
+
.resetZoom()
|
|
24
|
+
.wait(1500)
|
|
25
|
+
.run(); // finalises automatically — no start()/stop()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Recording is **on by default** and finalises when `.run()` ends — there's no
|
|
29
|
+
`start()`/`stop()`. Use `pause()` / `resume()` to carve out anything you don't
|
|
30
|
+
want on camera; every captured segment is stitched into one seamless MP4.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Fluent, queued API** — chain actions; nothing runs until `.run()`.
|
|
35
|
+
- **Deterministic & repeatable** — the recording is code, so it reproduces exactly.
|
|
36
|
+
- **Animated cursor overlay** with realistic movement and click feedback.
|
|
37
|
+
- **Smooth zoom & scroll** that animate origin and scale together.
|
|
38
|
+
- **Human-like typing** with jitter and natural pauses.
|
|
39
|
+
- **Element targeting** by CSS selector or visible text (`text:` prefix).
|
|
40
|
+
- **Off-camera segments** — `pause()`/`resume()` skip setup, navigations, or whole
|
|
41
|
+
screens; segments are auto-stitched into one seamless video.
|
|
42
|
+
- **Manual steps / logins** — `resumeOnInput()` waits for an in-page ▶ Play button
|
|
43
|
+
(see below), so you can sign in by hand before recording.
|
|
44
|
+
- **Auto-scroll** to bring elements into view before interacting.
|
|
45
|
+
- **Declarative JSON scripts + CLI** — author a recording as JSON (with a published
|
|
46
|
+
schema for editor autocomplete) and run it with `npx recordable demo.json`, no
|
|
47
|
+
install or TypeScript required.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npm install recordable
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Frames are captured via the Chrome DevTools Protocol and encoded with **FFmpeg** —
|
|
56
|
+
there's no external screen-recorder dependency. The ffmpeg binary ships via
|
|
57
|
+
[`@ffmpeg-installer/ffmpeg`](https://www.npmjs.com/package/@ffmpeg-installer/ffmpeg),
|
|
58
|
+
so there's nothing else to install (a system `ffmpeg` on your `PATH` is used as a
|
|
59
|
+
fallback).
|
|
60
|
+
|
|
61
|
+
## Declarative scripts (JSON)
|
|
62
|
+
|
|
63
|
+
You don't have to write TypeScript. A recording can be a plain **JSON** file — an
|
|
64
|
+
array of `{ action, ... }` actions that map 1:1 onto the chainable API, optionally
|
|
65
|
+
wrapped with a `config`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"$schema": "https://raw.githubusercontent.com/paragramagency/recordable/main/recordable.schema.json",
|
|
70
|
+
"config": { "typingSpeed": 14 },
|
|
71
|
+
"actions": [
|
|
72
|
+
{ "action": "pause" },
|
|
73
|
+
{ "action": "visit", "url": "https://example.com" },
|
|
74
|
+
{ "action": "resume" },
|
|
75
|
+
{ "action": "zoom", "level": 1.5, "origin": "#newsletter" },
|
|
76
|
+
{ "action": "type", "target": "#email", "text": "hello@example.com" },
|
|
77
|
+
{ "action": "click", "target": "text:Sign up" },
|
|
78
|
+
{ "action": "waitFor", "target": "text:Thanks", "state": "visible" },
|
|
79
|
+
{ "action": "resetZoom" }
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Each step's keys are the named arguments of the matching method — `type(target, text)`
|
|
85
|
+
→ `{ "action": "type", "target": …, "text": … }`; `waitFor`'s `state`/`timeout` are
|
|
86
|
+
top-level keys.
|
|
87
|
+
|
|
88
|
+
**Editor support.** Add the `"$schema"` line above (a URL, or a relative path to a
|
|
89
|
+
local copy) and your editor gives you autocomplete, required-key checking, and
|
|
90
|
+
typo catching for every action — no TypeScript needed. The schema is published as
|
|
91
|
+
`recordable.schema.json`.
|
|
92
|
+
|
|
93
|
+
Run a script from code with `runScript` (or `fromJSON` to build without running):
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { runScript } from "recordable";
|
|
97
|
+
import demo from "./demo.json" with { type: "json" };
|
|
98
|
+
|
|
99
|
+
await runScript(demo);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### CLI
|
|
103
|
+
|
|
104
|
+
Or run a JSON file directly — **no install required** via `npx`:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
npx recordable demo.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
recordable <script.json> [options]
|
|
112
|
+
|
|
113
|
+
--check Validate the script and exit (no browser, no recording)
|
|
114
|
+
--headless Run without a visible browser window
|
|
115
|
+
--silent Suppress recorder console output
|
|
116
|
+
--out-dir <dir> Output directory (overrides the script's config)
|
|
117
|
+
--name <name> Output filename (without extension)
|
|
118
|
+
--no-timestamp Don't prepend an ISO timestamp to the filename
|
|
119
|
+
-h, --help Show this help
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Relative `visit` URLs (e.g. `"./index.html"`) and a relative `outputDir` resolve
|
|
123
|
+
against the script file, so a script, its mockups, and its output stay together
|
|
124
|
+
regardless of where you run it from. `--out-dir` overrides the output location
|
|
125
|
+
(taken relative to the current directory). `--check` validates a script in CI or
|
|
126
|
+
while authoring without launching a browser.
|
|
127
|
+
|
|
128
|
+
## Off-camera work & seamless segments
|
|
129
|
+
|
|
130
|
+
`pause()` stops the camera but the chain keeps running, so anything up to the
|
|
131
|
+
next `resume()` happens off-camera — page loads, data setup, even navigating to a
|
|
132
|
+
different screen. Each recorded stretch is a segment, and they're concatenated
|
|
133
|
+
(losslessly, by stream-copy where possible) into a single MP4 on `.run()`:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await new Recordable()
|
|
137
|
+
.visit("/dashboard")
|
|
138
|
+
.click("text:Reports") // recorded
|
|
139
|
+
.pause()
|
|
140
|
+
.visit("/admin") // off-camera: jump to another screen, reset state…
|
|
141
|
+
.click("text:Seed demo data")
|
|
142
|
+
.resume()
|
|
143
|
+
.click("text:Run report") // recorded again — stitched seamlessly to the above
|
|
144
|
+
.run();
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Recording behind a login (manual steps)
|
|
148
|
+
|
|
149
|
+
Run **headful** (`headless: false`) so the Chrome window is interactive. Keep the
|
|
150
|
+
camera off while you sign in by hand, then `resumeOnInput()` waits for you to
|
|
151
|
+
click an **in-page ▶ Play button** (or press Enter) before recording resumes:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
await new Recordable({ headless: false })
|
|
155
|
+
.pause() // camera off — the login isn't recorded
|
|
156
|
+
.visit("https://app.example.com/login")
|
|
157
|
+
.resumeOnInput("Log in, then click ▶ Play to start recording")
|
|
158
|
+
.visit("https://app.example.com/dashboard")
|
|
159
|
+
.click("text:New project")
|
|
160
|
+
.run();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- **`resumeOnInput(message?)`** injects a ▶ Play button into the page itself and
|
|
164
|
+
blocks until you click it (Enter in the terminal also works). The button is
|
|
165
|
+
re-injected across navigations, so it survives login redirects.
|
|
166
|
+
- Prefer an automatic trigger? Use **`waitFor("#dashboard")`** after `resume()` to
|
|
167
|
+
carry on once a post-login element appears — no clicking required.
|
|
168
|
+
|
|
169
|
+
Because the manual step sits inside a `pause()`, the sign-in never appears in the
|
|
170
|
+
video.
|
|
171
|
+
|
|
172
|
+
## Intros, outros & mid-rolls
|
|
173
|
+
|
|
174
|
+
`insert(path)` splices an external video clip into the timeline at that point —
|
|
175
|
+
its position decides the role: first call is an intro, last is an outro, anything
|
|
176
|
+
in between is a mid-roll. The clip is normalized to the recording's resolution,
|
|
177
|
+
fps, and codec so the join is seamless.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
new Recordable()
|
|
181
|
+
.insert("intro.mp4", { fadeIn: 500, fadeOut: 600 }) // plays first
|
|
182
|
+
.visit("https://example.com")
|
|
183
|
+
.click("text:Get started")
|
|
184
|
+
.insert("feature-promo.mp4", { fadeIn: 600, fadeOut: 600 }) // mid-roll
|
|
185
|
+
.scroll("bottom")
|
|
186
|
+
.insert("outro.mp4", { fadeOut: 500 }) // plays last
|
|
187
|
+
.run();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
No `pause()`/`resume()` needed — `insert` seals the current segment and recording
|
|
191
|
+
resumes into a fresh one on the next action automatically. (Audio on the clip is
|
|
192
|
+
currently dropped; voiceover support is on the roadmap.)
|
|
193
|
+
|
|
194
|
+
**Cross-fades.** Pass `fadeIn` / `fadeOut` (ms) to dissolve rather than hard-cut.
|
|
195
|
+
A fade blends the clip with the **neighbouring recorded footage** (a true
|
|
196
|
+
cross-dissolve), or fades from/to **black** at the timeline ends where there's no
|
|
197
|
+
neighbour — so an intro's `fadeIn` fades up from black and dissolves into the page
|
|
198
|
+
on `fadeOut`, while an outro's `fadeOut` dissolves down to black. Omit them for a
|
|
199
|
+
hard cut. A cross-fade of _d_ ms overlaps the two pieces by _d_, shortening the
|
|
200
|
+
timeline by that much at each faded boundary.
|
|
201
|
+
|
|
202
|
+
## API
|
|
203
|
+
|
|
204
|
+
Create an instance with optional [config](#configuration), chain actions, then
|
|
205
|
+
`await .run()`.
|
|
206
|
+
|
|
207
|
+
### Recording
|
|
208
|
+
|
|
209
|
+
Recording is on by default and finalises automatically on `.run()`. These control
|
|
210
|
+
what lands on camera:
|
|
211
|
+
|
|
212
|
+
| Method | Description |
|
|
213
|
+
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
214
|
+
| `pause()` | Stop capturing; the chain keeps running off-camera. |
|
|
215
|
+
| `resume()` | Resume capturing in a fresh segment, immediately. |
|
|
216
|
+
| `resumeOnInput(message?)` | Resume only after the user clicks the in-page ▶ Play button (or presses Enter). |
|
|
217
|
+
| `insert(path, opts?)` | Splice an external clip (intro / outro / mid-roll) into the timeline; `opts.fadeIn`/`fadeOut` (ms) cross-fade it. |
|
|
218
|
+
|
|
219
|
+
### Navigation & waiting
|
|
220
|
+
|
|
221
|
+
| Method | Description |
|
|
222
|
+
| ------------------------ | --------------------------------------------------------------- |
|
|
223
|
+
| `visit(url, options?)` | Navigate and wait for the page to settle. |
|
|
224
|
+
| `waitFor(target, opts?)` | Wait for an element to become `visible` / `hidden` / `present`. |
|
|
225
|
+
| `wait(ms)` | Pause the sequence for `ms` milliseconds. |
|
|
226
|
+
|
|
227
|
+
### Interactions
|
|
228
|
+
|
|
229
|
+
| Method | Description |
|
|
230
|
+
| ----------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
231
|
+
| `click(target)` | Click an element. |
|
|
232
|
+
| `hover(target)` | Move onto an element to reveal `:hover` state (no click). |
|
|
233
|
+
| `type(target, text, { duration? })` | Type into a field with human-like timing; `duration` (ms) spreads keystrokes evenly with no jitter. |
|
|
234
|
+
| `clear(target)` | Select-all + delete the contents of a field. |
|
|
235
|
+
| `select(target, value)` | Choose an option in a native `<select>` (the OS-drawn option list isn't captured — see note below). |
|
|
236
|
+
| `key(key)` | Press a key, e.g. `"Escape"`, `"Enter"`, `"Tab"`. |
|
|
237
|
+
| `mouse(target \| {x, y})` | Move the cursor to an element or coordinates. |
|
|
238
|
+
|
|
239
|
+
> The browser draws an open `<select>`'s option list with the OS, outside the page,
|
|
240
|
+
> so the screencast can't capture it — `select()` shows the cursor and the value
|
|
241
|
+
> changing, but not the dropdown. For an on-camera dropdown, build a custom one from
|
|
242
|
+
> `click()`s.
|
|
243
|
+
|
|
244
|
+
### Camera
|
|
245
|
+
|
|
246
|
+
| Method | Description |
|
|
247
|
+
| ------------------------------------- | ----------------------------------------------------------------- |
|
|
248
|
+
| `scroll(target, { duration? })` | Smooth-scroll to `"top"`/`"bottom"`, a selector, or a Y position. |
|
|
249
|
+
| `zoom(level, { origin?, duration? })` | Smoothly scale from an origin (keyword, `%`, or selector). |
|
|
250
|
+
| `resetZoom({ duration? })` | Smoothly return to 1×. |
|
|
251
|
+
| `setConfig(config)` | Merge config mid-sequence (takes effect at that point). |
|
|
252
|
+
|
|
253
|
+
### Targeting
|
|
254
|
+
|
|
255
|
+
Anywhere a `target` is accepted you can pass:
|
|
256
|
+
|
|
257
|
+
- a **CSS selector** — `"#id"`, `".card"`, `'[name="email"]'`
|
|
258
|
+
- a **`text:` prefix** — `"text:Sign up"` matches by visible text
|
|
259
|
+
|
|
260
|
+
## Configuration
|
|
261
|
+
|
|
262
|
+
All options are optional; defaults shown.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
new Recordable({
|
|
266
|
+
viewport: { width: 1920, height: 1080 },
|
|
267
|
+
fps: 30,
|
|
268
|
+
outputDir: "./output",
|
|
269
|
+
outputName: "recordable",
|
|
270
|
+
outputTimestamp: true, // prepend an ISO timestamp to the filename
|
|
271
|
+
headless: false,
|
|
272
|
+
typingSpeed: 7, // characters per second
|
|
273
|
+
videoCrf: 18, // lower = better quality, larger file
|
|
274
|
+
videoCodec: "libx264",
|
|
275
|
+
videoPreset: "ultrafast",
|
|
276
|
+
zoomDuration: 600, // ms
|
|
277
|
+
actionDelay: 300, // ms inserted between every action
|
|
278
|
+
silent: false,
|
|
279
|
+
autoScroll: true, // scroll elements into view before interacting
|
|
280
|
+
scrollMargin: 120, // px kept around an element when auto-scrolling
|
|
281
|
+
scrollSpeed: 1500, // px/s
|
|
282
|
+
scrollDuration: 1200, // ms for the scroll action's transition
|
|
283
|
+
cursor: true, // show the animated cursor overlay
|
|
284
|
+
visitTimeout: 30_000, // ms for navigation / waitFor
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Development
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
npm install
|
|
292
|
+
npm run build # type-check + emit dist/ with .d.ts
|
|
293
|
+
npm run gen:schema # regenerate recordable.schema.json from the action manifest
|
|
294
|
+
npx tsx my-script.ts # run a recording script directly
|
|
295
|
+
node dist/cli.js demo.json # run a JSON script through the CLI locally
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The JSON action set and its schema are both generated from one manifest in
|
|
299
|
+
`src/actions.ts`; run `npm run gen:schema` after changing it.
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT © Cam Parry
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Per-action argument schema (the keyed args, excluding the `action`
|
|
4
|
+
* discriminator). strictObject so an unknown key (a typo) fails validation.
|
|
5
|
+
*/
|
|
6
|
+
declare const ACTIONS: {
|
|
7
|
+
pause: z.ZodObject<{}, z.core.$strict>;
|
|
8
|
+
resume: z.ZodObject<{}, z.core.$strict>;
|
|
9
|
+
resumeOnInput: z.ZodObject<{
|
|
10
|
+
message: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
insert: z.ZodObject<{
|
|
13
|
+
path: z.ZodString;
|
|
14
|
+
fadeIn: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
fadeOut: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
audio: z.ZodObject<{
|
|
18
|
+
path: z.ZodString;
|
|
19
|
+
wait: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
volume: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
setConfig: z.ZodObject<{
|
|
23
|
+
config: z.ZodObject<{
|
|
24
|
+
viewport: z.ZodDefault<z.ZodObject<{
|
|
25
|
+
width: z.ZodNumber;
|
|
26
|
+
height: z.ZodNumber;
|
|
27
|
+
}, z.core.$strict>>;
|
|
28
|
+
fps: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
outputDir: z.ZodDefault<z.ZodString>;
|
|
30
|
+
outputName: z.ZodDefault<z.ZodString>;
|
|
31
|
+
outputTimestamp: z.ZodDefault<z.ZodBoolean>;
|
|
32
|
+
assetsDir: z.ZodDefault<z.ZodString>;
|
|
33
|
+
headless: z.ZodDefault<z.ZodBoolean>;
|
|
34
|
+
launchArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
35
|
+
typingSpeed: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
videoCrf: z.ZodDefault<z.ZodNumber>;
|
|
37
|
+
videoCodec: z.ZodDefault<z.ZodString>;
|
|
38
|
+
videoPreset: z.ZodDefault<z.ZodString>;
|
|
39
|
+
zoomDuration: z.ZodDefault<z.ZodNumber>;
|
|
40
|
+
actionDelay: z.ZodDefault<z.ZodNumber>;
|
|
41
|
+
silent: z.ZodDefault<z.ZodBoolean>;
|
|
42
|
+
autoScroll: z.ZodDefault<z.ZodBoolean>;
|
|
43
|
+
scrollMargin: z.ZodDefault<z.ZodNumber>;
|
|
44
|
+
scrollSpeed: z.ZodDefault<z.ZodNumber>;
|
|
45
|
+
scrollDuration: z.ZodDefault<z.ZodNumber>;
|
|
46
|
+
cursor: z.ZodDefault<z.ZodBoolean>;
|
|
47
|
+
visitTimeout: z.ZodDefault<z.ZodNumber>;
|
|
48
|
+
baseDir: z.ZodDefault<z.ZodString>;
|
|
49
|
+
}, z.core.$strict>;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
visit: z.ZodObject<{
|
|
52
|
+
url: z.ZodString;
|
|
53
|
+
waitUntil: z.ZodOptional<z.ZodString>;
|
|
54
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
55
|
+
referer: z.ZodOptional<z.ZodString>;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
waitFor: z.ZodObject<{
|
|
58
|
+
target: z.ZodString;
|
|
59
|
+
state: z.ZodOptional<z.ZodEnum<{
|
|
60
|
+
visible: "visible";
|
|
61
|
+
hidden: "hidden";
|
|
62
|
+
present: "present";
|
|
63
|
+
}>>;
|
|
64
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
65
|
+
}, z.core.$strict>;
|
|
66
|
+
click: z.ZodObject<{
|
|
67
|
+
target: z.ZodString;
|
|
68
|
+
}, z.core.$strict>;
|
|
69
|
+
hover: z.ZodObject<{
|
|
70
|
+
target: z.ZodString;
|
|
71
|
+
}, z.core.$strict>;
|
|
72
|
+
type: z.ZodObject<{
|
|
73
|
+
target: z.ZodString;
|
|
74
|
+
text: z.ZodString;
|
|
75
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
76
|
+
}, z.core.$strict>;
|
|
77
|
+
clear: z.ZodObject<{
|
|
78
|
+
target: z.ZodString;
|
|
79
|
+
}, z.core.$strict>;
|
|
80
|
+
select: z.ZodObject<{
|
|
81
|
+
target: z.ZodString;
|
|
82
|
+
value: z.ZodString;
|
|
83
|
+
}, z.core.$strict>;
|
|
84
|
+
key: z.ZodObject<{
|
|
85
|
+
key: z.ZodString;
|
|
86
|
+
}, z.core.$strict>;
|
|
87
|
+
mouse: z.ZodObject<{
|
|
88
|
+
target: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
89
|
+
x: z.ZodNumber;
|
|
90
|
+
y: z.ZodNumber;
|
|
91
|
+
}, z.core.$strict>]>;
|
|
92
|
+
}, z.core.$strict>;
|
|
93
|
+
scroll: z.ZodObject<{
|
|
94
|
+
target: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
95
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
96
|
+
}, z.core.$strict>;
|
|
97
|
+
zoom: z.ZodObject<{
|
|
98
|
+
level: z.ZodNumber;
|
|
99
|
+
origin: z.ZodOptional<z.ZodString>;
|
|
100
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
101
|
+
}, z.core.$strict>;
|
|
102
|
+
resetZoom: z.ZodObject<{
|
|
103
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
104
|
+
}, z.core.$strict>;
|
|
105
|
+
wait: z.ZodObject<{
|
|
106
|
+
ms: z.ZodNumber;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
};
|
|
109
|
+
/** A single action: the action name plus its flat named arguments. */
|
|
110
|
+
export type Action = {
|
|
111
|
+
action: string;
|
|
112
|
+
[key: string]: unknown;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Validate one keyed action against the manifest: the action must exist and its
|
|
116
|
+
* argument *values* (and key names) must match the action's schema — so a wrong
|
|
117
|
+
* type (`{ action: "zoom", level: "big" }`) or a typo'd key fails here. Shared by
|
|
118
|
+
* the JSON loader and the Markdown mapper.
|
|
119
|
+
*/
|
|
120
|
+
export declare function validateAction(step: Action): void;
|
|
121
|
+
/**
|
|
122
|
+
* Turn one flat action into the positional argument list for its method.
|
|
123
|
+
*
|
|
124
|
+
* Optional positionals that are absent become `undefined` — JavaScript default
|
|
125
|
+
* parameters then apply, so a present later arg never lands in the wrong slot.
|
|
126
|
+
* Bag keys collapse into a single trailing options object.
|
|
127
|
+
*/
|
|
128
|
+
declare function buildArgs(step: Action, name: string): unknown[];
|
|
129
|
+
/**
|
|
130
|
+
* Map a positional method call — `{ name, args }` as produced by the Markdown
|
|
131
|
+
* parser — onto a flat keyed {@link Action}, the same IR the JSON layer uses.
|
|
132
|
+
* Positional args are named by manifest order; a trailing options object is
|
|
133
|
+
* flattened to top-level keys. The result is validated, so value/key typos throw.
|
|
134
|
+
*/
|
|
135
|
+
export declare function callToAction(name: string, args: readonly unknown[]): Action;
|
|
136
|
+
/** The action manifest, exported so schema/docs tooling can read it. */
|
|
137
|
+
export { ACTIONS };
|
|
138
|
+
/** Exported for unit tests: map a keyed action to its positional method args. */
|
|
139
|
+
export { buildArgs };
|
|
140
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAezB;;;GAGG;AACH,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyD0B,CAAC;AAWxC,sEAAsE;AACtE,MAAM,MAAM,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAuChE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBjD;AAED;;;;;;GAMG;AACH,iBAAS,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAcxD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CA2C3E;AAED,wEAAwE;AACxE,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,iFAAiF;AACjF,OAAO,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { RecordableError } from "./errors.js";
|
|
3
|
+
import { ConfigSchema } from "./config.js";
|
|
4
|
+
// ─── Action model ────────────────────────────────────────────────────────────
|
|
5
|
+
//
|
|
6
|
+
// A single action is a flat `{ action, ...args }` object that maps ~1:1 onto the
|
|
7
|
+
// chainable API. The ACTIONS manifest below is the single source of truth: one
|
|
8
|
+
// Zod schema per action drives value-level validation, the published JSON Schema
|
|
9
|
+
// (see schema.ts), and the Markdown marker mapping. The document that strings
|
|
10
|
+
// actions together — the `Script` type and its helpers — lives in script.ts.
|
|
11
|
+
const STATE = z.enum(["visible", "hidden", "present"]);
|
|
12
|
+
const XY = z.strictObject({ x: z.number(), y: z.number() });
|
|
13
|
+
/**
|
|
14
|
+
* Per-action argument schema (the keyed args, excluding the `action`
|
|
15
|
+
* discriminator). strictObject so an unknown key (a typo) fails validation.
|
|
16
|
+
*/
|
|
17
|
+
const ACTIONS = {
|
|
18
|
+
// Recording control
|
|
19
|
+
pause: z.strictObject({}),
|
|
20
|
+
resume: z.strictObject({}),
|
|
21
|
+
resumeOnInput: z.strictObject({ message: z.string().optional() }),
|
|
22
|
+
insert: z.strictObject({
|
|
23
|
+
path: z.string(),
|
|
24
|
+
fadeIn: z.number().optional(),
|
|
25
|
+
fadeOut: z.number().optional(),
|
|
26
|
+
}),
|
|
27
|
+
audio: z.strictObject({
|
|
28
|
+
path: z.string(),
|
|
29
|
+
wait: z.boolean().optional(),
|
|
30
|
+
volume: z.number().optional(),
|
|
31
|
+
}),
|
|
32
|
+
setConfig: z.strictObject({ config: ConfigSchema }),
|
|
33
|
+
// Navigation
|
|
34
|
+
visit: z.strictObject({
|
|
35
|
+
url: z.string(),
|
|
36
|
+
waitUntil: z.string().optional(),
|
|
37
|
+
timeout: z.number().optional(),
|
|
38
|
+
referer: z.string().optional(),
|
|
39
|
+
}),
|
|
40
|
+
waitFor: z.strictObject({
|
|
41
|
+
target: z.string(),
|
|
42
|
+
state: STATE.optional(),
|
|
43
|
+
timeout: z.number().optional(),
|
|
44
|
+
}),
|
|
45
|
+
// Interactions
|
|
46
|
+
click: z.strictObject({ target: z.string() }),
|
|
47
|
+
hover: z.strictObject({ target: z.string() }),
|
|
48
|
+
type: z.strictObject({
|
|
49
|
+
target: z.string(),
|
|
50
|
+
text: z.string(),
|
|
51
|
+
duration: z.number().optional(),
|
|
52
|
+
}),
|
|
53
|
+
clear: z.strictObject({ target: z.string() }),
|
|
54
|
+
select: z.strictObject({ target: z.string(), value: z.string() }),
|
|
55
|
+
key: z.strictObject({ key: z.string() }),
|
|
56
|
+
mouse: z.strictObject({ target: z.union([z.string(), XY]) }),
|
|
57
|
+
// Scrolling / zoom
|
|
58
|
+
scroll: z.strictObject({
|
|
59
|
+
target: z.union([z.string(), z.number()]),
|
|
60
|
+
duration: z.number().optional(),
|
|
61
|
+
}),
|
|
62
|
+
zoom: z.strictObject({
|
|
63
|
+
level: z.number(),
|
|
64
|
+
origin: z.string().optional(),
|
|
65
|
+
duration: z.number().optional(),
|
|
66
|
+
}),
|
|
67
|
+
resetZoom: z.strictObject({ duration: z.number().optional() }),
|
|
68
|
+
// Timing
|
|
69
|
+
wait: z.strictObject({ ms: z.number() }),
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Keys that are optional yet passed *positionally* in Markdown method calls
|
|
73
|
+
* (rather than gathered into the trailing options bag) — the only per-action
|
|
74
|
+
* fact not derivable from the schema.
|
|
75
|
+
*/
|
|
76
|
+
const POSITIONAL_OPTIONAL = {
|
|
77
|
+
resumeOnInput: ["message"],
|
|
78
|
+
};
|
|
79
|
+
// ─── Manifest derivation ─────────────────────────────────────────────────────
|
|
80
|
+
//
|
|
81
|
+
// Positional/bag layout is derived from each action's Zod `.shape`: keys in
|
|
82
|
+
// declaration order, with optionality read off `ZodOptional`.
|
|
83
|
+
const shapeOf = (name) => ACTIONS[name].shape;
|
|
84
|
+
/** All argument keys, in declaration order. */
|
|
85
|
+
const keysInOrder = (name) => Object.keys(shapeOf(name));
|
|
86
|
+
/** Whether `key` is optional for `name` (a `ZodOptional` in the shape). */
|
|
87
|
+
const isOptional = (name, key) => shapeOf(name)[key] instanceof z.ZodOptional;
|
|
88
|
+
/** Keys passed positionally: the required ones plus any flagged positional-optional. */
|
|
89
|
+
const positionalKeys = (name) => keysInOrder(name).filter((k) => !isOptional(name, k) || POSITIONAL_OPTIONAL[name]?.includes(k));
|
|
90
|
+
/** Keys gathered into the trailing options bag: optional and not positional. */
|
|
91
|
+
const bagKeys = (name) => keysInOrder(name).filter((k) => isOptional(name, k) && !POSITIONAL_OPTIONAL[name]?.includes(k));
|
|
92
|
+
/** One readable line per issue: `<path>: <message>`. */
|
|
93
|
+
function formatIssues(error) {
|
|
94
|
+
return error.issues
|
|
95
|
+
.map((issue) => {
|
|
96
|
+
const path = issue.path.join(".");
|
|
97
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
98
|
+
})
|
|
99
|
+
.join("; ");
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validate one keyed action against the manifest: the action must exist and its
|
|
103
|
+
* argument *values* (and key names) must match the action's schema — so a wrong
|
|
104
|
+
* type (`{ action: "zoom", level: "big" }`) or a typo'd key fails here. Shared by
|
|
105
|
+
* the JSON loader and the Markdown mapper.
|
|
106
|
+
*/
|
|
107
|
+
export function validateAction(step) {
|
|
108
|
+
const schema = ACTIONS[step.action];
|
|
109
|
+
if (!schema) {
|
|
110
|
+
throw new RecordableError("CONFIG_INVALID", `Unknown action "${step.action}" — valid actions: ${Object.keys(ACTIONS).join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
const { action: _action, ...rest } = step;
|
|
113
|
+
const result = schema.safeParse(rest);
|
|
114
|
+
if (!result.success) {
|
|
115
|
+
throw new RecordableError("CONFIG_INVALID", `Action "${step.action}": ${formatIssues(result.error)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Turn one flat action into the positional argument list for its method.
|
|
120
|
+
*
|
|
121
|
+
* Optional positionals that are absent become `undefined` — JavaScript default
|
|
122
|
+
* parameters then apply, so a present later arg never lands in the wrong slot.
|
|
123
|
+
* Bag keys collapse into a single trailing options object.
|
|
124
|
+
*/
|
|
125
|
+
function buildArgs(step, name) {
|
|
126
|
+
const args = [];
|
|
127
|
+
for (const key of positionalKeys(name))
|
|
128
|
+
args.push(step[key]);
|
|
129
|
+
const bag = bagKeys(name);
|
|
130
|
+
if (bag.length) {
|
|
131
|
+
const opts = {};
|
|
132
|
+
for (const key of bag)
|
|
133
|
+
if (key in step)
|
|
134
|
+
opts[key] = step[key];
|
|
135
|
+
args.push(Object.keys(opts).length ? opts : undefined);
|
|
136
|
+
}
|
|
137
|
+
// Trim trailing undefineds so the method's own defaults apply cleanly.
|
|
138
|
+
while (args.length && args[args.length - 1] === undefined)
|
|
139
|
+
args.pop();
|
|
140
|
+
return args;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Map a positional method call — `{ name, args }` as produced by the Markdown
|
|
144
|
+
* parser — onto a flat keyed {@link Action}, the same IR the JSON layer uses.
|
|
145
|
+
* Positional args are named by manifest order; a trailing options object is
|
|
146
|
+
* flattened to top-level keys. The result is validated, so value/key typos throw.
|
|
147
|
+
*/
|
|
148
|
+
export function callToAction(name, args) {
|
|
149
|
+
const schema = ACTIONS[name];
|
|
150
|
+
if (!schema) {
|
|
151
|
+
throw new Error(`Unknown action "${name}" — valid actions: ${Object.keys(ACTIONS).join(", ")}`);
|
|
152
|
+
}
|
|
153
|
+
const step = { action: name };
|
|
154
|
+
let i = 0;
|
|
155
|
+
for (const key of positionalKeys(name)) {
|
|
156
|
+
if (i < args.length)
|
|
157
|
+
step[key] = args[i++];
|
|
158
|
+
else if (!isOptional(name, key))
|
|
159
|
+
throw new Error(`Action "${name}" is missing required "${key}"`);
|
|
160
|
+
}
|
|
161
|
+
const bag = bagKeys(name);
|
|
162
|
+
if (i < args.length && bag.length) {
|
|
163
|
+
const obj = args[i++];
|
|
164
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
165
|
+
throw new Error(`Action "${name}": expected a trailing options object, got ${JSON.stringify(obj)}`);
|
|
166
|
+
}
|
|
167
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
168
|
+
if (!bag.includes(k)) {
|
|
169
|
+
throw new Error(`Action "${name}": unknown key "${k}" — valid keys: ${bag.join(", ")}`);
|
|
170
|
+
}
|
|
171
|
+
step[k] = v;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (i < args.length) {
|
|
175
|
+
throw new Error(`Action "${name}": too many arguments (expected at most ${i}, got ${args.length})`);
|
|
176
|
+
}
|
|
177
|
+
validateAction(step);
|
|
178
|
+
return step;
|
|
179
|
+
}
|
|
180
|
+
/** The action manifest, exported so schema/docs tooling can read it. */
|
|
181
|
+
export { ACTIONS };
|
|
182
|
+
/** Exported for unit tests: map a keyed action to its positional method args. */
|
|
183
|
+
export { buildArgs };
|
|
184
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
|