versioncam 0.2.0 → 0.4.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +64 -6
  3. package/dist/cli/commands/check.js +45 -4
  4. package/dist/cli/commands/check.js.map +1 -1
  5. package/dist/cli/commands/publish.d.ts +17 -0
  6. package/dist/cli/commands/publish.js +203 -0
  7. package/dist/cli/commands/publish.js.map +1 -0
  8. package/dist/cli/commands/record.js +2 -0
  9. package/dist/cli/commands/record.js.map +1 -1
  10. package/dist/cli/commands/render.js +22 -2
  11. package/dist/cli/commands/render.js.map +1 -1
  12. package/dist/cli/main.js +8 -0
  13. package/dist/cli/main.js.map +1 -1
  14. package/dist/cli/usage.d.ts +1 -1
  15. package/dist/cli/usage.js +8 -0
  16. package/dist/cli/usage.js.map +1 -1
  17. package/dist/config.d.ts +32 -1
  18. package/dist/config.js +34 -0
  19. package/dist/config.js.map +1 -1
  20. package/dist/driver/clip.d.ts +9 -1
  21. package/dist/driver/clip.js +17 -5
  22. package/dist/driver/clip.js.map +1 -1
  23. package/dist/driver/page-hooks.d.ts +65 -12
  24. package/dist/driver/page-hooks.js +101 -15
  25. package/dist/driver/page-hooks.js.map +1 -1
  26. package/dist/driver/reports.d.ts +34 -2
  27. package/dist/driver/reports.js +31 -3
  28. package/dist/driver/reports.js.map +1 -1
  29. package/dist/driver/session.d.ts +23 -0
  30. package/dist/driver/session.js +45 -7
  31. package/dist/driver/session.js.map +1 -1
  32. package/dist/driver/settle.d.ts +2 -1
  33. package/dist/driver/settle.js +13 -1
  34. package/dist/driver/settle.js.map +1 -1
  35. package/dist/index.d.ts +10 -2
  36. package/dist/index.js +5 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/publish/client.d.ts +38 -0
  39. package/dist/publish/client.js +118 -0
  40. package/dist/publish/client.js.map +1 -0
  41. package/dist/publish/index.d.ts +12 -0
  42. package/dist/publish/index.js +11 -0
  43. package/dist/publish/index.js.map +1 -0
  44. package/dist/publish/manifest.d.ts +46 -0
  45. package/dist/publish/manifest.js +133 -0
  46. package/dist/publish/manifest.js.map +1 -0
  47. package/dist/publish/protocol.d.ts +203 -0
  48. package/dist/publish/protocol.js +56 -0
  49. package/dist/publish/protocol.js.map +1 -0
  50. package/dist/render/encode.d.ts +15 -0
  51. package/dist/render/encode.js +23 -4
  52. package/dist/render/encode.js.map +1 -1
  53. package/dist/render/render.js +1 -0
  54. package/dist/render/render.js.map +1 -1
  55. package/dist/render/sequence.d.ts +3 -1
  56. package/dist/render/sequence.js +4 -1
  57. package/dist/render/sequence.js.map +1 -1
  58. package/dist/render/serve.d.ts +15 -1
  59. package/dist/render/serve.js +21 -2
  60. package/dist/render/serve.js.map +1 -1
  61. package/dsl.md +3 -2
  62. package/package.json +6 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,87 @@ and versions follow [semantic versioning](https://semver.org/). While the
6
6
  major version is 0, a minor version may change the config or the clip DSL,
7
7
  and its entry here says how.
8
8
 
9
+ ## 0.4.0 (2026-10-05)
10
+
11
+ A minor version: clips whose app uses CSS transitions record differently.
12
+
13
+ ### Changed
14
+
15
+ - **CSS transitions play.** Until now the recorder set every transition to
16
+ zero, so a change landed in the frame after it, and the only motion was
17
+ the renderer's crossfade at a settle. A change made inside a hold, such as
18
+ a map zooming on a wheel notch, jumped. Now each transition runs at its
19
+ authored duration and easing, on the page's own clock, from the frame the
20
+ recorder first sees it, the same on every run: menus, dialogs and
21
+ popovers open the way the app wrote them, and a map zooms over its
22
+ frames. `transitionend` fires on the frame the transition's time runs out,
23
+ so code that waits for it runs there.
24
+ - **A settle waits for running transitions** to finish, as it waits for
25
+ requests, so the frame after a settle shows them finished. A settle held up
26
+ by them names them in `settles.json`: `transitions(1)`.
27
+ - To record as before, set `capture: { transitions: "zero" }` in the config,
28
+ or in one clip's options. Transitions in an iframe stay zeroed either way,
29
+ since nothing drives an iframe's clock.
30
+
31
+ ### Security
32
+
33
+ - The page that renders a recording or a scene now reaches nothing but its
34
+ own routes. Every request it makes for another origin is refused before
35
+ it leaves the browser, and every WebSocket is closed before it connects;
36
+ until now, a WebSocket from the page could reach a port on the machine
37
+ doing the rendering. A scene that loaded something from elsewhere, such
38
+ as a web font, renders without it: put what a scene draws in its module.
39
+
40
+ ## 0.3.0 (2026-09-29)
41
+
42
+ A minor version: a new command, a new config field, and new files beside a
43
+ recording. Clips record exactly what they did before. `versioncam publish`
44
+ is for version.cam's hosted service, which opens with its beta; everything
45
+ else in this version works without it.
46
+
47
+ ### Added
48
+
49
+ - `versioncam publish` sends the recordings and what their last run said to
50
+ version.cam, which renders them and keeps them at a living address. It
51
+ records and renders nothing itself: for each clip the config defines, it
52
+ sends the recording if the clip's last run passed, or the failure if it
53
+ did not. Only the screens the service has not seen are uploaded. The
54
+ credential comes from the environment (`VERSIONCAM_OIDC_TOKEN`, which the
55
+ versioncam GitHub Action requests, or a project token in
56
+ `VERSIONCAM_TOKEN`), never from a flag. `--dry-run` prints what would be
57
+ sent.
58
+ - `--report <file>` on `check`, `render` and `publish` writes what happened
59
+ as JSON, for a CI step to read instead of parsing what the command
60
+ printed: each clip's result and the step a failure stopped at, the files a
61
+ render made, the living URLs a publish made.
62
+ - A recording keeps `targets.json` beside its timeline: where each step's
63
+ target was when the step began, by the step's index in `steps`. It is
64
+ what lets a screenshot be cropped to the control a step used.
65
+ - `failure.json` names the step that was running: its index in `steps` and
66
+ its call, such as `click`.
67
+ - `output.threads` in the config, and `VERSIONCAM_FFMPEG_THREADS` for one
68
+ run, fix how many threads each encoder uses, so a render made on another
69
+ machine is the same file.
70
+ - `versioncam/publish`, an entry point with the publish protocol's types and
71
+ `validateTimeline`, which pulls in nothing that needs Node.
72
+ - `versioncam --version` prints which versioncam this is, for a script that
73
+ must know before it drives it. The reports of `render` and `publish` carry
74
+ the version too, and `publish --report` is written when a publish fails,
75
+ with the error, so a CI step can say why without the log.
76
+
77
+ ### Changed
78
+
79
+ - A clip that fails before its first step (a saved session the browser
80
+ refuses, say) now leaves `failure.json` too, with its message and no step,
81
+ frame or picture, so `check --report` and `publish` say why it failed.
82
+
83
+ ### Fixed
84
+
85
+ - A render is now the same bytes every time it is made from one recording.
86
+ The WebM muxer wrote a random id into every file, so two renders of the
87
+ same recording always differed, and both muxers stamped each file with
88
+ the ffmpeg build that wrote it.
89
+
9
90
  ## 0.2.0 (2026-09-29)
10
91
 
11
92
  A minor version: new config fields and new camera methods. A clip whose
package/README.md CHANGED
@@ -105,10 +105,10 @@ cannot say, that is a missing field. It is not a reason to fork.
105
105
  | `viewport` | `{ width: 1440, height: 900 }` | The page's size. The video has the same shape. |
106
106
  | `dpr` | `2` | Device pixel ratio, from 1 to 3. At 2, a zoom shows real pixels. |
107
107
  | `fps` | `60` | Frames per second, from 10 to 120. |
108
- | `output` | `{ width: 1920 }` | The video's width. Its height follows the viewport. |
108
+ | `output` | `{ width: 1920 }` | The video's width. Its height follows the viewport. `threads` fixes how many threads each encoder uses: an encoder's bytes depend on it, so a fixed number renders the same recording to the same file on any machine. `VERSIONCAM_FFMPEG_THREADS` sets it for one run. |
109
109
  | `auth` | none | How the app signs in: a saved session, `{ storageState }`, or a hook, `{ login }`. |
110
110
  | `settle` | `quietMs: 150`, `timeoutMs: 15000`, `yieldMs: 20` | When a page counts as done. `ready` adds a condition of your own. |
111
- | `capture` | `gate: true`, `forceCaptureEvery: 30` | Which frames get a screenshot. See *Speed*. |
111
+ | `capture` | `gate: true`, `forceCaptureEvery: 30`, `transitions: "play"` | Which frames get a screenshot (see *Speed*), and whether the app's CSS transitions play on its clock or take no time, `"zero"` (see *Determinism*). |
112
112
  | `review` | `minSeconds: 9`, `maxSeconds: 11` | How long a clip should run. `versioncam review` holds clips to it. |
113
113
  | `cursorStart` | `{ x: 0.72, y: 0.28 }` | Where the cursor rests in the first frame, as fractions of the viewport. |
114
114
  | `clock` | `time: "2026-09-01T09:00:00Z"`, `timezoneId: "UTC"`, `locale: "en-GB"` | The instant, time zone and locale the app sees. |
@@ -224,7 +224,7 @@ test holds that list to the session's code. The same reference is
224
224
  | `title` | required | The clip's name, kept in its timeline. It is not drawn: write a caption with `s.caption`. |
225
225
  | `seed` | `1` | Seeds the jitter in cursor paths, dwells and typing. A new seed is a new take of the same script. |
226
226
  | `viewport`, `dpr`, `fps` | the config's | For a clip that needs a different shape. |
227
- | `capture` | the config's | A clip that films a canvas sets `{ forceCaptureEvery: 1 }` here. |
227
+ | `capture` | the config's | A clip that films a canvas sets `{ forceCaptureEvery: 1 }` here, and one whose app animates in ways better left out sets `{ transitions: "zero" }`. |
228
228
 
229
229
  ### The camera
230
230
 
@@ -320,6 +320,7 @@ the wreckage would help nobody.
320
320
  versioncam record [id…] drive the app and capture frames
321
321
  --draft 15fps at CSS scale: cheap, for writing clips
322
322
  versioncam check [id…] record every clip; exit 1 if any breaks
323
+ --report <file> and write each clip's result there, as JSON
323
324
  versioncam stability [id…] record each clip twice the same way; stable or not
324
325
  --runs <n> how many times (default 2)
325
326
  --full at full quality rather than draft
@@ -362,6 +363,31 @@ versioncam render [id…] composite and encode recordings and scenes
362
363
  no ids: every one, stale recordings included
363
364
  --scene <id> and this scene, whatever the ids say (repeatable)
364
365
  --sequence stitch the results into one piece
366
+ --report <file> and write what it made there, as JSON
367
+ ```
368
+
369
+ ### Publish
370
+
371
+ Send what the last run recorded to [version.cam](https://version.cam), which
372
+ renders it and keeps it at a living address. `publish` records nothing and
373
+ renders nothing: for each clip the config defines, it sends the recording if
374
+ the clip's last run passed, or the failure if it did not, so a clip that
375
+ broke says so on every page that embeds it. Only the states the service has
376
+ not seen are uploaded, so a version that changed one screen sends one
377
+ screen.
378
+
379
+ It needs a credential in the environment, never on the command line:
380
+ `VERSIONCAM_OIDC_TOKEN`, which the versioncam GitHub Action requests from
381
+ GitHub for the run (no secret to store), or `VERSIONCAM_TOKEN`, a project
382
+ token from version.cam for any other CI. `VERSIONCAM_API` points it
383
+ somewhere other than `https://api.version.cam`. Outside GitHub Actions it
384
+ reads the commit and branch from git; `VERSIONCAM_COMMIT` and
385
+ `VERSIONCAM_BRANCH` set them outright.
386
+
387
+ ```
388
+ versioncam publish [id…] send the recordings and their checks to version.cam
389
+ --dry-run print what would be sent, and send nothing
390
+ --report <file> and write the living URLs there, as JSON
365
391
  ```
366
392
 
367
393
  ### From another directory
@@ -380,6 +406,7 @@ running, and says to `cd` into it instead.
380
406
 
381
407
  ```
382
408
  versioncam <command> --cwd <dir> as if run in <dir>, which holds versioncam.config.ts
409
+ versioncam --version which versioncam this is
383
410
  ```
384
411
 
385
412
  ## What a recording leaves behind
@@ -391,8 +418,9 @@ versioncam <command> --cwd <dir> as if run in <dir>, which holds versioncam.co
391
418
  | `timeline.json` | Everything the renderer needs, and everything a reviewer can check. |
392
419
  | `states/` | The page states the timeline indexes, one image each. |
393
420
  | `settles.json` | Every wait: what it was still blocked on, and how long it really took. |
421
+ | `targets.json` | Where each step's target was when the step began, by the step's index in `steps`: the box of the control it used. New in 0.3.0. |
394
422
  | `failure.png` | Only after a failure: the page at the moment the clip gave up. |
395
- | `failure.json` | Only after a failure: the message, the frame and the authored second. |
423
+ | `failure.json` | Only after a failure: the message, the frame, the authored second, and the step that was running (its index in `steps` and its call; new in 0.3.0). |
396
424
 
397
425
  The timeline's `frames` say which state each frame shows. Beside them are the
398
426
  tracks the renderer draws: `cursor`, `pointer`, `camera`, `highlights` and
@@ -470,8 +498,8 @@ how long that loop takes.
470
498
  moved. The screenshot is by far the most expensive thing a frame does: 25 ms,
471
499
  against half a millisecond for everything else. So the driver takes one only
472
500
  when the page can have changed. That is when the DOM mutated, the element
473
- under the cursor changed, an animation is running, input was just sent, or
474
- `forceCaptureEvery` frames have gone by. On the example app that skips 81% to
501
+ under the cursor changed, an animation or a transition is running, input was
502
+ just sent, or `forceCaptureEvery` frames have gone by. On the example app that skips 81% to
475
503
  95% of frames, and the recording is identical either way. A test records every
476
504
  clip both ways on every push and compares the bytes. The failure it guards
477
505
  against is not a crash but a video that is quietly wrong.
@@ -586,6 +614,12 @@ package's own tests assert it for capture and for rendering on every change,
586
614
  on macOS and on Linux. A golden recording pins the capture path against
587
615
  changes nobody meant to make.
588
616
 
617
+ A render is the same bytes every time it is made from one recording, on one
618
+ ffmpeg build, with one encoder thread count. The thread count otherwise
619
+ follows the machine's cores, so a render that must match another machine's
620
+ fixes it with `output.threads` or `VERSIONCAM_FFMPEG_THREADS`. Before 0.3.0
621
+ every WebM carried a random id and no two were the same file.
622
+
589
623
  Across machines the claim is narrower. The **timeline** is identical: it is
590
624
  what the driver decided, and nothing in it depends on the host. The captured
591
625
  **pixels** are identical only where the rendering environment is. macOS and
@@ -598,6 +632,30 @@ If two recordings of your clip on one machine differ, something outside the
598
632
  authored clock reached a frame: an unwarmed resource, a real timer, a font
599
633
  that had not finished loading. Find the cause. The next section is how.
600
634
 
635
+ ### The app's own motion
636
+
637
+ The page runs on a fake clock that moves only when the recorder moves it: a
638
+ frame's worth at a time while frames are made, and in small ticks while a
639
+ settle waits. The app's timers and animation frames run on it, and so does
640
+ its CSS motion:
641
+
642
+ - **Transitions play** at their authored duration and easing, on the page's
643
+ clock, from the frame the recorder first sees each one. A menu fades in, a
644
+ drawer slides, a map zooms over its frames, and `transitionend` fires on
645
+ the frame its time runs out. A settle waits until none is running, so the
646
+ frame after it shows them finished. `capture: { transitions: "zero" }`
647
+ makes them take no time instead, as before 0.4.0, in the config or in one
648
+ clip.
649
+ - **Animations are scrubbed** by the clip's time: a spinner turns at the rate
650
+ the video plays, whatever the machine is doing. The scrub counts from the
651
+ clip's start, not the animation's, so a one-off animation that starts
652
+ partway through a clip shows its end.
653
+ - **Not on the clock:** animations a script makes with `element.animate()`,
654
+ Chromium's smooth scrolling of a mouse wheel, and transitions inside an
655
+ iframe, which stay zeroed since nothing drives an iframe's clock. A clip
656
+ that films one of the first two may differ between runs: `stability` says
657
+ so.
658
+
601
659
  ### When a clip will not reproduce
602
660
 
603
661
  The recorder can only be as deterministic as the app it films. An app that is
@@ -1,8 +1,13 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
1
3
  import { parseArgs } from "node:util";
2
4
  import { launchBrowser } from "../../driver/launch.js";
3
- import { runClip } from "../../driver/clip.js";
5
+ import { RECORDER_VERSION, runClip } from "../../driver/clip.js";
4
6
  import { loadClips, loadConfig } from "../../loader.js";
5
7
  import { drivingApp } from "../app.js";
8
+ import { ensureFailure } from "../../driver/reports.js";
9
+ import { FAILURE_JSON, FAILURE_PNG, } from "../../driver/reports.js";
10
+ import { writeReport } from "./publish.js";
6
11
  /**
7
12
  * Record every clip and report which still work.
8
13
  *
@@ -11,20 +16,22 @@ import { drivingApp } from "../app.js";
11
16
  * longer exists, and encoding a video of the wreckage helps nobody.
12
17
  */
13
18
  export async function check(argv) {
14
- const { positionals } = parseArgs({
19
+ const { positionals, values } = parseArgs({
15
20
  args: argv,
21
+ options: { report: { type: "string" } },
16
22
  strict: false,
17
23
  allowPositionals: true,
18
24
  });
25
+ const reportPath = typeof values.report === "string" ? resolve(values.report) : null;
19
26
  const config = await loadConfig(process.cwd());
20
27
  const clips = await loadClips(config, positionals);
21
28
  if (clips.length === 0) {
22
29
  process.stderr.write("No clips to check.\n");
23
30
  return 1;
24
31
  }
25
- return drivingApp(config, () => checkAll(config, clips));
32
+ return drivingApp(config, () => checkAll(config, clips, reportPath));
26
33
  }
27
- async function checkAll(config, clips) {
34
+ async function checkAll(config, clips, reportPath) {
28
35
  const browser = await launchBrowser();
29
36
  const failures = [];
30
37
  try {
@@ -35,6 +42,7 @@ async function checkAll(config, clips) {
35
42
  }
36
43
  catch (error) {
37
44
  failures.push({ id: clip.id, error: error });
45
+ ensureFailure(join(config.recordingsDir, clip.id), error);
38
46
  process.stdout.write(`FAIL ${clip.id}\n`);
39
47
  }
40
48
  }
@@ -46,6 +54,39 @@ async function checkAll(config, clips) {
46
54
  for (const { id, error } of failures) {
47
55
  process.stderr.write(`\n${id}\n ${error.message}\n`);
48
56
  }
57
+ if (reportPath) {
58
+ writeReport(reportPath, checkReport(config, clips, failures));
59
+ }
49
60
  return failures.length > 0 ? 1 : 0;
50
61
  }
62
+ /**
63
+ * What `--report` writes: each clip's result, the step a failure stopped at,
64
+ * and where its picture is, for a machine to read. The Action builds its pull
65
+ * request comment from this, so it never has to parse what `check` prints or
66
+ * guess where the recordings directory is.
67
+ */
68
+ function checkReport(config, clips, failures) {
69
+ return {
70
+ recorder: RECORDER_VERSION,
71
+ clips: clips.map((clip) => {
72
+ const failed = failures.find((f) => f.id === clip.id);
73
+ if (!failed)
74
+ return { id: clip.id, status: "pass" };
75
+ const dir = join(config.recordingsDir, clip.id);
76
+ const written = join(dir, FAILURE_JSON);
77
+ const failure = existsSync(written)
78
+ ? JSON.parse(readFileSync(written, "utf8"))
79
+ : null;
80
+ const picture = join(dir, FAILURE_PNG);
81
+ return {
82
+ id: clip.id,
83
+ status: "fail",
84
+ message: failed.error.message,
85
+ step: failure?.step ?? null,
86
+ t: failure?.t ?? null,
87
+ picture: existsSync(picture) ? picture : null,
88
+ };
89
+ }),
90
+ };
91
+ }
51
92
  //# sourceMappingURL=check.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAc;IACxC,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QAChC,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,KAAK;QACb,gBAAgB,EAAE,IAAI;KACvB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,WAAuB,CAAC,CAAC;IAE/D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC7C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,MAAsB,EACtB,KAAuB;IAEvB,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAmC,EAAE,CAAC;IAEpD,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;gBACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC;gBACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,KAAK,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,+BAA+B,CACnF,CAAC;IACF,KAAK,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;QACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Send the recordings, and what their last run said, to version.cam.
3
+ *
4
+ * It records nothing and renders nothing. It reads what `check`, `record` or
5
+ * `stability` left in the recordings directory, for the clips the config
6
+ * defines today (a recording of a clip since renamed or deleted is left
7
+ * behind), and publishes each one's latest word: a complete recording that
8
+ * passed, or the failure that stopped it. The service renders the rest.
9
+ *
10
+ * The credential comes from the environment, never from a flag, so it
11
+ * cannot end up in a shell history or a CI log line:
12
+ * `VERSIONCAM_OIDC_TOKEN` is the GitHub token the Action requests, and
13
+ * `VERSIONCAM_TOKEN` is a project token for any other CI.
14
+ */
15
+ export declare function publish(argv: string[]): Promise<number>;
16
+ /** Written for a machine: the Action reads it for the job summary. */
17
+ export declare function writeReport(path: string, report: unknown): void;
@@ -0,0 +1,203 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { parseArgs } from "node:util";
6
+ import { RECORDER_VERSION } from "../../driver/clip.js";
7
+ import { loadClips, loadConfig } from "../../loader.js";
8
+ import { publishRecordings, PublishRefused, } from "../../publish/client.js";
9
+ import { publishedRenderConfig, readClip, } from "../../publish/manifest.js";
10
+ import { DEFAULT_API, PUBLISH_PROTOCOL, } from "../../publish/protocol.js";
11
+ /**
12
+ * Send the recordings, and what their last run said, to version.cam.
13
+ *
14
+ * It records nothing and renders nothing. It reads what `check`, `record` or
15
+ * `stability` left in the recordings directory, for the clips the config
16
+ * defines today (a recording of a clip since renamed or deleted is left
17
+ * behind), and publishes each one's latest word: a complete recording that
18
+ * passed, or the failure that stopped it. The service renders the rest.
19
+ *
20
+ * The credential comes from the environment, never from a flag, so it
21
+ * cannot end up in a shell history or a CI log line:
22
+ * `VERSIONCAM_OIDC_TOKEN` is the GitHub token the Action requests, and
23
+ * `VERSIONCAM_TOKEN` is a project token for any other CI.
24
+ */
25
+ export async function publish(argv) {
26
+ const { values, positionals } = parseArgs({
27
+ args: argv,
28
+ options: {
29
+ "dry-run": { type: "boolean", default: false },
30
+ report: { type: "string" },
31
+ },
32
+ strict: false,
33
+ allowPositionals: true,
34
+ });
35
+ const ids = positionals;
36
+ const dryRun = values["dry-run"] === true;
37
+ const reportPath = typeof values.report === "string" ? resolve(values.report) : null;
38
+ const config = await loadConfig(process.cwd());
39
+ const clips = await loadClips(config);
40
+ const unknown = ids.filter((id) => !clips.some((clip) => clip.id === id));
41
+ if (unknown.length > 0) {
42
+ process.stderr.write(`No clip is called ${unknown.join(", ")}. The clips are: ${clips.map((c) => c.id).join(", ") || "none"}.\n`);
43
+ return 1;
44
+ }
45
+ const renderConfig = publishedRenderConfig(config);
46
+ const manifests = [];
47
+ const files = new Map();
48
+ const unrecorded = [];
49
+ for (const clip of clips) {
50
+ if (ids.length > 0 && !ids.includes(clip.id))
51
+ continue;
52
+ const read = readClip(join(config.recordingsDir, clip.id), { id: clip.id, title: clip.options.title ?? "" }, renderConfig);
53
+ if (!read) {
54
+ unrecorded.push(clip.id);
55
+ continue;
56
+ }
57
+ manifests.push(read.manifest);
58
+ files.set(clip.id, read.files);
59
+ }
60
+ if (unrecorded.length > 0) {
61
+ process.stderr.write(`Not recorded here, so not published: ${unrecorded.join(", ")}. ` +
62
+ "Run `versioncam check` or `versioncam record` first.\n");
63
+ }
64
+ if (manifests.length === 0) {
65
+ process.stderr.write("Nothing to publish.\n");
66
+ return 1;
67
+ }
68
+ const where = provenance(config.root);
69
+ const begin = {
70
+ protocol: PUBLISH_PROTOCOL,
71
+ recorder: RECORDER_VERSION,
72
+ project: process.env.GITHUB_REPOSITORY ?? null,
73
+ ...where,
74
+ clips: manifests,
75
+ };
76
+ if (dryRun) {
77
+ process.stdout.write(`${JSON.stringify(begin, null, 2)}\n`);
78
+ return 0;
79
+ }
80
+ const credential = process.env.VERSIONCAM_OIDC_TOKEN || process.env.VERSIONCAM_TOKEN;
81
+ if (!credential) {
82
+ process.stderr.write("publish needs a credential in the environment: VERSIONCAM_OIDC_TOKEN, " +
83
+ "which the versioncam Action requests from GitHub, or VERSIONCAM_TOKEN, " +
84
+ "a project token from version.cam for any other CI.\n");
85
+ return 1;
86
+ }
87
+ const api = process.env.VERSIONCAM_API || DEFAULT_API;
88
+ try {
89
+ const { begun, committed } = await publishRecordings(begin, filesFor(files), {
90
+ api,
91
+ credential,
92
+ log: (line) => process.stdout.write(`${line}\n`),
93
+ });
94
+ process.stdout.write(`Published to ${api} as ${committed.project}:\n`);
95
+ for (const version of committed.versions) {
96
+ process.stdout.write(` ${describe(version)}\n`);
97
+ }
98
+ for (const refusal of begun.refused) {
99
+ process.stderr.write(` ${refusal.clip}: ${refusal.reason}\n`);
100
+ }
101
+ if (reportPath) {
102
+ writeReport(reportPath, {
103
+ recorder: RECORDER_VERSION,
104
+ api,
105
+ project: committed.project,
106
+ versions: committed.versions,
107
+ refused: begun.refused,
108
+ error: null,
109
+ });
110
+ }
111
+ return 0;
112
+ }
113
+ catch (error) {
114
+ const message = error instanceof PublishRefused
115
+ ? `version.cam refused the publish: ${error.message}`
116
+ : `publish failed: ${error.message}`;
117
+ process.stderr.write(`${message}\n`);
118
+ // A CI step reading the report learns what went wrong without the log.
119
+ if (reportPath) {
120
+ writeReport(reportPath, {
121
+ recorder: RECORDER_VERSION,
122
+ api,
123
+ project: null,
124
+ versions: [],
125
+ refused: [],
126
+ error: message,
127
+ });
128
+ }
129
+ return 1;
130
+ }
131
+ }
132
+ function describe(version) {
133
+ if (version.check === "fail") {
134
+ return `${version.clip}: failed its check, ${version.stale ? "stale" : "recorded"} at ${version.versionUrl}`;
135
+ }
136
+ const unchanged = version.unchanged ? " (unchanged)" : "";
137
+ const refused = version.refused ? ` (not stored: ${version.refused})` : "";
138
+ return `${version.clip}: ${version.url}${unchanged}${refused}`;
139
+ }
140
+ /**
141
+ * Which commit and branch these recordings are of. In GitHub Actions the
142
+ * service reads all of this from its own token and only checks these; for
143
+ * any other CI they are the whole story, so they can be set outright.
144
+ */
145
+ function provenance(root) {
146
+ const env = process.env;
147
+ const git = (...args) => {
148
+ try {
149
+ return execFileSync("git", args, {
150
+ cwd: root,
151
+ encoding: "utf8",
152
+ stdio: ["ignore", "pipe", "ignore"],
153
+ }).trim();
154
+ }
155
+ catch {
156
+ return "";
157
+ }
158
+ };
159
+ const commit = env.VERSIONCAM_COMMIT || env.GITHUB_SHA || git("rev-parse", "HEAD");
160
+ const branch = env.VERSIONCAM_BRANCH ||
161
+ env.GITHUB_HEAD_REF ||
162
+ env.GITHUB_REF_NAME ||
163
+ git("rev-parse", "--abbrev-ref", "HEAD");
164
+ const ref = env.GITHUB_REF || (branch ? `refs/heads/${branch}` : "");
165
+ if (!commit || !branch) {
166
+ throw new Error("publish could not tell which commit and branch these recordings are of. " +
167
+ "Run it in a git checkout, or set VERSIONCAM_COMMIT and VERSIONCAM_BRANCH.");
168
+ }
169
+ return { commit, branch, ref, runId: env.GITHUB_RUN_ID || null };
170
+ }
171
+ function filesFor(files) {
172
+ const statePath = (hash) => {
173
+ for (const recording of files.values()) {
174
+ const path = recording.states.get(hash);
175
+ if (path)
176
+ return path;
177
+ }
178
+ throw new Error(`the service asked for a state no recording has: ${hash}`);
179
+ };
180
+ const of = (clip) => {
181
+ const recording = files.get(clip);
182
+ if (!recording)
183
+ throw new Error(`the service asked about an unknown clip: ${clip}`);
184
+ return recording;
185
+ };
186
+ const required = (bytes, what) => {
187
+ if (!bytes)
188
+ throw new Error(`the service asked for ${what}, which is not there`);
189
+ return bytes;
190
+ };
191
+ return {
192
+ state: async (hash) => readFile(statePath(hash)),
193
+ timeline: async (clip) => of(clip).timeline,
194
+ targets: async (clip) => required(of(clip).targets, `${clip}'s targets`),
195
+ failure: async (clip) => required(of(clip).failure, `${clip}'s failure picture`),
196
+ };
197
+ }
198
+ /** Written for a machine: the Action reads it for the job summary. */
199
+ export function writeReport(path, report) {
200
+ mkdirSync(dirname(path), { recursive: true });
201
+ writeFileSync(path, `${JSON.stringify(report, null, 2)}\n`);
202
+ }
203
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../../../src/cli/commands/publish.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ import { runClip } from "../../driver/clip.js";
6
6
  import { formatTiming } from "../../core/timing.js";
7
7
  import { loadClips, loadConfig } from "../../loader.js";
8
8
  import { drivingApp } from "../app.js";
9
+ import { ensureFailure } from "../../driver/reports.js";
9
10
  /**
10
11
  * Record every clip the config points at, serially.
11
12
  *
@@ -57,6 +58,7 @@ async function recordAll(config, clips, draft) {
57
58
  }
58
59
  catch (error) {
59
60
  failures.push({ id: clip.id, error: error });
61
+ ensureFailure(join(config.recordingsDir, clip.id), error);
60
62
  process.stdout.write(` ${clip.id}: FAILED\n`);
61
63
  }
62
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"record.js","sourceRoot":"","sources":["../../../src/cli/commands/record.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../../src/cli/commands/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAc;IACzC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QACxC,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvD,MAAM,EAAE,KAAK;QACb,gBAAgB,EAAE,IAAI;KACvB,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,WAAuB,CAAC,CAAC;IAE/D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,WAAW,CAAC,MAAM,GAAG,CAAC;YACpB,CAAC,CAAC,mBAAmB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YAChD,CAAC,CAAC,wCAAwC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,IAAI,KAAK,CAC9F,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,MAAsB,EACtB,KAAuB,EACvB,KAAc;IAEd,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAmC,EAAE,CAAC;IAEpD,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;oBAC9D,KAAK;iBACN,CAAC,CAAC;gBACH,uEAAuE;gBACvE,kEAAkE;gBAClE,sEAAsE;gBACtE,kEAAkE;gBAClE,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,cAAc,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAClE,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACxD,MAAM,IAAI,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;gBACnF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,KAAK,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,cAAc,WAAW;oBAC3E,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,YAAY,IAAI,GAAG;oBAC5C,WAAW,KAAK,YAAY,IAAI,KAAK;oBACrC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAChC,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC;gBACtD,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,KAAc,CAAC,CAAC;gBACnE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,KAAK,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;YACrC,wEAAwE;YACxE,qEAAqE;YACrE,uEAAuE;YACvE,oEAAoE;YACpE,yEAAyE;YACzE,yEAAyE;YACzE,0DAA0D;YAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,EAAE,aAAa,CAAC,CAAC;YAC3D,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC;gBAC9B,CAAC,CAAC,iCAAiC,IAAI,EAAE;gBACzC,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,OAAO,GAAG,OAAO,MAAM,CAAC,CAAC;QAClE,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}