styleproof 2.4.0 → 3.0.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 +58 -0
- package/bin/styleproof-diff.mjs +7 -48
- package/bin/styleproof-init.mjs +37 -3
- package/bin/styleproof-report.mjs +35 -5
- package/dist/gitref.d.ts +13 -0
- package/dist/gitref.js +44 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,64 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.0.0] - 2026-06-27
|
|
11
|
+
|
|
12
|
+
**Milestone: the committed-map gate is how StyleProof works now.** Capture runs
|
|
13
|
+
**pre-push** (parallel, with auto-detected `@media` breakpoints), the lean computed-
|
|
14
|
+
style map is committed and **pushed in a single `git push`**, and CI is a
|
|
15
|
+
**browser-less diff** of two precomputed maps — measured ~5400× cheaper on the
|
|
16
|
+
compare step, and it skips build + serve entirely. `styleproof-init` scaffolds **and
|
|
17
|
+
activates** the whole gate in one command. Coverage stays **full and sound**: every
|
|
18
|
+
surface is captured (parallelised) and what changed is determined by _measuring_ the
|
|
19
|
+
map, never by guessing which pages a code change touched. **No breaking API changes**
|
|
20
|
+
— existing specs and the classic capture-both-in-CI flow keep working unchanged; the
|
|
21
|
+
major marks the new default paradigm.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`styleproof-init` now activates the pre-push hook for you** (`git config
|
|
26
|
+
core.hooksPath .githooks`), so a single `styleproof-init` is all it takes — no
|
|
27
|
+
follow-up command. It never clobbers a repo that already manages hooks: if
|
|
28
|
+
`core.hooksPath` is already set or a `.husky/` dir exists, it leaves them alone and
|
|
29
|
+
prints the one-liner instead.
|
|
30
|
+
|
|
31
|
+
## [2.5.0] - 2026-06-26
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **`styleproof-init` scaffolds parallel surface capture (`fullyParallel: true`).**
|
|
36
|
+
StyleProof emits one test per surface × width, each an isolated page writing a
|
|
37
|
+
uniquely-keyed file — independent and safe to run concurrently. Without
|
|
38
|
+
`fullyParallel`, all surfaces sit in one spec file and capture **serially**; with it
|
|
39
|
+
they fan out across workers. Measured: 6 surfaces went from 12.3 s (1 worker) to
|
|
40
|
+
4.9 s (4 workers) — **~2.5× faster**, byte-identical output. Profiling confirmed the
|
|
41
|
+
single-capture path is correctness-bound (the settle is a deterministic wait; forced
|
|
42
|
+
states must be isolated per element), so parallelism across surfaces — not
|
|
43
|
+
micro-optimising one capture — is the real lever.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **Dogfood: StyleProof certifies its own demo page in CI.** A new
|
|
48
|
+
`example/demo/index.html` plus `test/dogfood.e2e.spec.ts` run the full
|
|
49
|
+
capture → detect → diff pipeline on a real multi-element page every CI run:
|
|
50
|
+
auto-detection finds the demo's `@media` breakpoints, two captures certify
|
|
51
|
+
identical (determinism), and a planted restyle is caught. `test:e2e` now runs the
|
|
52
|
+
whole `*.e2e.spec.ts` suite. (Repo-only; `example/` isn't in the published package.)
|
|
53
|
+
|
|
54
|
+
- **`npm run bench` — measures the committed-map gate's CI speedup.** A reproducible
|
|
55
|
+
benchmark of one in-browser capture vs one precomputed-map diff, projected to a
|
|
56
|
+
sample app. On the dev fixture: capture ≈ 1 s/surface-width, diff ≈ 0.4 ms — so the
|
|
57
|
+
per-PR compare step is ~1000s× cheaper, before counting the build + serve the
|
|
58
|
+
committed-map CI also skips. (Internal tooling; not shipped in the package.)
|
|
59
|
+
- **The Action and `styleproof-report` support `--base-ref`.** The committed-map gate
|
|
60
|
+
now gets the full review experience, not just a bare diff: pass `base-ref` to the
|
|
61
|
+
Action (e.g. the PR base branch) and point `fresh-dir` at your committed maps — it
|
|
62
|
+
reads the base from git and produces the same before/after report + approval gate,
|
|
63
|
+
with no recapture. `styleproof-report --base-ref <gitref> <mapsDir>` does the same
|
|
64
|
+
on the CLI. The git-materialisation is now a shared `materializeRef` (in `gitref`)
|
|
65
|
+
used by both `styleproof-diff` and `styleproof-report`. `baseline-dir` is no longer
|
|
66
|
+
required when `base-ref` is set.
|
|
67
|
+
|
|
10
68
|
## [2.4.0] - 2026-06-26
|
|
11
69
|
|
|
12
70
|
### Added
|
package/bin/styleproof-diff.mjs
CHANGED
|
@@ -26,10 +26,8 @@
|
|
|
26
26
|
* error, 3 = only new surfaces (present on one side, no baseline to diff against).
|
|
27
27
|
*/
|
|
28
28
|
import fs from 'node:fs';
|
|
29
|
-
import os from 'node:os';
|
|
30
|
-
import path from 'node:path';
|
|
31
|
-
import { spawnSync } from 'node:child_process';
|
|
32
29
|
import { diffStyleMapDirs, findingLabel } from '../dist/diff.js';
|
|
30
|
+
import { materializeRef, GitRefError } from '../dist/gitref.js';
|
|
33
31
|
|
|
34
32
|
const HELP = `styleproof-diff — certify a CSS refactor by diffing two computed-style captures
|
|
35
33
|
|
|
@@ -68,50 +66,6 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
68
66
|
} else args.push(argv[i]);
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
/** Run git, exiting 2 with a clean message on failure. */
|
|
72
|
-
function git(gitArgs, { encoding = 'buffer' } = {}) {
|
|
73
|
-
const r = spawnSync('git', gitArgs, { encoding, maxBuffer: 1 << 28 });
|
|
74
|
-
return r;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Materialise the captures committed at <dir> as of <ref> into a fresh temp dir,
|
|
79
|
-
* so the base of the diff is e.g. main's map — no checkout, no recapture. Reads
|
|
80
|
-
* only via git (no `tar`/deps); binary `.json.gz` files come through `git show`.
|
|
81
|
-
*/
|
|
82
|
-
function materializeRef(ref, dir) {
|
|
83
|
-
const top = git(['rev-parse', '--show-toplevel'], { encoding: 'utf8' });
|
|
84
|
-
if (top.status !== 0) {
|
|
85
|
-
console.error('styleproof-diff --base-ref must run inside a git repository');
|
|
86
|
-
process.exit(2);
|
|
87
|
-
}
|
|
88
|
-
const toplevel = top.stdout.trim();
|
|
89
|
-
// -z: NUL-separated, unquoted paths (handles spaces/unicode).
|
|
90
|
-
const ls = git(['ls-tree', '-r', '-z', '--name-only', ref, '--', dir], { encoding: 'utf8' });
|
|
91
|
-
if (ls.status !== 0) {
|
|
92
|
-
console.error(`cannot read ${dir} at ${ref}: ${(ls.stderr || '').toString().trim()}`);
|
|
93
|
-
process.exit(2);
|
|
94
|
-
}
|
|
95
|
-
const files = ls.stdout.split('\0').filter(Boolean);
|
|
96
|
-
if (files.length === 0) {
|
|
97
|
-
console.error(`no committed captures at ${dir} in ${ref} — commit the maps so the base lives in git`);
|
|
98
|
-
process.exit(2);
|
|
99
|
-
}
|
|
100
|
-
const dirRepoRel = path.relative(toplevel, path.resolve(dir));
|
|
101
|
-
const dest = fs.mkdtempSync(path.join(os.tmpdir(), 'styleproof-baseref-'));
|
|
102
|
-
for (const f of files) {
|
|
103
|
-
const show = git(['show', `${ref}:${f}`]); // buffer — preserves .json.gz bytes
|
|
104
|
-
if (show.status !== 0) {
|
|
105
|
-
console.error(`cannot read ${f} at ${ref}`);
|
|
106
|
-
process.exit(2);
|
|
107
|
-
}
|
|
108
|
-
const out = path.join(dest, path.relative(dirRepoRel, f));
|
|
109
|
-
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
110
|
-
fs.writeFileSync(out, show.stdout);
|
|
111
|
-
}
|
|
112
|
-
return dest;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
69
|
let dirA;
|
|
116
70
|
let dirB;
|
|
117
71
|
let tmpBase = null;
|
|
@@ -124,7 +78,12 @@ if (baseRef) {
|
|
|
124
78
|
console.error(`no capture at ${args[0]}`);
|
|
125
79
|
process.exit(2);
|
|
126
80
|
}
|
|
127
|
-
|
|
81
|
+
try {
|
|
82
|
+
tmpBase = materializeRef(baseRef, args[0]);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(e instanceof GitRefError ? `styleproof-diff --base-ref: ${e.message}` : String(e?.message ?? e));
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
128
87
|
dirA = tmpBase;
|
|
129
88
|
dirB = args[0];
|
|
130
89
|
} else {
|
package/bin/styleproof-init.mjs
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import fs from 'node:fs';
|
|
22
22
|
import path from 'node:path';
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
23
24
|
import { discoverNextRoutes } from '../dist/index.js';
|
|
24
25
|
|
|
25
26
|
const HELP = `styleproof-init — scaffold a styleproof capture spec
|
|
@@ -209,6 +210,14 @@ const CONFIG = `import { defineConfig, devices } from '@playwright/test';
|
|
|
209
210
|
// in-flight data either way, but a production build removes the variance at the source.)
|
|
210
211
|
export default defineConfig({
|
|
211
212
|
timeout: 120_000,
|
|
213
|
+
// Capture surfaces in PARALLEL. StyleProof generates one test per surface × width,
|
|
214
|
+
// each an isolated page writing a uniquely-keyed file (\`<key>@<width>.json.gz\`), with
|
|
215
|
+
// per-page record/replay and frozen clock — so they're independent and safe to run
|
|
216
|
+
// concurrently. Without this, all surfaces sit in one spec file and capture serially;
|
|
217
|
+
// with it they fan out across workers, a near-linear speedup on a multi-surface app.
|
|
218
|
+
// (\`--shard\` splits them across CI machines too; they write disjoint files into one
|
|
219
|
+
// dir.) Tune \`workers\` to your machine if needed.
|
|
220
|
+
fullyParallel: true,
|
|
212
221
|
use: {
|
|
213
222
|
baseURL: process.env.BASE_URL || '${baseUrl}',
|
|
214
223
|
},
|
|
@@ -230,7 +239,8 @@ export default defineConfig({
|
|
|
230
239
|
// always carries a base map and CI is a fast, browser-less diff of two precomputed
|
|
231
240
|
// maps. The hook redirects capture into a committed `stylemaps/` dir and drops
|
|
232
241
|
// screenshots via the STYLEPROOF_BASEDIR / STYLEPROOF_SCREENSHOTS env knobs.
|
|
233
|
-
const
|
|
242
|
+
const HOOK_DIR = '.githooks';
|
|
243
|
+
const HOOK_PATH = `${HOOK_DIR}/pre-push`;
|
|
234
244
|
const PREPUSH_HOOK = `#!/bin/sh
|
|
235
245
|
# StyleProof pre-push hook (generated by styleproof-init).
|
|
236
246
|
#
|
|
@@ -350,9 +360,33 @@ if (ci.wrote) {
|
|
|
350
360
|
console.log(`${CI_PATH} already exists — left untouched`);
|
|
351
361
|
}
|
|
352
362
|
|
|
363
|
+
// Activate the hook so ONE `styleproof-init` is all it takes — but never clobber a
|
|
364
|
+
// repo that already manages its hooks (husky, or an existing core.hooksPath); there
|
|
365
|
+
// we leave it and print the one-liner instead.
|
|
366
|
+
const git = (a) => spawnSync('git', a, { encoding: 'utf8' });
|
|
367
|
+
const inGitRepo = git(['rev-parse', '--is-inside-work-tree']).status === 0;
|
|
368
|
+
const existingHooksPath = inGitRepo ? git(['config', '--local', '--get', 'core.hooksPath']).stdout.trim() : '';
|
|
369
|
+
const managesHooks = existingHooksPath !== '' || fs.existsSync('.husky');
|
|
370
|
+
let hookActivated = false;
|
|
371
|
+
if (inGitRepo && !managesHooks) {
|
|
372
|
+
if (git(['config', 'core.hooksPath', HOOK_DIR]).status === 0) {
|
|
373
|
+
hookActivated = true;
|
|
374
|
+
console.log(`activated the pre-push hook (core.hooksPath → ${HOOK_DIR})`);
|
|
375
|
+
wroteSomething = true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
353
379
|
console.log('\nHow the gate works — capture local, CI just diffs:');
|
|
354
|
-
|
|
355
|
-
console.log('
|
|
380
|
+
if (hookActivated) {
|
|
381
|
+
console.log(' 1. ✓ Pre-push hook is active — nothing to do.');
|
|
382
|
+
} else if (managesHooks) {
|
|
383
|
+
console.log(` 1. Your repo already manages git hooks (${existingHooksPath || '.husky'}), so the hook`);
|
|
384
|
+
console.log(` wasn't auto-activated. Point your hook runner at ${HOOK_DIR}/pre-push, or run:`);
|
|
385
|
+
console.log(` git config core.hooksPath ${HOOK_DIR}`);
|
|
386
|
+
} else {
|
|
387
|
+
console.log(' 1. Activate the pre-push hook (once per clone):');
|
|
388
|
+
console.log(` git config core.hooksPath ${HOOK_DIR}`);
|
|
389
|
+
}
|
|
356
390
|
console.log(' 2. Push your branch. The hook captures the computed-style map against a');
|
|
357
391
|
console.log(' PRODUCTION build, commits it (lean .json.gz under stylemaps/), and pushes it');
|
|
358
392
|
console.log(' with your branch — one `git push`, never two.');
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
* need the .png screenshots that `defineStyleMapCapture` saves by default.
|
|
11
11
|
* Exit code 0 = no changes, 1 = report generated, 2 = usage error.
|
|
12
12
|
*/
|
|
13
|
+
import fs from 'node:fs';
|
|
13
14
|
import { generateStyleMapReport } from '../dist/report.js';
|
|
15
|
+
import { materializeRef, GitRefError } from '../dist/gitref.js';
|
|
14
16
|
|
|
15
17
|
const HELP = `styleproof-report — reviewable before/after report from two captures
|
|
16
18
|
|
|
17
19
|
usage: styleproof-report <beforeDir> <afterDir> --out <dir> [options]
|
|
20
|
+
styleproof-report --base-ref <gitref> <mapsDir> --out <dir> [options]
|
|
18
21
|
|
|
19
22
|
options:
|
|
23
|
+
--base-ref <ref> use <mapsDir> as committed at <ref> (e.g. main) as the
|
|
24
|
+
before side — base from git, no recapture
|
|
20
25
|
--out <dir> output directory (default: styleproof-report)
|
|
21
26
|
--image-base-url <url> prefix for image URLs in report.md (default: relative)
|
|
22
27
|
--pad <px> padding around changed rects when cropping (default: 24)
|
|
@@ -47,6 +52,7 @@ let minWidth;
|
|
|
47
52
|
let minHeight;
|
|
48
53
|
let includeLayoutNoise = false;
|
|
49
54
|
let includeContent = false;
|
|
55
|
+
let baseRef = null;
|
|
50
56
|
for (let i = 0; i < argv.length; i++) {
|
|
51
57
|
const a = argv[i];
|
|
52
58
|
if (a === '-h' || a === '--help') {
|
|
@@ -70,14 +76,36 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
70
76
|
else if (a.startsWith('--include-layout-noise=')) includeLayoutNoise = a.slice(23) !== 'false';
|
|
71
77
|
else if (a === '--include-content') includeContent = true;
|
|
72
78
|
else if (a.startsWith('--include-content=')) includeContent = a.slice(18) !== 'false';
|
|
79
|
+
else if (a === '--base-ref') baseRef = argv[++i];
|
|
80
|
+
else if (a.startsWith('--base-ref=')) baseRef = a.slice(11);
|
|
73
81
|
else if (a.startsWith('--')) {
|
|
74
82
|
console.error(`unknown flag: ${a}`);
|
|
75
83
|
process.exit(2);
|
|
76
84
|
} else args.push(a);
|
|
77
85
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
let beforeDir;
|
|
87
|
+
let afterDir;
|
|
88
|
+
let tmpBase = null;
|
|
89
|
+
if (baseRef) {
|
|
90
|
+
if (args.length !== 1) {
|
|
91
|
+
console.error('usage: styleproof-report --base-ref <gitref> <mapsDir> --out <dir> [options]');
|
|
92
|
+
process.exit(2);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
tmpBase = materializeRef(baseRef, args[0]);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error(e instanceof GitRefError ? `styleproof-report --base-ref: ${e.message}` : String(e?.message ?? e));
|
|
98
|
+
process.exit(2);
|
|
99
|
+
}
|
|
100
|
+
beforeDir = tmpBase;
|
|
101
|
+
afterDir = args[0];
|
|
102
|
+
} else {
|
|
103
|
+
if (args.length !== 2) {
|
|
104
|
+
console.error('usage: styleproof-report <beforeDir> <afterDir> --out <dir> [options] (--help for all options)');
|
|
105
|
+
process.exit(2);
|
|
106
|
+
}
|
|
107
|
+
beforeDir = args[0];
|
|
108
|
+
afterDir = args[1];
|
|
81
109
|
}
|
|
82
110
|
for (const [name, val] of [
|
|
83
111
|
['--pad', pad],
|
|
@@ -99,8 +127,8 @@ if (foldDetailsAt !== undefined && Number.isNaN(foldDetailsAt)) {
|
|
|
99
127
|
let result;
|
|
100
128
|
try {
|
|
101
129
|
result = generateStyleMapReport({
|
|
102
|
-
beforeDir
|
|
103
|
-
afterDir
|
|
130
|
+
beforeDir,
|
|
131
|
+
afterDir,
|
|
104
132
|
outDir: flags.out,
|
|
105
133
|
imageBaseUrl: flags.imageBaseUrl || undefined,
|
|
106
134
|
pad,
|
|
@@ -114,6 +142,8 @@ try {
|
|
|
114
142
|
} catch (e) {
|
|
115
143
|
console.error(e.message);
|
|
116
144
|
process.exit(2);
|
|
145
|
+
} finally {
|
|
146
|
+
if (tmpBase) fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
117
147
|
}
|
|
118
148
|
|
|
119
149
|
const newNote = result.newSurfaces ? ` (+${result.newSurfaces} new surface(s) with no baseline)` : '';
|
package/dist/gitref.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A readable failure materialising a base from git — CLIs map this to exit 2. */
|
|
2
|
+
export declare class GitRefError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Materialise the captures committed at `dir` as of `ref` into a fresh temp dir,
|
|
6
|
+
* so a diff/report can use e.g. `main`'s committed map as the base with no checkout
|
|
7
|
+
* and no recapture — the "base map lives on main, CI just diffs" model.
|
|
8
|
+
*
|
|
9
|
+
* Reads purely through git (`ls-tree`/`show`, no `tar`/deps); binary `.json.gz`
|
|
10
|
+
* bytes come straight from `git show`. The caller owns cleanup of the returned dir.
|
|
11
|
+
* Throws {@link GitRefError} (never exits) so it's reusable from any entry point.
|
|
12
|
+
*/
|
|
13
|
+
export declare function materializeRef(ref: string, dir: string): string;
|
package/dist/gitref.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
/** A readable failure materialising a base from git — CLIs map this to exit 2. */
|
|
6
|
+
export class GitRefError extends Error {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Materialise the captures committed at `dir` as of `ref` into a fresh temp dir,
|
|
10
|
+
* so a diff/report can use e.g. `main`'s committed map as the base with no checkout
|
|
11
|
+
* and no recapture — the "base map lives on main, CI just diffs" model.
|
|
12
|
+
*
|
|
13
|
+
* Reads purely through git (`ls-tree`/`show`, no `tar`/deps); binary `.json.gz`
|
|
14
|
+
* bytes come straight from `git show`. The caller owns cleanup of the returned dir.
|
|
15
|
+
* Throws {@link GitRefError} (never exits) so it's reusable from any entry point.
|
|
16
|
+
*/
|
|
17
|
+
export function materializeRef(ref, dir) {
|
|
18
|
+
const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8', maxBuffer: 1 << 28 });
|
|
19
|
+
if (top.status !== 0)
|
|
20
|
+
throw new GitRefError('must run inside a git repository');
|
|
21
|
+
const toplevel = top.stdout.trim();
|
|
22
|
+
// -z: NUL-separated, unquoted paths (handles spaces/unicode).
|
|
23
|
+
const ls = spawnSync('git', ['ls-tree', '-r', '-z', '--name-only', ref, '--', dir], {
|
|
24
|
+
encoding: 'utf8',
|
|
25
|
+
maxBuffer: 1 << 28,
|
|
26
|
+
});
|
|
27
|
+
if (ls.status !== 0)
|
|
28
|
+
throw new GitRefError(`cannot read ${dir} at ${ref}: ${(ls.stderr || '').trim()}`);
|
|
29
|
+
const files = ls.stdout.split('\0').filter(Boolean);
|
|
30
|
+
if (files.length === 0) {
|
|
31
|
+
throw new GitRefError(`no committed captures at ${dir} in ${ref} — commit the maps so the base lives in git`);
|
|
32
|
+
}
|
|
33
|
+
const dirRepoRel = path.relative(toplevel, path.resolve(dir));
|
|
34
|
+
const dest = fs.mkdtempSync(path.join(os.tmpdir(), 'styleproof-baseref-'));
|
|
35
|
+
for (const f of files) {
|
|
36
|
+
const show = spawnSync('git', ['show', `${ref}:${f}`], { maxBuffer: 1 << 28 }); // buffer — preserves .json.gz
|
|
37
|
+
if (show.status !== 0)
|
|
38
|
+
throw new GitRefError(`cannot read ${f} at ${ref}`);
|
|
39
|
+
const out = path.join(dest, path.relative(dirRepoRel, f));
|
|
40
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
41
|
+
fs.writeFileSync(out, show.stdout);
|
|
42
|
+
}
|
|
43
|
+
return dest;
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "styleproof",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Catch every CSS change before it ships — review PRs and certify refactors by the browser's computed styles, not pixels. Works with any styling system.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"playwright",
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"format:check": "prettier --check \"{src,bin,test,example}/**/*.{ts,mjs,js}\" \"*.{ts,js,mjs,json,md}\"",
|
|
62
62
|
"test": "npm run build && node --test test/*.test.mjs",
|
|
63
63
|
"test:watch": "node --test --watch test/*.test.mjs",
|
|
64
|
-
"test:e2e": "npm run build && playwright test
|
|
64
|
+
"test:e2e": "npm run build && playwright test",
|
|
65
|
+
"bench": "npm run build && node bench/gate-speed.mjs",
|
|
65
66
|
"init": "node ./bin/styleproof-init.mjs",
|
|
66
67
|
"prepare": "npm run build && husky",
|
|
67
68
|
"prepublishOnly": "npm run clean && npm run build && npm run typecheck"
|