tuffgal 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (188) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/README.md +23 -14
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +173 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/init.d.ts +17 -0
  8. package/dist/commands/init.d.ts.map +1 -0
  9. package/{src/commands/init.ts → dist/commands/init.js} +46 -64
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/supervise.d.ts +40 -0
  12. package/dist/commands/supervise.d.ts.map +1 -0
  13. package/dist/commands/supervise.js +183 -0
  14. package/dist/commands/supervise.js.map +1 -0
  15. package/dist/config.d.ts +227 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +220 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/coverage/flows.d.ts +14 -0
  20. package/dist/coverage/flows.d.ts.map +1 -0
  21. package/dist/coverage/flows.js +77 -0
  22. package/dist/coverage/flows.js.map +1 -0
  23. package/dist/coverage/screens.d.ts +10 -0
  24. package/dist/coverage/screens.d.ts.map +1 -0
  25. package/dist/coverage/screens.js +43 -0
  26. package/dist/coverage/screens.js.map +1 -0
  27. package/dist/index.d.ts +9 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +9 -0
  30. package/dist/index.js.map +1 -0
  31. package/{src → dist}/reporter/assets/report.css +257 -126
  32. package/dist/reporter/assets/report.js +248 -0
  33. package/dist/reporter/template.d.ts +8 -0
  34. package/dist/reporter/template.d.ts.map +1 -0
  35. package/dist/reporter/template.js +398 -0
  36. package/dist/reporter/template.js.map +1 -0
  37. package/dist/reporter/writeReport.d.ts +8 -0
  38. package/dist/reporter/writeReport.d.ts.map +1 -0
  39. package/dist/reporter/writeReport.js +22 -0
  40. package/dist/reporter/writeReport.js.map +1 -0
  41. package/dist/runner/approve.d.ts +25 -0
  42. package/dist/runner/approve.d.ts.map +1 -0
  43. package/dist/runner/approve.js +54 -0
  44. package/dist/runner/approve.js.map +1 -0
  45. package/dist/runner/bridges/database.d.ts +15 -0
  46. package/dist/runner/bridges/database.d.ts.map +1 -0
  47. package/dist/runner/bridges/database.js +27 -0
  48. package/dist/runner/bridges/database.js.map +1 -0
  49. package/dist/runner/bridges/devServers.d.ts +16 -0
  50. package/dist/runner/bridges/devServers.d.ts.map +1 -0
  51. package/dist/runner/bridges/devServers.js +117 -0
  52. package/dist/runner/bridges/devServers.js.map +1 -0
  53. package/dist/runner/coverage.d.ts +27 -0
  54. package/dist/runner/coverage.d.ts.map +1 -0
  55. package/dist/runner/coverage.js +73 -0
  56. package/dist/runner/coverage.js.map +1 -0
  57. package/dist/runner/interpolate.d.ts +13 -0
  58. package/dist/runner/interpolate.d.ts.map +1 -0
  59. package/dist/runner/interpolate.js +31 -0
  60. package/dist/runner/interpolate.js.map +1 -0
  61. package/dist/runner/resolveLocator.d.ts +22 -0
  62. package/dist/runner/resolveLocator.d.ts.map +1 -0
  63. package/dist/runner/resolveLocator.js +41 -0
  64. package/dist/runner/resolveLocator.js.map +1 -0
  65. package/dist/runner/run.d.ts +18 -0
  66. package/dist/runner/run.d.ts.map +1 -0
  67. package/dist/runner/run.js +154 -0
  68. package/dist/runner/run.js.map +1 -0
  69. package/dist/runner/runAction.d.ts +31 -0
  70. package/dist/runner/runAction.d.ts.map +1 -0
  71. package/dist/runner/runAction.js +335 -0
  72. package/dist/runner/runAction.js.map +1 -0
  73. package/dist/runner/runStory.d.ts +52 -0
  74. package/dist/runner/runStory.d.ts.map +1 -0
  75. package/dist/runner/runStory.js +275 -0
  76. package/dist/runner/runStory.js.map +1 -0
  77. package/dist/runner/scheduler.d.ts +33 -0
  78. package/dist/runner/scheduler.d.ts.map +1 -0
  79. package/dist/runner/scheduler.js +215 -0
  80. package/dist/runner/scheduler.js.map +1 -0
  81. package/dist/runner/steps/click.d.ts +4 -0
  82. package/dist/runner/steps/click.d.ts.map +1 -0
  83. package/dist/runner/steps/click.js +11 -0
  84. package/dist/runner/steps/click.js.map +1 -0
  85. package/dist/runner/steps/input.d.ts +4 -0
  86. package/dist/runner/steps/input.d.ts.map +1 -0
  87. package/dist/runner/steps/input.js +11 -0
  88. package/dist/runner/steps/input.js.map +1 -0
  89. package/dist/runner/steps/intercept.d.ts +13 -0
  90. package/dist/runner/steps/intercept.d.ts.map +1 -0
  91. package/dist/runner/steps/intercept.js +28 -0
  92. package/dist/runner/steps/intercept.js.map +1 -0
  93. package/dist/runner/steps/navigate.d.ts +6 -0
  94. package/dist/runner/steps/navigate.d.ts.map +1 -0
  95. package/dist/runner/steps/navigate.js +8 -0
  96. package/dist/runner/steps/navigate.js.map +1 -0
  97. package/dist/runner/steps/read.d.ts +11 -0
  98. package/dist/runner/steps/read.d.ts.map +1 -0
  99. package/dist/runner/steps/read.js +21 -0
  100. package/dist/runner/steps/read.js.map +1 -0
  101. package/dist/runner/steps/scroll.d.ts +3 -0
  102. package/dist/runner/steps/scroll.d.ts.map +1 -0
  103. package/dist/runner/steps/scroll.js +6 -0
  104. package/dist/runner/steps/scroll.js.map +1 -0
  105. package/{src/runner/steps/type.ts → dist/runner/steps/type.d.ts} +2 -4
  106. package/dist/runner/steps/type.d.ts.map +1 -0
  107. package/dist/runner/steps/type.js +10 -0
  108. package/dist/runner/steps/type.js.map +1 -0
  109. package/dist/runner/steps/wait.d.ts +3 -0
  110. package/dist/runner/steps/wait.d.ts.map +1 -0
  111. package/dist/runner/steps/wait.js +4 -0
  112. package/dist/runner/steps/wait.js.map +1 -0
  113. package/dist/runner/steps/waitFor.d.ts +4 -0
  114. package/dist/runner/steps/waitFor.d.ts.map +1 -0
  115. package/dist/runner/steps/waitFor.js +11 -0
  116. package/dist/runner/steps/waitFor.js.map +1 -0
  117. package/dist/runner/storyFilter.d.ts +11 -0
  118. package/dist/runner/storyFilter.d.ts.map +1 -0
  119. package/dist/runner/storyFilter.js +12 -0
  120. package/dist/runner/storyFilter.js.map +1 -0
  121. package/dist/schema/action.d.ts +537 -0
  122. package/dist/schema/action.d.ts.map +1 -0
  123. package/dist/schema/action.js +174 -0
  124. package/dist/schema/action.js.map +1 -0
  125. package/dist/schema/load.d.ts +20 -0
  126. package/dist/schema/load.d.ts.map +1 -0
  127. package/dist/schema/load.js +86 -0
  128. package/dist/schema/load.js.map +1 -0
  129. package/dist/schema/result.d.ts +114 -0
  130. package/dist/schema/result.d.ts.map +1 -0
  131. package/dist/schema/result.js +26 -0
  132. package/dist/schema/result.js.map +1 -0
  133. package/dist/schema/story.d.ts +54 -0
  134. package/dist/schema/story.d.ts.map +1 -0
  135. package/dist/schema/story.js +90 -0
  136. package/dist/schema/story.js.map +1 -0
  137. package/dist/screenshots/baselineStore.d.ts +58 -0
  138. package/dist/screenshots/baselineStore.d.ts.map +1 -0
  139. package/dist/screenshots/baselineStore.js +113 -0
  140. package/dist/screenshots/baselineStore.js.map +1 -0
  141. package/{src/screenshots/capture.ts → dist/screenshots/capture.d.ts} +2 -12
  142. package/dist/screenshots/capture.d.ts.map +1 -0
  143. package/dist/screenshots/capture.js +27 -0
  144. package/dist/screenshots/capture.js.map +1 -0
  145. package/dist/screenshots/diff.d.ts +41 -0
  146. package/dist/screenshots/diff.d.ts.map +1 -0
  147. package/dist/screenshots/diff.js +64 -0
  148. package/dist/screenshots/diff.js.map +1 -0
  149. package/dist/util.d.ts +16 -0
  150. package/dist/util.d.ts.map +1 -0
  151. package/dist/util.js +48 -0
  152. package/dist/util.js.map +1 -0
  153. package/package.json +22 -12
  154. package/bin/tuffgal.mjs +0 -2
  155. package/src/.gitkeep +0 -0
  156. package/src/cli.ts +0 -158
  157. package/src/commands/supervise.ts +0 -267
  158. package/src/config.ts +0 -222
  159. package/src/coverage/flows.ts +0 -90
  160. package/src/coverage/screens.ts +0 -52
  161. package/src/index.ts +0 -28
  162. package/src/reporter/assets/report.js +0 -45
  163. package/src/reporter/template.ts +0 -355
  164. package/src/reporter/writeReport.ts +0 -37
  165. package/src/runner/approve.ts +0 -65
  166. package/src/runner/bridges/database.ts +0 -34
  167. package/src/runner/bridges/devServers.ts +0 -174
  168. package/src/runner/coverage.ts +0 -76
  169. package/src/runner/interpolate.ts +0 -36
  170. package/src/runner/resolveLocator.ts +0 -47
  171. package/src/runner/run.ts +0 -177
  172. package/src/runner/runAction.ts +0 -422
  173. package/src/runner/runStory.ts +0 -195
  174. package/src/runner/scheduler.ts +0 -223
  175. package/src/runner/steps/click.ts +0 -16
  176. package/src/runner/steps/input.ts +0 -17
  177. package/src/runner/steps/intercept.ts +0 -28
  178. package/src/runner/steps/navigate.ts +0 -14
  179. package/src/runner/steps/read.ts +0 -20
  180. package/src/runner/steps/scroll.ts +0 -12
  181. package/src/runner/steps/wait.ts +0 -5
  182. package/src/runner/steps/waitFor.ts +0 -16
  183. package/src/schema/action.ts +0 -176
  184. package/src/schema/load.ts +0 -94
  185. package/src/schema/result.ts +0 -83
  186. package/src/schema/story.ts +0 -58
  187. package/src/screenshots/baselineStore.ts +0 -114
  188. package/src/screenshots/diff.ts +0 -101
package/CHANGELOG.md ADDED
@@ -0,0 +1,174 @@
1
+ # 🪵 Changelog
2
+
3
+ **All notable project changes will be documented in this file.** The format
4
+ is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
+ this project uses [Pride Versioning](https://pridever.org) → `PROUD.DEFAULT.SHAME`
6
+
7
+ ## [Unreleased]
8
+
9
+ _Nothing right now_
10
+
11
+ ## [0.1.0-alpha.10] — 2026-06-22
12
+
13
+ ### Fixed
14
+
15
+ - **Per-breakpoint database isolation.** alpha.9 ran each breakpoint in its own
16
+ browser context but applied a story's fixtures only once, before the
17
+ breakpoint loop — so a story that mutated server state passed at the first
18
+ mode and then ran the next mode against the mutated rows (a registered user
19
+ collided on the second mode, an already-read link no longer showed as unread,
20
+ an emptied list had nothing left to empty). Fixtures are now re-applied before
21
+ **every** breakpoint, resetting that state to a known baseline per mode.
22
+ Fixtures are idempotent, so non-mutating stories are unaffected.
23
+
24
+ ### Added
25
+
26
+ - **`${breakpoint}` interpolation token.** The current mode name is exposed to
27
+ action interpolation as `${breakpoint}`, so a story can key data it creates
28
+ through the app (with no backing fixture to reset) per mode — e.g.
29
+ `fresh+${breakpoint}@example.test` registers a distinct user at each viewport
30
+ instead of colliding on a shared value. A story parameter literally named
31
+ `breakpoint` overrides the injected value.
32
+
33
+ ## [0.1.0-alpha.9] — 2026-06-21
34
+
35
+ ### Added
36
+
37
+ - Breakpoint modes, replacing the single `viewport` config
38
+ - `mobile` 375×667 / `tablet` 768×1024 / `laptop` 1024×768 / `desktop` 1280×800
39
+ - Each mode runs in its own browser context + produces its own baselines
40
+ - Each `config.breakpoints` entry is a bare name (registry dimensions) or `{ name, width?, height? }` to override that mode's size; an omitted axis inherits the registry default
41
+ - Order preserved; when a name repeats, the first entry wins
42
+ - Omit `breakpoints` to run a single `desktop` mode (1280×800)
43
+ - Per-story `breakpoints` field, same shape, that **replaces** the project's modes for that story — a story may run a mode the project does not configure, and its overrides resolve against the registry
44
+ - Breakpoint grouping in the HTML report, each group labelled with the real capture size; a single-mode story renders as a flat list with no breakpoint chrome
45
+ - Pre-breakpoint baselines at `<action>/0.png` are read as fallbacks until promoted
46
+
47
+ ### Removed
48
+
49
+ **Breaking:** `viewport` config field + per-story `viewport` override. Set
50
+ a single mode's size with `breakpoints: [{ name: 'desktop', width, height }]`
51
+ instead.
52
+
53
+ ## [0.1.0-alpha.8] — 2026-06-20
54
+
55
+ ### Fixed
56
+
57
+ - Reset page scroll to origin before each full-page capture, so `position: sticky` and `fixed` elements no longer render shifted by `scrollY`
58
+
59
+ ## [0.1.0-alpha.7] — 2026-06-20
60
+
61
+ ### Added
62
+
63
+ - Add `npm run preview` to render a sample HTML report
64
+
65
+ ### Changed
66
+
67
+ - Capitalize the report title to "Tuffgal report" and show a friendlier timestamp
68
+ - Convey story status as a colored, marked story name instead of a badge
69
+ - Make the whole action row clickable to toggle screenshots
70
+ - Move the expand/collapse buttons to the right of the filters; the story-count status now sits beside the filters, with filter-aware labels
71
+ - Rename "pass" terminology to "passed" throughout the HTML report
72
+ - Show the status badge as plain text (no glyphs)
73
+ - Validate the consumer config before resolving it, failing with the config file path instead of an opaque `TypeError`
74
+
75
+ ### Fixed
76
+
77
+ - Guard the diff core against non-RGBA PNG pixel formats so a format change fails loudly instead of scoring garbage
78
+ - Memoize coverage init so concurrent workers no longer orphan the HTML report
79
+ - Raise subtle-text contrast to clear WCAG 1.4.3 (4.5:1) in both light and dark themes
80
+ - Reject non-finite or non-positive numeric CLI flags at parse time (e.g. `--idle-limit foo` no longer busy-loops the supervisor)
81
+ - Render the HTML report's default screenshot server-side so screenshots still show when JavaScript fails or is disabled
82
+ - Serialize per-baseline writes so stories sharing an action no longer race to create the same baseline under `--workers > 1`
83
+
84
+ ### Removed
85
+
86
+ - Drop redundant "Failures" section at the bottom of the HTML report
87
+ - Stop linking to Playwright trace in the HTML report
88
+
89
+ ## [0.1.0-alpha.6] — 2026-06-11
90
+
91
+ ### Added
92
+
93
+ - Add per-story overrides for browser `viewport` optional config field
94
+ - Add `tuffgal approve --new-only` flag to limit baselines to new stories
95
+ - Allow reports to be filtered by passed, changed, or failed
96
+ - Allow reports to expand/contract all screenshots
97
+
98
+ ### Changed
99
+
100
+ - Edit `tuffgal run` output to group changed, failed at end w/ report link
101
+ - Skip npm release if no version bump
102
+
103
+ ## [0.1.0-alpha.5] — 2026-06-11
104
+
105
+ ### Added
106
+
107
+ `navigate` step accepts an optional `waitUntil` field (`'load' |
108
+ 'domcontentloaded' | 'networkidle' | 'commit'`) that overrides Playwright's
109
+ `page.goto` ready signal on a per-step basis. Defaults to `'networkidle'`.
110
+ Use `'domcontentloaded'` for dev-mode pages with long-tail external fetches
111
+ that prevent `networkidle` from settling.
112
+
113
+ ## [0.1.0-alpha.4] — 2026-06-06
114
+
115
+ ### Added
116
+
117
+ - `tuffgal approve` now updates the a11y snapshot at the same time as the screenshot
118
+ - `--story` works the same way on both `tuffgal run` and `tuffgal approve`
119
+ - Two stories with the same filename in different folders is now an error
120
+
121
+ ### Changed
122
+
123
+ - Stories that get skipped b/c a prereq failed now show up in the run report + totals
124
+ - Screenshot comparison uses SSIM only
125
+ - README and `docs/` pages now match how the code actually works lol
126
+
127
+ ### Removed
128
+
129
+ - **Breaking:** Removed the legacy `diff.maxDiffRatio` setting
130
+ - **Breaking:** Dropped the `ci` block from `TuffgalConfig` and the public API
131
+ - Removed stale `bin/**` and `schema/**` paths
132
+
133
+ ## [0.1.0-alpha.3] — 2026-06-06
134
+
135
+ Grants clipboard permissions in browser contexts.
136
+
137
+ Without `permissions: ['clipboard-read', 'clipboard-write']` Playwright
138
+ default-denies clipboard API access, which breaks any story using the Web
139
+ Clipboard API.
140
+
141
+ ## [0.1.0-alpha.2] — 2026-06-04
142
+
143
+ `npm run build` now copies `src/reporter/assets/{report.css,report.js}`
144
+ into `dist/reporter/assets/`. Earlier alpha builds shipped without the
145
+ reporter's static assets, so the generated HTML report rendered without
146
+ styling or interactivity.
147
+
148
+ ## [0.1.0-alpha.1] — 2026-06-04
149
+
150
+ ### Changed
151
+
152
+ - Package now ships compiled `dist/` instead of raw `.ts` sources
153
+ - CLI entry point is `./dist/cli.js`
154
+
155
+ ### Fixed
156
+
157
+ - Corrected `bin` extension and publish flags for the alpha channel
158
+
159
+ ## [0.1.0-alpha.0] — 2026-06-04
160
+
161
+ Initial public alpha. Tuffgal extracted from [Linklater](https://github.com/nschneble/linklater)'s
162
+ in-tree visual testing workspace.
163
+
164
+ [Unreleased]: https://github.com/nschneble/tuffgal/compare/v0.1.0-alpha.9...HEAD
165
+ [0.1.0-alpha.9]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.9
166
+ [0.1.0-alpha.8]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.8
167
+ [0.1.0-alpha.7]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.7
168
+ [0.1.0-alpha.6]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.6
169
+ [0.1.0-alpha.5]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.5
170
+ [0.1.0-alpha.4]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.4
171
+ [0.1.0-alpha.3]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.3
172
+ [0.1.0-alpha.2]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.2
173
+ [0.1.0-alpha.1]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.1
174
+ [0.1.0-alpha.0]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.0
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Tuffgal
2
2
 
3
- ![GitHub License](https://img.shields.io/github/license/nschneble/tuffgal)
3
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
4
4
 
5
- > JSON-driven visual regression for web apps. Authoring stays declarative,
6
- > screenshots become a committable build artifact.
5
+ JSON-driven visual regression testing for web apps.
7
6
 
8
7
  <img src="tuffgal.png" alt="Tuffgal" />
9
8
 
10
- **Status:** Pre-release. `v0.1.0-alpha` in active extraction from the
11
- `apps/testing-ui` workspace in [Linklater](https://github.com/nschneble/linklater).
9
+ **Status:** Pre-1.0. Published on npm as `tuffgal@0.1.0-alpha.{n}` with
10
+ provenance. [Linklater](https://github.com/nschneble/linklater) is the
11
+ pilot consumer.
12
12
 
13
13
  Public API is unstable until `v1.0.0`.
14
14
 
@@ -24,17 +24,21 @@ When a screenshot changes, a human reviews the diff and decides what to do.
24
24
 
25
25
  ## What ships in v1
26
26
 
27
- - 10 step primitives as action verbs: `click`, `input`, `intercept`,
28
- `navigate`, `read`, `scroll`, `type`, `wait`, `waitFor`, and `screenshot`
29
- as the implicit capture point
27
+ - 9 step primitives composed into actions: `click`, `input`, `intercept`,
28
+ `navigate`, `read`, `scroll`, `type`, `wait`, `waitFor`
29
+ - An implicit screenshot at the end of every action
30
+ - Named breakpoint modes (`mobile`, `tablet`, `laptop`, `desktop`) keyed to
31
+ Tailwind widths. Pick which to run per project or per story. Each mode
32
+ gets its own baseline and a per-mode group in the HTML report
30
33
  - DAG scheduler with `needs`/`produces` labels and parallel workers
31
34
  - SSIM-gated visual diff + pixelmatch overlay + a11y-tree snapshots
32
35
  - Trace zip on failure (Playwright trace viewer)
33
36
  - Clock freeze (`page.clock.install`)
34
37
  - Storage-state persistence across stories
35
- - Static HTML reporter + optional SARIF for GitHub code scanning
38
+ - Static HTML reporter
36
39
  - V8 coverage (optional via `monocart-coverage-reports`)
37
- - Per-story DB reset + fixture hooks (consumer-supplied via config)
40
+ - Per-run DB reset + per-breakpoint fixture hooks (consumer-supplied via config),
41
+ plus a `${breakpoint}` interpolation token for per-mode test data
38
42
  - Process supervisor for dev-server hot-reload rot (it happens)
39
43
 
40
44
  ## What's explicitly out of scope (v1)
@@ -45,18 +49,23 @@ When a screenshot changes, a human reviews the diff and decides what to do.
45
49
  - WebDriver / Puppeteer substrate
46
50
  - Supporting browsers other than Chromium
47
51
 
48
- ## Quick start (when alpha releases)
52
+ ## Quick start
49
53
 
50
54
  ```bash
51
- npm install -D tuffgal
55
+ npm install -D tuffgal@alpha
52
56
  npx tuffgal init # scaffolds tuffgal.config.ts
53
57
  npx tuffgal run # runs all stories
54
58
  ```
55
59
 
60
+ For CI on GitHub Actions, use the companion
61
+ [`nschneble/tuffgal-action`](https://github.com/nschneble/tuffgal-action)
62
+ composite action.
63
+
56
64
  ## Documentation
57
65
 
58
66
  - [App contract](docs/app-contract.md)
59
67
  - [Authoring guide](docs/authoring.md)
68
+ - [Changelog](CHANGELOG.md)
60
69
  - [CI integration](docs/ci.md)
61
70
  - [Config reference](docs/config.md)
62
71
  - [Migrating from Cypress](docs/migration-cypress.md)
@@ -76,8 +85,8 @@ MIT. See [LICENSE](LICENSE).
76
85
  | Core extraction | ✅ |
77
86
  | Bridges | ✅ |
78
87
  | Linklater migration | ✅ |
79
- | GitHub Action | 🚧 |
80
- | `v0.1.0-alpha` npm publish | 🚧 |
88
+ | GitHub Action | ✅ |
89
+ | `v0.1.0-alpha` npm publish | ✅ |
81
90
  | `v1.0.0` public launch | ⏳ |
82
91
  | `v1.1.0` AI fuzzy matching | Deferred |
83
92
 
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ import { approveAll } from "./runner/approve.js";
3
+ import { init } from "./commands/init.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { runAll } from "./runner/run.js";
6
+ import { supervise } from "./commands/supervise.js";
7
+ const COMMANDS = ['approve', 'help', 'init', 'run', 'supervise'];
8
+ function parseArguments(argv) {
9
+ const [command, ...rest] = argv;
10
+ const parsed = {
11
+ command: COMMANDS.includes(command)
12
+ ? command
13
+ : 'help',
14
+ headed: false,
15
+ manageServers: false,
16
+ coverage: false,
17
+ newOnly: false,
18
+ };
19
+ for (let index = 0; index < rest.length; index += 1) {
20
+ const arg = rest[index];
21
+ if (arg === undefined)
22
+ continue;
23
+ if (arg === '--headed') {
24
+ parsed.headed = true;
25
+ }
26
+ else if (arg === '--story') {
27
+ parsed.storyFilter = rest[index + 1];
28
+ index += 1;
29
+ }
30
+ else if (arg.startsWith('--story=')) {
31
+ parsed.storyFilter = arg.slice('--story='.length);
32
+ }
33
+ else if (arg === '--workers') {
34
+ parsed.workers = numericFlag('--workers', rest[index + 1]);
35
+ index += 1;
36
+ }
37
+ else if (arg.startsWith('--workers=')) {
38
+ parsed.workers = numericFlag('--workers', arg.slice('--workers='.length));
39
+ }
40
+ else if (arg === '--manage-servers') {
41
+ parsed.manageServers = true;
42
+ }
43
+ else if (arg === '--coverage') {
44
+ parsed.coverage = true;
45
+ }
46
+ else if (arg === '--new-only') {
47
+ parsed.newOnly = true;
48
+ }
49
+ else if (arg === '--healthcheck-interval') {
50
+ parsed.healthcheckIntervalMs = numericFlag('--healthcheck-interval', rest[index + 1]);
51
+ index += 1;
52
+ }
53
+ else if (arg.startsWith('--healthcheck-interval=')) {
54
+ parsed.healthcheckIntervalMs = numericFlag('--healthcheck-interval', arg.slice('--healthcheck-interval='.length));
55
+ }
56
+ else if (arg === '--idle-limit') {
57
+ parsed.idleLimitMs = numericFlag('--idle-limit', rest[index + 1]);
58
+ index += 1;
59
+ }
60
+ else if (arg.startsWith('--idle-limit=')) {
61
+ parsed.idleLimitMs = numericFlag('--idle-limit', arg.slice('--idle-limit='.length));
62
+ }
63
+ else if (arg === '--max-runtime') {
64
+ parsed.maxRuntimeMs = numericFlag('--max-runtime', rest[index + 1]);
65
+ index += 1;
66
+ }
67
+ else if (arg.startsWith('--max-runtime=')) {
68
+ parsed.maxRuntimeMs = numericFlag('--max-runtime', arg.slice('--max-runtime='.length));
69
+ }
70
+ else if (arg === '--max-respawns') {
71
+ parsed.maxRespawns = numericFlag('--max-respawns', rest[index + 1]);
72
+ index += 1;
73
+ }
74
+ else if (arg.startsWith('--max-respawns=')) {
75
+ parsed.maxRespawns = numericFlag('--max-respawns', arg.slice('--max-respawns='.length));
76
+ }
77
+ }
78
+ return parsed;
79
+ }
80
+ /**
81
+ * Parses a numeric CLI flag, rejecting anything non-finite or <= 0. Without
82
+ * this, `--idle-limit foo` became `NaN` and flowed into `setTimeout(…, NaN)`,
83
+ * which behaves as `0` and busy-loops the supervisor's healthcheck. Fail loudly
84
+ * at parse time instead.
85
+ */
86
+ function numericFlag(flag, raw) {
87
+ const value = Number(raw);
88
+ if (raw === undefined ||
89
+ raw === '' ||
90
+ !Number.isFinite(value) ||
91
+ value <= 0) {
92
+ throw new Error(`${flag} requires a positive number (got "${raw ?? ''}")`);
93
+ }
94
+ return value;
95
+ }
96
+ function printHelp() {
97
+ process.stdout.write([
98
+ 'Usage: tuffgal <command> [options]',
99
+ '',
100
+ 'Commands:',
101
+ ' run Run every story under the configured stories directory.',
102
+ ' approve Promote every "changed" actual to its baseline.',
103
+ ' init Scaffold a tuffgal.config.ts in the current directory.',
104
+ ' supervise Long-running wrapper around devServers.command with',
105
+ ' healthcheck restart, idle auto-term, and wall-clock cap.',
106
+ ' help Show this message.',
107
+ '',
108
+ 'Options:',
109
+ ' --story <name> Filter to a single story (filename or story text).',
110
+ ' --headed Show the browser while running.',
111
+ ' --workers N Override the worker pool size (default min(cpus/2, 4)).',
112
+ ' --manage-servers Spawn devServers.command, wait, run, then kill it.',
113
+ ' --coverage Capture V8 JS + CSS coverage and emit a monocart report.',
114
+ '',
115
+ 'Approve options:',
116
+ ' --new-only Only promote new baselines; skip changed.',
117
+ '',
118
+ 'Supervise options:',
119
+ ' --healthcheck-interval N Probe interval in ms (default 30_000).',
120
+ ' --idle-limit N Ms with no `tuffgal run` heartbeat before exit (default 600_000).',
121
+ ' --max-runtime N Wall-clock cap in ms (default 3_600_000).',
122
+ ' --max-respawns N Respawn budget after unhealthy/exit (default 3).',
123
+ ].join('\n') + '\n');
124
+ }
125
+ async function main() {
126
+ const args = parseArguments(process.argv.slice(2));
127
+ if (args.newOnly &&
128
+ (args.command === 'run' ||
129
+ args.command === 'supervise' ||
130
+ args.command === 'init')) {
131
+ process.stderr.write('tuffgal error: --new-only is only valid with the `approve` subcommand\n');
132
+ process.exit(1);
133
+ }
134
+ if (args.command === 'help') {
135
+ printHelp();
136
+ return;
137
+ }
138
+ if (args.command === 'init') {
139
+ await init({ cwd: process.cwd() });
140
+ return;
141
+ }
142
+ const config = await loadConfig(process.cwd());
143
+ if (args.command === 'run') {
144
+ const result = await runAll(config, {
145
+ storyFilter: args.storyFilter,
146
+ headed: args.headed,
147
+ workers: args.workers,
148
+ manageServers: args.manageServers,
149
+ coverage: args.coverage,
150
+ });
151
+ process.exit(result.totals.failed > 0 ? 1 : 0);
152
+ }
153
+ if (args.command === 'approve') {
154
+ const summary = await approveAll(config, {
155
+ storyFilter: args.storyFilter,
156
+ newOnly: args.newOnly,
157
+ });
158
+ process.stdout.write(`\nApproved ${summary.approved} baselines; skipped ${summary.skipped} actions.\n`);
159
+ }
160
+ if (args.command === 'supervise') {
161
+ await supervise(config, {
162
+ healthcheckIntervalMs: args.healthcheckIntervalMs,
163
+ idleLimitMs: args.idleLimitMs,
164
+ maxRuntimeMs: args.maxRuntimeMs,
165
+ maxRespawns: args.maxRespawns,
166
+ });
167
+ }
168
+ }
169
+ main().catch((error) => {
170
+ process.stderr.write(`tuffgal error: ${error instanceof Error ? error.message : String(error)}\n`);
171
+ process.exit(1);
172
+ });
173
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface InitOptions {
2
+ cwd: string;
3
+ }
4
+ /**
5
+ * Scaffolds a starter Tuffgal layout in the given directory:
6
+ * - tuffgal.config.ts (commented template)
7
+ * - tuffgal/actions/.gitkeep
8
+ * - tuffgal/stories/.gitkeep
9
+ * - tuffgal/baselines/.gitkeep
10
+ * - tuffgal/.gitignore (ignores .auth/ and report/)
11
+ *
12
+ * Refuses to overwrite an existing tuffgal.config.{ts,js}. Existing
13
+ * scaffolded directories are left alone — a second \`init\` is safe to run
14
+ * after manually deleting just the config.
15
+ */
16
+ export declare function init(options: InitOptions): Promise<void>;
17
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA+EA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6C9D"}
@@ -1,8 +1,7 @@
1
- import { mkdir, writeFile, access } from 'node:fs/promises';
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { join, relative } from 'node:path';
3
-
3
+ import { pathExists } from "../util.js";
4
4
  const CONFIG_FILES = ['tuffgal.config.ts', 'tuffgal.config.js'];
5
-
6
5
  const CONFIG_TEMPLATE = `import { defineConfig } from 'tuffgal';
7
6
 
8
7
  /**
@@ -31,7 +30,14 @@ export default defineConfig({
31
30
  // leave this empty — cookies always persist via Playwright storage state.
32
31
  // storageStatePins: ['session_token'],
33
32
 
34
- viewport: { width: 1280, height: 800 },
33
+ // Breakpoint modes to run, keyed to Tailwind widths:
34
+ // mobile 375x667 · tablet 768x1024 · laptop 1024x768 · desktop 1280x800
35
+ // Each mode gets its own baseline + a group in the report. Omit to run a
36
+ // single \`desktop\` mode (1280x800). Override a mode's size with an object,
37
+ // e.g. { name: 'desktop', width: 1440, height: 900 }. A story can run its own
38
+ // modes via its own \`breakpoints\` field, which replace these for that story.
39
+ // breakpoints: ['mobile', 'tablet', 'laptop', 'desktop'],
40
+
35
41
  defaultTimeoutMs: 10_000,
36
42
  navigationTimeoutMs: 15_000,
37
43
  frozenTime: '2026-01-15T12:00:00.000Z',
@@ -57,22 +63,15 @@ export default defineConfig({
57
63
  // },
58
64
  });
59
65
  `;
60
-
61
66
  const GITKEEP_SUBDIRECTORIES = [
62
- 'tuffgal/actions',
63
- 'tuffgal/stories',
64
- 'tuffgal/baselines',
67
+ 'tuffgal/actions',
68
+ 'tuffgal/stories',
69
+ 'tuffgal/baselines',
65
70
  ];
66
-
67
71
  const REPORT_GITIGNORE = `# Generated by Tuffgal — do not commit.
68
72
  .auth/
69
73
  report/
70
74
  `;
71
-
72
- export interface InitOptions {
73
- cwd: string;
74
- }
75
-
76
75
  /**
77
76
  * Scaffolds a starter Tuffgal layout in the given directory:
78
77
  * - tuffgal.config.ts (commented template)
@@ -85,56 +84,39 @@ export interface InitOptions {
85
84
  * scaffolded directories are left alone — a second \`init\` is safe to run
86
85
  * after manually deleting just the config.
87
86
  */
88
- export async function init(options: InitOptions): Promise<void> {
89
- const { cwd } = options;
90
-
91
- for (const candidate of CONFIG_FILES) {
92
- if (await pathExists(join(cwd, candidate))) {
93
- throw new Error(
94
- `${candidate} already exists at ${cwd}. Refusing to overwrite. ` +
95
- `Delete it manually if you want to start over.`,
96
- );
87
+ export async function init(options) {
88
+ const { cwd } = options;
89
+ for (const candidate of CONFIG_FILES) {
90
+ if (await pathExists(join(cwd, candidate))) {
91
+ throw new Error(`${candidate} already exists at ${cwd}. Refusing to overwrite. ` +
92
+ `Delete it manually if you want to start over.`);
93
+ }
97
94
  }
98
- }
99
-
100
- const configPath = join(cwd, 'tuffgal.config.ts');
101
- await writeFile(configPath, CONFIG_TEMPLATE, 'utf8');
102
- process.stdout.write(`Wrote ${relative(cwd, configPath) || 'tuffgal.config.ts'}\n`);
103
-
104
- for (const subdirectory of GITKEEP_SUBDIRECTORIES) {
105
- const absolute = join(cwd, subdirectory);
106
- await mkdir(absolute, { recursive: true });
107
- const gitkeep = join(absolute, '.gitkeep');
108
- if (!(await pathExists(gitkeep))) {
109
- await writeFile(gitkeep, '', 'utf8');
110
- process.stdout.write(`Wrote ${relative(cwd, gitkeep)}\n`);
95
+ const configPath = join(cwd, 'tuffgal.config.ts');
96
+ await writeFile(configPath, CONFIG_TEMPLATE, 'utf8');
97
+ process.stdout.write(`Wrote ${relative(cwd, configPath) || 'tuffgal.config.ts'}\n`);
98
+ for (const subdirectory of GITKEEP_SUBDIRECTORIES) {
99
+ const absolute = join(cwd, subdirectory);
100
+ await mkdir(absolute, { recursive: true });
101
+ const gitkeep = join(absolute, '.gitkeep');
102
+ if (!(await pathExists(gitkeep))) {
103
+ await writeFile(gitkeep, '', 'utf8');
104
+ process.stdout.write(`Wrote ${relative(cwd, gitkeep)}\n`);
105
+ }
111
106
  }
112
- }
113
-
114
- const gitignorePath = join(cwd, 'tuffgal', '.gitignore');
115
- if (!(await pathExists(gitignorePath))) {
116
- await writeFile(gitignorePath, REPORT_GITIGNORE, 'utf8');
117
- process.stdout.write(`Wrote ${relative(cwd, gitignorePath)}\n`);
118
- }
119
-
120
- process.stdout.write(
121
- [
122
- '',
123
- 'Tuffgal scaffold ready. Next steps:',
124
- ' 1. Edit tuffgal.config.ts to point at your dev server.',
125
- ' 2. Write your first action under tuffgal/actions/.',
126
- ' 3. Write your first story under tuffgal/stories/.',
127
- ' 4. Run `npx tuffgal run` to capture baselines.',
128
- '',
129
- ].join('\n'),
130
- );
131
- }
132
-
133
- async function pathExists(absolute: string): Promise<boolean> {
134
- try {
135
- await access(absolute);
136
- return true;
137
- } catch {
138
- return false;
139
- }
107
+ const gitignorePath = join(cwd, 'tuffgal', '.gitignore');
108
+ if (!(await pathExists(gitignorePath))) {
109
+ await writeFile(gitignorePath, REPORT_GITIGNORE, 'utf8');
110
+ process.stdout.write(`Wrote ${relative(cwd, gitignorePath)}\n`);
111
+ }
112
+ process.stdout.write([
113
+ '',
114
+ 'Tuffgal scaffold ready. Next steps:',
115
+ ' 1. Edit tuffgal.config.ts to point at your dev server.',
116
+ ' 2. Write your first action under tuffgal/actions/.',
117
+ ' 3. Write your first story under tuffgal/stories/.',
118
+ ' 4. Run `npx tuffgal run` to capture baselines.',
119
+ '',
120
+ ].join('\n'));
140
121
  }
122
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,YAAY,GAAG,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;AAEhE,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4DvB,CAAC;AAEF,MAAM,sBAAsB,GAAG;IAC7B,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;CACpB,CAAC;AAEF,MAAM,gBAAgB,GAAG;;;CAGxB,CAAC;AAMF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAoB;IAC7C,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAExB,KAAK,MAAM,SAAS,IAAI,YAAY,EAAE,CAAC;QACrC,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CACb,GAAG,SAAS,sBAAsB,GAAG,2BAA2B;gBAC9D,+CAA+C,CAClD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAClD,MAAM,SAAS,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,CAAC,CAAC;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,SAAS,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,mBAAmB,IAAI,CAC9D,CAAC;IAEF,KAAK,MAAM,YAAY,IAAI,sBAAsB,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACzC,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAC3C,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACjC,MAAM,SAAS,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;IACzD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;QACvC,MAAM,SAAS,CAAC,aAAa,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;QACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,EAAE;QACF,qCAAqC;QACrC,0DAA0D;QAC1D,sDAAsD;QACtD,qDAAqD;QACrD,kDAAkD;QAClD,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { ResolvedConfig } from '../config.ts';
2
+ export interface SuperviseOptions {
3
+ /** Milliseconds between port + heartbeat probes. Default 30_000. */
4
+ healthcheckIntervalMs?: number;
5
+ /**
6
+ * Window with no `tuffgal run` heartbeat before the supervisor self-
7
+ * terminates. Default 10 minutes. The heartbeat file lives at
8
+ * `config.paths.report/.heartbeat` and is touched by every `tuffgal
9
+ * run` invocation.
10
+ */
11
+ idleLimitMs?: number;
12
+ /** Wall-clock cap, end-to-end. Default 60 minutes. */
13
+ maxRuntimeMs?: number;
14
+ /**
15
+ * How many times the supervisor may respawn `devServers.command` after
16
+ * an unhealthy probe or unexpected exit before giving up. Default 3.
17
+ */
18
+ maxRespawns?: number;
19
+ }
20
+ /**
21
+ * Long-running supervisor around `config.devServers.command`. Solves
22
+ * three problems observed during heavy harness iteration:
23
+ *
24
+ * 1. Hot-reload rot — after many file-watch restarts the framework
25
+ * drifts into a broken state. Supervisor probes every declared
26
+ * healthcheck URL and restarts the whole tree on failure.
27
+ * 2. Forgotten dev servers — supervisor self-terminates after a
28
+ * wall-clock cap and after an idle window with no heartbeat from
29
+ * `tuffgal run`.
30
+ * 3. Manual signal management — SIGINT/SIGTERM teardown kills the
31
+ * whole detached process group, not just the shell wrapper.
32
+ *
33
+ * Each invocation of `tuffgal run` writes
34
+ * `config.paths.report/.heartbeat` (ISO timestamp). Supervisor reads
35
+ * its mtime every healthcheck. If `Date.now() - mtime > idleLimitMs`,
36
+ * supervisor shuts down. Missing heartbeat file is treated as "no runs
37
+ * yet", not stale, so a fresh shell does not self-terminate immediately.
38
+ */
39
+ export declare function supervise(config: ResolvedConfig, options?: SuperviseOptions): Promise<void>;
40
+ //# sourceMappingURL=supervise.d.ts.map