@afixt/screenshot-utils 0.9.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/CHANGELOG.md +39 -0
- package/LICENSE +13 -0
- package/README.md +132 -0
- package/bin/afixt-screenshot +14 -0
- package/dist/index.js +2797 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2795 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +164 -0
- package/src/adapters/cdp.js +116 -0
- package/src/adapters/index.js +17 -0
- package/src/adapters/playwright.js +150 -0
- package/src/adapters/puppeteer.js +128 -0
- package/src/capture/element.js +257 -0
- package/src/capture/index.js +17 -0
- package/src/capture/long-page.js +267 -0
- package/src/capture/page.js +207 -0
- package/src/capture/responsive.js +94 -0
- package/src/capture/settle.js +182 -0
- package/src/capture/viewport.js +21 -0
- package/src/cli/index.js +266 -0
- package/src/compose/annotate.js +283 -0
- package/src/compose/diff.js +173 -0
- package/src/compose/heatmap.js +159 -0
- package/src/compose/index.js +8 -0
- package/src/compose/theme.js +45 -0
- package/src/errors/adapter.js +65 -0
- package/src/errors/base.js +48 -0
- package/src/errors/capture.js +175 -0
- package/src/errors/compose.js +78 -0
- package/src/errors/index.js +75 -0
- package/src/errors/optional.js +37 -0
- package/src/errors/redaction.js +42 -0
- package/src/errors/tile.js +61 -0
- package/src/errors/transform.js +78 -0
- package/src/index.js +69 -0
- package/src/redact/index.js +6 -0
- package/src/redact/policy.js +67 -0
- package/src/redact/redact.js +268 -0
- package/src/tile/dzi.js +268 -0
- package/src/tile/index.js +5 -0
- package/src/transform/convert.js +54 -0
- package/src/transform/crop.js +251 -0
- package/src/transform/index.js +8 -0
- package/src/transform/normalize.js +83 -0
- package/src/transform/resize.js +99 -0
- package/src/types/index.d.ts +432 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.9.0] - 2026-05-22
|
|
11
|
+
|
|
12
|
+
First published release. Phases 0–4 of `PLAN.md` are complete: the full
|
|
13
|
+
public API surface from `spec.md` § 8 is implemented and tested (221
|
|
14
|
+
tests). Released as `0.9.0` rather than `1.0.0` because the v1.0.0
|
|
15
|
+
acceptance bar in `spec.md` § 23 is not yet fully met — OpenSeadragon
|
|
16
|
+
end-to-end integration, 80% coverage thresholds, the performance suite
|
|
17
|
+
running green in CI, and live Playwright/CDP integration tests remain.
|
|
18
|
+
The API is expected to stay stable through to `1.0.0`.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Phase 0 bootstrap: package.json, ESLint flat config, Prettier, Husky hooks (pre-commit / commit-msg / pre-push / post-merge), commitlint, jscpd, JSDoc setup, GitHub Actions workflows (`ci`, `pr-check`, `security`, `docs`).
|
|
23
|
+
- Build pipeline producing dual CJS / ESM bundles via `tsup`, with `.d.ts` emitted from JSDoc via `tsc`.
|
|
24
|
+
- Phase 1 M1.1 — full error hierarchy per spec § 14.1 (25 classes: `ScreenshotUtilsError` + `CaptureError` tree + `TransformError` tree + `ComposeError` tree + `TileError` tree + `RedactionError` tree + `AdapterError` tree + `OptionalDependencyMissingError`). Every class carries a stable `code`, optional `reason`, optional `cause`, optional `context`, and a `retriable` boolean per spec § 14.2 — § 14.4.
|
|
25
|
+
- Phase 1 M1.1 — comprehensive TypeScript type definitions in `src/types/index.d.ts` covering geometry, capture / transform / compose / tile / redact options and results, the `PageAdapter` contract, and the error shape (spec §§ 8, 9, 10, 14).
|
|
26
|
+
- Expanded `defaults` to match spec § 11.1 exactly (`format`, `quality`, `fullPage`, `timeout`, `padding`, `stripHeight`, `tileSize`, `tileOverlap`, `tileFormat`, `tileQuality`, `waitForStable`).
|
|
27
|
+
- Public surface re-exports every error class alongside `defaults` and `version`.
|
|
28
|
+
|
|
29
|
+
- Phase 1 M1.2 — `fromPuppeteerPage(page, options?)` adapter wrapping a Puppeteer `Page` into the package's `PageAdapter` interface. Validates the page object up-front (throws `AdapterContractError` listing missing methods), wraps every browser call so failures surface as `BrowserApiError` with operation context, and reports Chromium-default capabilities (16384 max screenshot height, viewport-emulation + CDP support). No top-level `require('puppeteer')` — the consumer owns the install.
|
|
30
|
+
- Phase 1 M1.2 — reusable adapter contract suite at `test/adapters/contract.js`. Any adapter (mock, Puppeteer, future Playwright + CDP) must pass the same assertions: required-method coverage, capability shape, screenshot returns Buffer, getMetrics returns PageMetrics with ISO-8601 capturedAt, scroll/setViewport/evaluate resolve cleanly.
|
|
31
|
+
- Phase 1 M1.2 — test-only mock adapter at `test/_helpers/mock-adapter.js` (not exported from the package) for unit-testing capture functions without launching a browser.
|
|
32
|
+
- Phase 1 M1.2 — Puppeteer integration test that launches headless Chromium, navigates to `test/fixtures/simple.html`, runs the contract suite, and verifies PNG magic bytes on a real screenshot.
|
|
33
|
+
- Phase 1 M1.3 — `capturePage(adapter, options?)` implementing spec § 8.1.1. Returns a `CaptureResult` with `format`, `bytes` (Buffer or Readable stream), `page` metrics, `timing` (`settleMs`/`captureMs`/`encodeMs`/`totalMs`), and `warnings`. `sharp` is lazy-loaded only on the re-encode path (webp output). Emits `capture:start` and `capture:complete` events on the optional `events` sink. `timeout` enforced via a `CaptureTimeoutError`-throwing race. `UnsupportedFormatError` thrown for non-`png|jpeg|webp` formats.
|
|
34
|
+
- Phase 1 M1.4 — `waitForStable(adapter, options?)` implementing spec § 8.1.6. Awaits `document.fonts.ready`, image `load`/`error` events, CSS animations (per `animations: 'reduce' | 'wait' | 'ignore'`), and `requestIdleCallback` + idle ms settle. Runs entirely in the page context via a single `adapter.evaluate()` call to avoid Node↔browser round-trips. Returns `StabilityReport { fonts, images, animations, totalMs }`. Integrated into `capturePage`: when the `waitForStable` option is set, the timing.settleMs reflects the wait and any timeouts surface as `warnings` on the result.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Coverage provider switched from `istanbul` to `v8` so cross-file `instanceof` checks track correctly through the CJS+ESM bridge in Vitest.
|
|
39
|
+
- ESLint test override now declares Vitest globals (describe, it, expect, beforeAll, afterAll, etc.) so `.js` test helpers can rely on them without importing from `vitest` (Vitest is ESM-only and cannot be required from CJS test helpers).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright (c) AFixt. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its source code are proprietary and confidential. No license,
|
|
4
|
+
express or implied, is granted to any third party. Possession of this source
|
|
5
|
+
does not convey any right to use, copy, modify, merge, publish, distribute,
|
|
6
|
+
sublicense, or sell copies of this software, in whole or in part, except as
|
|
7
|
+
authorized in writing by AFixt.
|
|
8
|
+
|
|
9
|
+
Internal AFixt / Revenant platform users may use this package within the
|
|
10
|
+
platform's scope under the terms of their employment, contractor, or
|
|
11
|
+
licensing agreement with AFixt.
|
|
12
|
+
|
|
13
|
+
For licensing inquiries, contact: karl.groves@afixt.com
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @afixt/screenshot-utils
|
|
2
|
+
|
|
3
|
+
> Screenshot capture, transform, compose, tile, and redact utilities for the AFixt/Revenant accessibility platform.
|
|
4
|
+
|
|
5
|
+
Status: **0.9.0** — Phases 0–4 of [`PLAN.md`](./PLAN.md) are complete; the full public API from [`spec.md`](./spec.md) § 8 is implemented and tested. Pre-1.0 while the v1.0.0 acceptance bar in `spec.md` § 23 is finished (OpenSeadragon e2e, 80% coverage, perf suite in CI). The API is expected to stay stable through to 1.0.0.
|
|
6
|
+
|
|
7
|
+
This package owns **all** screenshot operations for the AFixt/Revenant platform: full-page, viewport, element, long-page strip-stitch, multi-viewport capture; crop / resize / convert / normalize transforms; annotate / heatmap / diff / composite; DZI tile pyramids; and PII redaction. It is being extracted from `@afixt/afixt-engine`; the engine will become a consumer.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @afixt/screenshot-utils
|
|
15
|
+
# optional peers — install only what you use
|
|
16
|
+
npm install puppeteer pixelmatch pngjs
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
const { capturePage, crop, fromPuppeteerPage } = require('@afixt/screenshot-utils');
|
|
21
|
+
|
|
22
|
+
const adapter = fromPuppeteerPage(page);
|
|
23
|
+
const { bytes, page: metrics, timing } = await capturePage(adapter, { fullPage: true });
|
|
24
|
+
const thumbnail = await crop(bytes, { x: 0, y: 0, width: 400, height: 300 });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Adapter factories, capture functions, transforms, compose, tile, and redact
|
|
28
|
+
are all exported from the package root. See `spec.md` § 8 for the full API
|
|
29
|
+
and `docs/consumer-cookbook.md` for end-to-end recipes.
|
|
30
|
+
|
|
31
|
+
## Why this exists
|
|
32
|
+
|
|
33
|
+
Screenshot work was scattered across `afixt-engine`, `mortise`, and ad-hoc consumer scripts. Pulling it into one package gives us:
|
|
34
|
+
|
|
35
|
+
- One canonical implementation of long-page stitching, element capture, and DZI tiling.
|
|
36
|
+
- A browser-agnostic surface via the `PageAdapter` interface — `puppeteer`, `playwright`, and CDP all work.
|
|
37
|
+
- A stable error contract with named classes, stable `code`s, and `retriable` hints.
|
|
38
|
+
- Predictable, conservative defaults (PNG, lossless, full-page) that consumers can opt out of.
|
|
39
|
+
- No upward dependency on the engine — every other team can adopt it without a circular import.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
| Dependency | Status | Why you'd want it |
|
|
44
|
+
| ------------------- | ---------------------------------------------- | -------------------------------------- |
|
|
45
|
+
| `sharp` | Required runtime dep — installed automatically | All raster operations |
|
|
46
|
+
| `puppeteer` >=23 | Optional peer | `fromPuppeteerPage` adapter |
|
|
47
|
+
| `playwright` >=1.40 | Optional peer | `fromPlaywrightPage` adapter (Phase 2) |
|
|
48
|
+
| `pixelmatch` | Optional peer | `diff()` function (Phase 4) |
|
|
49
|
+
| `pngjs` | Optional peer | `pixelmatch` co-dependency |
|
|
50
|
+
| `@napi-rs/canvas` | Deferred to v1.1 | Advanced annotation strokes |
|
|
51
|
+
|
|
52
|
+
Node `>=22.22.1` is required (`engine-strict=true`).
|
|
53
|
+
|
|
54
|
+
## PageAdapter
|
|
55
|
+
|
|
56
|
+
The capture functions never import a browser. Consumers wrap their `Page`-like object:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
const { fromPuppeteerPage } = require('@afixt/screenshot-utils/adapters/puppeteer');
|
|
60
|
+
const adapter = fromPuppeteerPage(page);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Anything that satisfies the `PageAdapter` interface works — write your own for CDP-only runtimes. See `docs/adapters.md` (Phase 4) for the contract.
|
|
64
|
+
|
|
65
|
+
## Capture functions
|
|
66
|
+
|
|
67
|
+
- `capturePage(adapter, opts)` — full page or viewport
|
|
68
|
+
- `captureViewport(adapter, opts)` — just what's currently scrolled into view
|
|
69
|
+
- `captureElement(adapter, selector | rect, opts)` — single element (deprecated path; prefer `cropMany`)
|
|
70
|
+
- `captureLongPage(adapter, opts)` — strip-and-stitch for very tall pages
|
|
71
|
+
- `captureResponsive(adapter, viewports, opts)` — capture once per viewport
|
|
72
|
+
- `waitForStable(adapter, opts)` — fonts ready, images complete, animation policy honored
|
|
73
|
+
|
|
74
|
+
## Transform & compose
|
|
75
|
+
|
|
76
|
+
- `crop(buffer, rect, opts)` / `cropMany(buffer, rectsByKey, opts)` — extract one or many sub-images
|
|
77
|
+
- `convert(buffer, format, opts)` — re-encode
|
|
78
|
+
- `resize(buffer, opts)` — fit, contain, cover
|
|
79
|
+
- `normalize(buffer, opts)` — strip metadata, fix orientation
|
|
80
|
+
- `annotate(buffer, annotations, opts)` — boxes, labels, severities (Phase 4)
|
|
81
|
+
- `heatmap(buffer, points, opts)` — kernel-density overlay (Phase 4)
|
|
82
|
+
- `diff(a, b, opts)` — pixelmatch-backed visual diff (Phase 4)
|
|
83
|
+
|
|
84
|
+
## Tiling
|
|
85
|
+
|
|
86
|
+
- `tileDzi(buffer, outDir | 'buffers', opts)` — produce a Deep Zoom Image pyramid (Phase 2)
|
|
87
|
+
|
|
88
|
+
## Redaction
|
|
89
|
+
|
|
90
|
+
- `redact(buffer, regions, mode, opts)` — blur, mask, or pixelate PII regions (Phase 4)
|
|
91
|
+
- `applyPiiPolicy(buffer, policy)` — apply a policy bundle for repeatable redaction
|
|
92
|
+
|
|
93
|
+
## CLI (Phase 4)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx @afixt/screenshot-utils crop input.png --rect 0,0,400,300 --out thumb.png
|
|
97
|
+
npx @afixt/screenshot-utils tile input.png --out ./tiles
|
|
98
|
+
npx @afixt/screenshot-utils diff before.png after.png --out diff.png
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Performance tips
|
|
102
|
+
|
|
103
|
+
- Pass `encoding: 'stream'` for memory-sensitive pipelines.
|
|
104
|
+
- Use `cropMany` rather than calling `crop` in a loop — internally bounded parallelism is faster and uses less memory.
|
|
105
|
+
- For long pages, prefer `captureLongPage` over a giant viewport — sharp handles strips better than 30 000-px-tall buffers.
|
|
106
|
+
- See `docs/performance.md` (Phase 4) for ceilings.
|
|
107
|
+
|
|
108
|
+
## Errors
|
|
109
|
+
|
|
110
|
+
Every failure is a named class with a stable `code`, a structured `reason` enum, and a `retriable: boolean` hint. Hierarchy in `spec.md` § 14.1. Catalog in `docs/error-codes.md` (Phase 4).
|
|
111
|
+
|
|
112
|
+
## Versioning & non-goals
|
|
113
|
+
|
|
114
|
+
The `src/index.js` tagged exports are the **only** semver-protected API. Deep imports are unstable. v1.0.0 ships AVIF, `@napi-rs/canvas` annotations, and streaming DZI as v1.1 follow-ups.
|
|
115
|
+
|
|
116
|
+
This package is intentionally **not** responsible for:
|
|
117
|
+
|
|
118
|
+
- Storage, network transport, auth, retention, or encryption
|
|
119
|
+
- Accessibility-test logic (skip rules, severity assignment, etc.)
|
|
120
|
+
- Browser launching or pooling
|
|
121
|
+
- Mutable global state
|
|
122
|
+
|
|
123
|
+
See `spec.md` § 4.2 for the full non-goals list.
|
|
124
|
+
|
|
125
|
+
## Contributing
|
|
126
|
+
|
|
127
|
+
This is an internal AFixt package — issue tracker and PRs at [github.com/AFixt/screenshot-utils](https://github.com/AFixt/screenshot-utils).
|
|
128
|
+
|
|
129
|
+
- Branching: Git Flow. PRs target `develop`.
|
|
130
|
+
- Tests-first (TDD). 2–3 tests per batch; never commit failing tests.
|
|
131
|
+
- Conventional Commits enforced by `commitlint`.
|
|
132
|
+
- See `CLAUDE.md` for the rules every change must satisfy.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { main } = require('../src/cli/index.js');
|
|
5
|
+
|
|
6
|
+
main(process.argv.slice(2)).then(
|
|
7
|
+
code => {
|
|
8
|
+
process.exit(code);
|
|
9
|
+
},
|
|
10
|
+
error => {
|
|
11
|
+
process.stderr.write(`Fatal: ${error?.stack ?? error}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
);
|