styleproof 3.20.0 → 4.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 +192 -0
- package/README.md +437 -321
- package/bin/styleproof-capture.mjs +4 -0
- package/bin/styleproof-diff.mjs +192 -27
- package/bin/styleproof-init.mjs +75 -7
- package/bin/styleproof-map.mjs +23 -9
- package/bin/styleproof-report.mjs +6 -0
- package/dist/capture-url.js +5 -0
- package/dist/capture.d.ts +59 -0
- package/dist/capture.js +127 -5
- package/dist/change-groups.d.ts +91 -0
- package/dist/change-groups.js +471 -0
- package/dist/coverage.d.ts +7 -0
- package/dist/data-residue.d.ts +70 -0
- package/dist/data-residue.js +105 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/map-store.d.ts +37 -1
- package/dist/map-store.js +103 -10
- package/dist/report.d.ts +1 -4
- package/dist/report.js +100 -353
- package/dist/runner.d.ts +20 -0
- package/dist/runner.js +63 -9
- package/example/styleproof-approve.yml +108 -0
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { captureStyleMap, saveStyleMap, loadStyleMap, trackInflightRequests } from './capture.js';
|
|
1
|
+
export { captureStyleMap, saveStyleMap, loadStyleMap, trackInflightRequests, trackDataResidue, urlMatcher, } from './capture.js';
|
|
2
2
|
export * from './inventory.js';
|
|
3
|
+
export * from './data-residue.js';
|
|
3
4
|
export { captureUrlToDir, runCaptureUrl, parseCaptureUrlArgs, UsageError } from './capture-url.js';
|
|
4
5
|
export { crawlAndCapture, CRAWL_DEFAULTS } from './crawl-surfaces.js';
|
|
5
6
|
export { loadSetupSteps } from './capture-url.js';
|
package/dist/map-store.d.ts
CHANGED
|
@@ -61,7 +61,12 @@ export declare function expectedCompatibilityKey(options?: {
|
|
|
61
61
|
}): string;
|
|
62
62
|
export declare function currentGitSha(cwd?: string, env?: NodeJS.ProcessEnv): string;
|
|
63
63
|
export declare function refSha(ref: string, cwd?: string): string;
|
|
64
|
-
|
|
64
|
+
/**
|
|
65
|
+
* True if any tracked file is modified/added/deleted. `ignorePrefix` (a repo-relative
|
|
66
|
+
* directory) is excluded — pass the map OUTPUT dir when re-sampling AFTER a capture, so
|
|
67
|
+
* the maps the capture just wrote don't read as tree dirt and mask a real source edit.
|
|
68
|
+
*/
|
|
69
|
+
export declare function workingTreeDirty(cwd?: string, ignorePrefix?: string): boolean;
|
|
65
70
|
export declare function remoteExists(remote?: string, cwd?: string): boolean;
|
|
66
71
|
export declare function writeMapManifest(options: {
|
|
67
72
|
dir: string;
|
|
@@ -72,7 +77,38 @@ export declare function writeMapManifest(options: {
|
|
|
72
77
|
cwd?: string;
|
|
73
78
|
env?: NodeJS.ProcessEnv;
|
|
74
79
|
}): MapManifest;
|
|
80
|
+
/**
|
|
81
|
+
* Write a `styleproof-manifest.json` for a one-shot `styleproof-capture` output dir,
|
|
82
|
+
* so a two-directory `styleproof-diff design <build>` has the same-environment guard
|
|
83
|
+
* on both sides (v4 refuses to compare a manifest-less side). Unlike
|
|
84
|
+
* {@link writeMapManifest}, this may run OUTSIDE a git repo (a design mockup, a static
|
|
85
|
+
* export), so the git-derived fields degrade gracefully: `sha` falls back to
|
|
86
|
+
* `'uncommitted'` and `dirty` to `true` rather than throwing. The parts the guard
|
|
87
|
+
* actually consumes — `compatibilityKey`, `platform`/`arch`/`nodeMajor`,
|
|
88
|
+
* `playwrightVersion`, `browserVersion`, `baseUrl` — are recorded the same way as a
|
|
89
|
+
* spec capture. Overwrites any existing manifest in `dir`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function writeCaptureManifest(options: {
|
|
92
|
+
dir: string;
|
|
93
|
+
screenshots: boolean;
|
|
94
|
+
cwd?: string;
|
|
95
|
+
env?: NodeJS.ProcessEnv;
|
|
96
|
+
}): MapManifest;
|
|
75
97
|
export declare function readMapManifest(dir: string): MapManifest | null;
|
|
98
|
+
/** Which side(s) of a two-directory compare hold captured maps but NO
|
|
99
|
+
* `styleproof-manifest.json` — a legacy committed-map bundle. Since v4 that is
|
|
100
|
+
* unsupported: without a manifest the same-environment guard can't be enforced, so the
|
|
101
|
+
* CLI refuses (exit 2). `null` means every side WITH maps also has a manifest (nothing to
|
|
102
|
+
* refuse). A side with zero maps is NOT flagged — an empty/bare dir is "no baseline yet",
|
|
103
|
+
* handled by the base/head-missing guards, not this one. Pure: presence reads only, so the
|
|
104
|
+
* CLI layer owns the exit code and the library stays side-effect-free. */
|
|
105
|
+
export declare function manifestlessSide(beforeDir: string, afterDir: string): 'before' | 'after' | 'both' | null;
|
|
106
|
+
/** Fail-loud message for a manifest-less compare. Since v4 a side without a
|
|
107
|
+
* `styleproof-manifest.json` is unsupported: the same-environment guard can't be
|
|
108
|
+
* enforced, so captures from different browser builds or platforms would diff as
|
|
109
|
+
* false changes. The CLI raises this and exits 2 (usage/capture error) — the
|
|
110
|
+
* legacy "compare anyway" tolerance is gone. */
|
|
111
|
+
export declare function manifestlessError(side: 'before' | 'after' | 'both'): string;
|
|
76
112
|
export declare function assertCompatibleMapDirs(beforeDir: string, afterDir: string): void;
|
|
77
113
|
export declare function publishMapBundle(options: {
|
|
78
114
|
dir: string;
|
package/dist/map-store.js
CHANGED
|
@@ -153,28 +153,40 @@ export function refSha(ref, cwd = process.cwd()) {
|
|
|
153
153
|
throw new MapStoreError(`could not resolve ${ref} to a commit`);
|
|
154
154
|
return sha;
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
/**
|
|
157
|
+
* True if any tracked file is modified/added/deleted. `ignorePrefix` (a repo-relative
|
|
158
|
+
* directory) is excluded — pass the map OUTPUT dir when re-sampling AFTER a capture, so
|
|
159
|
+
* the maps the capture just wrote don't read as tree dirt and mask a real source edit.
|
|
160
|
+
*/
|
|
161
|
+
export function workingTreeDirty(cwd = process.cwd(), ignorePrefix) {
|
|
157
162
|
const r = runGit(cwd, ['status', '--porcelain']);
|
|
158
163
|
const status = r.status === 0 ? r.stdout.trimEnd() : '';
|
|
159
164
|
if (!status)
|
|
160
165
|
return false;
|
|
166
|
+
const prefix = ignorePrefix ? `${ignorePrefix.replace(/\/+$/, '')}/` : undefined;
|
|
161
167
|
return status.split(/\r?\n/).some((line) => {
|
|
162
168
|
const file = line.slice(3).trim();
|
|
163
|
-
|
|
169
|
+
if (!file || GENERATED_DIRTY_ALLOWLIST.has(file))
|
|
170
|
+
return false;
|
|
171
|
+
if (prefix && (file === prefix.slice(0, -1) || file.startsWith(prefix)))
|
|
172
|
+
return false;
|
|
173
|
+
return true;
|
|
164
174
|
});
|
|
165
175
|
}
|
|
166
176
|
export function remoteExists(remote = DEFAULT_REMOTE, cwd = process.cwd()) {
|
|
167
177
|
return runGit(cwd, ['remote', 'get-url', remote], 1 << 20).status === 0;
|
|
168
178
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
179
|
+
/** Assemble a {@link MapManifest} from the compatibility inputs and the caller-resolved
|
|
180
|
+
* git fields. Shared by {@link writeMapManifest} (spec capture) and
|
|
181
|
+
* {@link writeCaptureManifest} (one-shot capture) so the object shape lives in one place. */
|
|
182
|
+
function buildManifest(options) {
|
|
183
|
+
const { dir, input } = options;
|
|
184
|
+
const browserVersion = readBrowserBuildSidecar(dir);
|
|
185
|
+
return {
|
|
174
186
|
version: 1,
|
|
175
187
|
packageVersion: input.packageVersion,
|
|
176
|
-
sha: options.sha
|
|
177
|
-
dirty: options.dirty
|
|
188
|
+
sha: options.sha,
|
|
189
|
+
dirty: options.dirty,
|
|
178
190
|
spec: input.spec,
|
|
179
191
|
specHash: input.specHash,
|
|
180
192
|
...(input.lockfile ? { lockfile: input.lockfile } : {}),
|
|
@@ -186,10 +198,49 @@ export function writeMapManifest(options) {
|
|
|
186
198
|
nodeMajor: input.nodeMajor,
|
|
187
199
|
...(input.baseUrl ? { baseUrl: input.baseUrl } : {}),
|
|
188
200
|
screenshots: options.screenshots,
|
|
189
|
-
har: hasHar(
|
|
201
|
+
har: hasHar(dir),
|
|
190
202
|
compatibilityKey: hash(JSON.stringify(input)).slice(0, 16),
|
|
191
203
|
createdAt: new Date().toISOString(),
|
|
192
204
|
};
|
|
205
|
+
}
|
|
206
|
+
export function writeMapManifest(options) {
|
|
207
|
+
const cwd = options.cwd ?? process.cwd();
|
|
208
|
+
const input = compatibilityInput({ cwd, spec: options.spec, baseUrl: options.env?.BASE_URL ?? process.env.BASE_URL });
|
|
209
|
+
const manifest = buildManifest({
|
|
210
|
+
dir: options.dir,
|
|
211
|
+
input,
|
|
212
|
+
sha: options.sha ?? currentGitSha(cwd, options.env),
|
|
213
|
+
dirty: options.dirty ?? workingTreeDirty(cwd),
|
|
214
|
+
screenshots: options.screenshots,
|
|
215
|
+
});
|
|
216
|
+
fs.writeFileSync(path.join(options.dir, MAP_MANIFEST), JSON.stringify(manifest, null, 2));
|
|
217
|
+
return manifest;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Write a `styleproof-manifest.json` for a one-shot `styleproof-capture` output dir,
|
|
221
|
+
* so a two-directory `styleproof-diff design <build>` has the same-environment guard
|
|
222
|
+
* on both sides (v4 refuses to compare a manifest-less side). Unlike
|
|
223
|
+
* {@link writeMapManifest}, this may run OUTSIDE a git repo (a design mockup, a static
|
|
224
|
+
* export), so the git-derived fields degrade gracefully: `sha` falls back to
|
|
225
|
+
* `'uncommitted'` and `dirty` to `true` rather than throwing. The parts the guard
|
|
226
|
+
* actually consumes — `compatibilityKey`, `platform`/`arch`/`nodeMajor`,
|
|
227
|
+
* `playwrightVersion`, `browserVersion`, `baseUrl` — are recorded the same way as a
|
|
228
|
+
* spec capture. Overwrites any existing manifest in `dir`.
|
|
229
|
+
*/
|
|
230
|
+
export function writeCaptureManifest(options) {
|
|
231
|
+
const cwd = options.cwd ?? process.cwd();
|
|
232
|
+
// A one-shot capture has no spec file; key comparability off the capture inputs only.
|
|
233
|
+
const input = compatibilityInput({ cwd, spec: MAP_MANIFEST, baseUrl: options.env?.BASE_URL ?? process.env.BASE_URL });
|
|
234
|
+
// Degrade the git fields gracefully outside a repo (a design mockup): no HEAD → uncommitted/dirty.
|
|
235
|
+
const sha = gitOutput(cwd, ['rev-parse', 'HEAD']) || 'uncommitted';
|
|
236
|
+
const manifest = buildManifest({
|
|
237
|
+
dir: options.dir,
|
|
238
|
+
input,
|
|
239
|
+
sha,
|
|
240
|
+
dirty: sha === 'uncommitted' ? true : workingTreeDirty(cwd),
|
|
241
|
+
screenshots: options.screenshots,
|
|
242
|
+
});
|
|
243
|
+
fs.mkdirSync(options.dir, { recursive: true });
|
|
193
244
|
fs.writeFileSync(path.join(options.dir, MAP_MANIFEST), JSON.stringify(manifest, null, 2));
|
|
194
245
|
return manifest;
|
|
195
246
|
}
|
|
@@ -201,6 +252,48 @@ export function readMapManifest(dir) {
|
|
|
201
252
|
return null;
|
|
202
253
|
}
|
|
203
254
|
}
|
|
255
|
+
/** True if `dir` holds at least one captured surface map (`<key>@<width>.json[.gz]`),
|
|
256
|
+
* ignoring metadata sidecars. Distinguishes a real capture that merely lacks a manifest
|
|
257
|
+
* (a legacy committed-map bundle — v4 refuses it) from an empty/bare dir (no baseline
|
|
258
|
+
* yet — the missing-map guards own that). */
|
|
259
|
+
function dirHasMaps(dir) {
|
|
260
|
+
try {
|
|
261
|
+
return fs.readdirSync(dir).some(isMapFile);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** Which side(s) of a two-directory compare hold captured maps but NO
|
|
268
|
+
* `styleproof-manifest.json` — a legacy committed-map bundle. Since v4 that is
|
|
269
|
+
* unsupported: without a manifest the same-environment guard can't be enforced, so the
|
|
270
|
+
* CLI refuses (exit 2). `null` means every side WITH maps also has a manifest (nothing to
|
|
271
|
+
* refuse). A side with zero maps is NOT flagged — an empty/bare dir is "no baseline yet",
|
|
272
|
+
* handled by the base/head-missing guards, not this one. Pure: presence reads only, so the
|
|
273
|
+
* CLI layer owns the exit code and the library stays side-effect-free. */
|
|
274
|
+
export function manifestlessSide(beforeDir, afterDir) {
|
|
275
|
+
const before = dirHasMaps(beforeDir) && readMapManifest(beforeDir) == null;
|
|
276
|
+
const after = dirHasMaps(afterDir) && readMapManifest(afterDir) == null;
|
|
277
|
+
if (before && after)
|
|
278
|
+
return 'both';
|
|
279
|
+
if (before)
|
|
280
|
+
return 'before';
|
|
281
|
+
if (after)
|
|
282
|
+
return 'after';
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
/** Fail-loud message for a manifest-less compare. Since v4 a side without a
|
|
286
|
+
* `styleproof-manifest.json` is unsupported: the same-environment guard can't be
|
|
287
|
+
* enforced, so captures from different browser builds or platforms would diff as
|
|
288
|
+
* false changes. The CLI raises this and exits 2 (usage/capture error) — the
|
|
289
|
+
* legacy "compare anyway" tolerance is gone. */
|
|
290
|
+
export function manifestlessError(side) {
|
|
291
|
+
const carry = side === 'both' ? 'before and after carry' : `${side} carries`;
|
|
292
|
+
return (`styleproof: ${carry} no ${MAP_MANIFEST} — environment compatibility can't be verified, so ` +
|
|
293
|
+
'captures from different browser builds or platforms would diff as false changes. ' +
|
|
294
|
+
'Re-capture with current StyleProof (styleproof-map, or styleproof-capture for a one-shot ' +
|
|
295
|
+
'diff); maps without a manifest are unsupported since v4.');
|
|
296
|
+
}
|
|
204
297
|
export function assertCompatibleMapDirs(beforeDir, afterDir) {
|
|
205
298
|
const before = readMapManifest(beforeDir);
|
|
206
299
|
const after = readMapManifest(afterDir);
|
package/dist/report.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type PropChange } from './diff.js';
|
|
2
1
|
export { describeChange, colorName, tokenIndex, toHex } from './describe.js';
|
|
2
|
+
export { summarizeProps, prettyLabel } from './change-groups.js';
|
|
3
3
|
/**
|
|
4
4
|
* Visual diff report: for every surface with findings, crop the before/after
|
|
5
5
|
* full-page screenshots around the changed elements and write a markdown
|
|
@@ -77,8 +77,5 @@ export type ReportResult = {
|
|
|
77
77
|
reportMdPath: string;
|
|
78
78
|
reportJsonPath: string;
|
|
79
79
|
};
|
|
80
|
-
export declare function summarizeProps(props: PropChange[]): PropChange[];
|
|
81
|
-
/** `div.who-grid`, `a.nav-cta`, `h3` — the semantic marker class, else the tag. */
|
|
82
|
-
export declare function prettyLabel(p: string, cls: string): string;
|
|
83
80
|
export declare function excerptPair(before: string, after: string): [string, string];
|
|
84
81
|
export declare function generateStyleMapReport(opts: ReportOptions): ReportResult;
|