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/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';
@@ -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
- export declare function workingTreeDirty(cwd?: string): boolean;
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
- export function workingTreeDirty(cwd = process.cwd()) {
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
- return file && !GENERATED_DIRTY_ALLOWLIST.has(file);
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
- export function writeMapManifest(options) {
170
- const cwd = options.cwd ?? process.cwd();
171
- const input = compatibilityInput({ cwd, spec: options.spec, baseUrl: options.env?.BASE_URL ?? process.env.BASE_URL });
172
- const browserVersion = readBrowserBuildSidecar(options.dir);
173
- const manifest = {
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 ?? currentGitSha(cwd, options.env),
177
- dirty: options.dirty ?? workingTreeDirty(cwd),
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(options.dir),
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;