@casomoltd/tooling 0.22.5 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ Shared linting, formatting, commit config, and CLI tools for Casomo Ltd's repos.
25
25
  | `pre-commit` | Husky pre-commit hook — runs `npm run check` |
26
26
  | `commit-msg` | Husky commit-msg hook — runs commitlint |
27
27
  | `readability` | Measure reading difficulty of page content |
28
- | `screenshot` | Capture a dev server page via Puppeteer |
28
+ | `screenshot` | Capture a dev server page via Playwright — an agent's visual feedback loop |
29
29
  | `build-report` | Compile a Typst client report to PDF with the house template |
30
30
 
31
31
  ## Install
@@ -43,9 +43,31 @@ npm i -D @casomoltd/tooling
43
43
 
44
44
  The package is public, so installs need no token or `.npmrc`. The `files`
45
45
  allowlist plus the `verify-pack` leak-gate keep the published surface to
46
- configs/CLI only. `puppeteer` (used by the `screenshot` bin) is an
47
- **optional peer dependency** — add it yourself (`npm i -D puppeteer`) in
48
- repos that run `screenshot`.
46
+ configs/CLI only.
47
+
48
+ **Installing this package never installs a browser.** The `screenshot`
49
+ bin needs one and `build-report` needs the `typst` CLI, but neither is
50
+ declared here — see [External prerequisites](#external-prerequisites).
51
+
52
+ ## External prerequisites
53
+
54
+ Two bins need something npm cannot sensibly deliver: `screenshot` needs
55
+ a Chromium build, `build-report` needs the `typst` CLI and the IBM Plex
56
+ fonts. **Neither is declared — not as a dependency, and not as an
57
+ optional peer.** Both load their prerequisite lazily and fail with an
58
+ install instruction when it is missing, so a consumer that never runs
59
+ those bins carries nothing for them.
60
+
61
+ The rule, for anything added later: a heavyweight prerequisite only some
62
+ consumers need is documented here, never declared. An optional peer
63
+ looks free because npm installs nothing for it, but it resolves into a
64
+ consumer's lockfile as soon as anything pulls it in, and npm will not
65
+ prune a satisfied optional peer afterwards — not on `npm install`, not
66
+ on `npm install --package-lock-only`, not on `npm uninstall`. That is
67
+ how one dev-only browser put the `extract-zip` advisory
68
+ (GHSA-jmr9-qjv8-65gv) into four repos that never took a screenshot, and
69
+ why clearing it needed a release of this package rather than a fix in
70
+ each of them.
49
71
 
50
72
  ## Husky hooks
51
73
 
@@ -104,7 +126,20 @@ version that ships the config.
104
126
 
105
127
  ## Screenshot tool
106
128
 
107
- Capture the running dev server for visual inspection:
129
+ A visual feedback loop for coding agents. Claude captures the page it
130
+ just changed, reads the PNG back and checks its own work, instead of
131
+ waiting for someone to look and paste a screen grab. That is what the
132
+ bin is for, and why its output lands under `.claude/`; a person wanting
133
+ a screenshot already has a browser open.
134
+
135
+ It needs a Chromium build, which this package does not install (see
136
+ [External prerequisites](#external-prerequisites)):
137
+
138
+ ```bash
139
+ npm i -D playwright && npx playwright install chromium
140
+ ```
141
+
142
+ Then capture the running dev server:
108
143
 
109
144
  ```bash
110
145
  npm run ss # 1280×800 desktop capture
@@ -175,10 +210,9 @@ after is the body. From a consumer repo the import goes through
175
210
  `report/example.typ` is a lorem-ipsum reference report exercising every
176
211
  feature the template styles — compile it to see the house style.
177
212
 
178
- Two prerequisites are external to npm (like puppeteer for
179
- `screenshot`, they are deliberately not dependencies): the **`typst`
180
- CLI** on PATH (`snap install typst` / `cargo install typst-cli`) and
181
- the **IBM Plex Sans / IBM Plex Mono** fonts
213
+ Two [external prerequisites](#external-prerequisites) apply here: the
214
+ **`typst` CLI** on PATH (`snap install typst` / `cargo install
215
+ typst-cli`) and the **IBM Plex Sans / IBM Plex Mono** fonts
182
216
  ([github.com/IBM/plex](https://github.com/IBM/plex)) — `build-report`
183
217
  fails loud on the former and warns on the latter.
184
218
 
@@ -83,11 +83,11 @@ function findRoot(from) {
83
83
  }
84
84
  const root = values.root ? path.resolve(values.root) : findRoot(input);
85
85
 
86
- // typst is an external native binary (like puppeteer for screenshot, it is
87
- // deliberately not an npm dependency). One probe does double duty: ENOENT
88
- // means typst is missing; otherwise the output feeds the font check. Missing
89
- // fonts don't fail a Typst compile — it silently falls back and the PDF comes
90
- // out off-brand — so warn loud up front.
86
+ // typst is an external native binary (like playwright's browsers for
87
+ // screenshot, it is deliberately not an npm dependency). One probe does
88
+ // double duty: ENOENT means typst is missing; otherwise the output feeds
89
+ // the font check. Missing fonts don't fail a Typst compile — it silently
90
+ // falls back and the PDF comes out off-brand — so warn loud up front.
91
91
  const fonts = spawnSync("typst", ["fonts"], {encoding: "utf8"});
92
92
  if (fonts.error) {
93
93
  fail(
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Capture a screenshot of the running dev server.
4
+ * Capture a screenshot of a running dev server.
5
5
  *
6
6
  * Usage:
7
7
  * npm run ss [page] [--width <n>] [--height <n>]
@@ -25,37 +25,38 @@
25
25
  *
26
26
  * Environment:
27
27
  * SCREENSHOT_URL Base URL to capture (default: http://localhost:3000)
28
+ * SCREENSHOT_DIR Output directory (default: <cwd>/.claude/screenshots)
28
29
  *
29
30
  * Output:
30
- * .claude/screenshots/{page}-{timestamp}.png Timestamped capture
31
- * .claude/screenshots/latest.png Always the most recent capture
31
+ * {dir}/{page}-{timestamp}.png Timestamped capture
32
+ * {dir}/latest.png Always the most recent capture
32
33
  */
33
34
 
34
35
  import { parseArgs } from "node:util";
35
36
  import path from "path";
36
37
  import fs from "fs";
37
38
 
38
- // puppeteer is an optional peer dependency (it pulls Chromium, ~200MB), so
39
- // consumers that never screenshot don't install it. Load it lazily and fail
40
- // loud if the screenshot bin is invoked without it.
41
- async function loadPuppeteer() {
39
+ // playwright is an external prerequisite, not a dependency of this package.
40
+ // A declared optional peer resolves into every consumer's lockfile and npm
41
+ // will not prune it again, so a browser nobody screenshots with becomes a
42
+ // standing advisory. Load it lazily and fail loud when it is absent.
43
+ async function loadChromium() {
42
44
  try {
43
- return (await import("puppeteer")).default;
45
+ return (await import("playwright")).chromium;
44
46
  } catch {
45
47
  throw new Error(
46
- "puppeteer is not installed — add it as a devDependency " +
47
- "(npm i -D puppeteer) to use the screenshot bin.",
48
+ "playwright is not installed — add it as a devDependency " +
49
+ "(npm i -D playwright && npx playwright install chromium) " +
50
+ "to use the screenshot bin.",
48
51
  );
49
52
  }
50
53
  }
51
54
 
52
55
  const BASE_URL =
53
56
  process.env.SCREENSHOT_URL || "http://localhost:3000";
54
- const OUTPUT_DIR = path.join(
55
- process.cwd(),
56
- ".claude",
57
- "screenshots",
58
- );
57
+ const OUTPUT_DIR =
58
+ process.env.SCREENSHOT_DIR ||
59
+ path.join(process.cwd(), ".claude", "screenshots");
59
60
 
60
61
  const PRESETS = {
61
62
  mobile: { width: 390, height: 844 },
@@ -111,15 +112,16 @@ async function screenshot() {
111
112
 
112
113
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
113
114
 
114
- const puppeteer = await loadPuppeteer();
115
- const browser = await puppeteer.launch({
115
+ const chromium = await loadChromium();
116
+ const browser = await chromium.launch({
116
117
  headless: true,
117
118
  args: ["--no-sandbox", "--disable-setuid-sandbox"],
118
119
  });
119
120
 
120
121
  try {
121
- const page = await browser.newPage();
122
- await page.setViewport({ width, height });
122
+ const page = await browser.newPage({
123
+ viewport: { width, height },
124
+ });
123
125
 
124
126
  const url =
125
127
  pageName === "base"
@@ -128,7 +130,7 @@ async function screenshot() {
128
130
  console.log(`Navigating to ${url}...`);
129
131
 
130
132
  await page.goto(url, {
131
- waitUntil: "networkidle0",
133
+ waitUntil: "networkidle",
132
134
  timeout: 30000,
133
135
  });
134
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@casomoltd/tooling",
3
- "version": "0.22.5",
3
+ "version": "0.23.1",
4
4
  "description": "Casomo's shared dev config: ESLint, Prettier, commitlint, knip & jscpd presets and dev CLIs (git hooks, readability, screenshots)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,13 +55,7 @@
55
55
  "eslint-config-next": ">=16",
56
56
  "eslint-plugin-sonarjs": ">=3",
57
57
  "jscpd": ">=4",
58
- "knip": ">=5",
59
- "puppeteer": ">=24"
60
- },
61
- "peerDependenciesMeta": {
62
- "puppeteer": {
63
- "optional": true
64
- }
58
+ "knip": ">=5"
65
59
  },
66
60
  "devDependencies": {
67
61
  "@types/node": "^26.0.0",