@domandigital/craft 0.11.0 → 0.13.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.
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  snapshotUrl,
3
3
  snapshotUrls
4
- } from "./chunk-JP75P2DU.js";
4
+ } from "./chunk-ZLDA2SYB.js";
5
5
  import {
6
6
  describeTypicality,
7
- loadNull,
8
- typicality
9
- } from "./chunk-3OZQ4C5R.js";
7
+ loadNull
8
+ } from "./chunk-ESCVTKPN.js";
9
+ import {
10
+ readSnapshot
11
+ } from "./chunk-CZDOMIVS.js";
10
12
  import {
11
13
  SOURCE_FILE,
12
14
  finish,
@@ -16,15 +18,14 @@ import {
16
18
  prepareReport,
17
19
  readPaths,
18
20
  toJson
19
- } from "./chunk-2AJB27BJ.js";
20
- import {
21
- fingerprint
22
- } from "./chunk-QMYERCBB.js";
21
+ } from "./chunk-3VTPI7BU.js";
23
22
  import {
24
- SNAPSHOT_VERSION,
25
23
  auditSnapshot,
26
- scanSource
27
- } from "./chunk-3NGWXZZE.js";
24
+ componentTypicalities,
25
+ fingerprint,
26
+ scanSource,
27
+ typicality
28
+ } from "./chunk-GMVXADJ6.js";
28
29
 
29
30
  // src/audit/cli.ts
30
31
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -49,6 +50,12 @@ function parseViewports(value) {
49
50
  }
50
51
 
51
52
  // src/audit/cli.ts
53
+ function describeComponents(components, pageMeasured, nullMeasured) {
54
+ if (!pageMeasured) return " components not measured: the page's snapshot predates version 2";
55
+ if (!nullMeasured) return " components not measured: the null model predates snapshot version 2; rebuild it to compare components";
56
+ if (components.length === 0) return " components none the null model has enough pages of to compare";
57
+ return components.map((c, i) => `${i === 0 ? " components" : " "} ${c.role.padEnd(13)} ${c.score.toFixed(2)}: ${c.typical ? "typical" : "not typical"} (${c.runs} null pages have one)`).join("\n");
58
+ }
52
59
  var fmt = (o) => o ? `oklch(${o.l.toFixed(3)} ${o.c.toFixed(3)} ${o.h.toFixed(1)})` : "none";
53
60
  function describeFingerprint(fp) {
54
61
  return [
@@ -81,10 +88,8 @@ function merge(a, b) {
81
88
  }
82
89
  };
83
90
  }
84
- function readSnapshot(path) {
85
- const data = JSON.parse(readFileSync(path, "utf8"));
86
- if (data.version !== SNAPSHOT_VERSION) throw new Error(`${path} is snapshot version ${String(data.version)}; this craft reads ${SNAPSHOT_VERSION}`);
87
- return data;
91
+ function readSnapshotFile(path) {
92
+ return readSnapshot(JSON.parse(readFileSync(path, "utf8")), path);
88
93
  }
89
94
  async function readText(source, cwd) {
90
95
  if (/^https?:\/\//i.test(source)) {
@@ -123,7 +128,7 @@ async function runAudit(command, args, io) {
123
128
  const failed = [];
124
129
  const local = target ? resolve(io.cwd, target) : "";
125
130
  if (target && command === "audit" && /\.json$/i.test(target) && existsSync(local)) {
126
- audited.push({ url: target, viewport: null, snapshot: readSnapshot(local) });
131
+ audited.push({ url: target, viewport: null, snapshot: readSnapshotFile(local) });
127
132
  } else if (target && viewports.length === 1) {
128
133
  const snapshot = await snapshotUrl(target, { viewport: viewports[0] });
129
134
  audited.push({ url: target, viewport: viewports[0] ?? null, snapshot });
@@ -167,11 +172,17 @@ ${failed.map((f) => ` ${f.url}: ${f.error}`).join("\n")}`);
167
172
  const pooled = nulls.flatMap((m, i) => m.runs.map((r) => nulls.length > 1 ? { ...r, id: `${i + 1}/${r.id}` } : r));
168
173
  const pages = audited.map((a) => {
169
174
  const fp = fingerprint(a.snapshot);
170
- return { url: a.snapshot.url, viewport: a.viewport, fingerprint: fp, typicality: nulls.length ? typicality(fp, pooled) : null };
175
+ return {
176
+ url: a.snapshot.url,
177
+ viewport: a.viewport,
178
+ fingerprint: fp,
179
+ typicality: nulls.length ? typicality(fp, pooled) : null,
180
+ components: nulls.length ? componentTypicalities(fp, pooled) : []
181
+ };
171
182
  });
172
183
  const unmeasured = failed.length > 0 && flags.strict ? 1 : 0;
173
184
  if (flags.json) {
174
- const extra = single ? { fingerprint: pages[0].fingerprint, typicality: pages[0].typicality, snapshot: flags.out ?? null } : {};
185
+ const extra = single ? { fingerprint: pages[0].fingerprint, typicality: pages[0].typicality, components: pages[0].components, snapshot: flags.out ?? null } : {};
175
186
  io.out(toJson({ report: prepared, ...extra, pages, failed }));
176
187
  return Math.max(prepared.summary.blocking > 0 || flags.strict && prepared.summary.findings > 0 ? 1 : 0, unmeasured);
177
188
  }
@@ -184,6 +195,7 @@ ${failed.map((f) => ` ${f.url}: ${f.error}`).join("\n")}`);
184
195
  if (page.typicality) {
185
196
  io.out("");
186
197
  io.out(describeTypicality(page.typicality, pooled.length, nulls.length));
198
+ io.out(describeComponents(page.components, page.fingerprint.sections !== void 0, pooled.some((r) => r.fingerprint.sections !== void 0)));
187
199
  }
188
200
  }
189
201
  if (failed.length) {
@@ -199,6 +211,7 @@ ${failed.map((f) => ` ${f.url}: ${f.error}`).join("\n")}`);
199
211
  }
200
212
 
201
213
  export {
214
+ describeComponents,
202
215
  describeFingerprint,
203
216
  merge,
204
217
  resolvePages,
@@ -0,0 +1,75 @@
1
+ // src/direction/png.ts
2
+ import { inflateSync } from "zlib";
3
+ function decodePng(buf) {
4
+ const sig = [137, 80, 78, 71, 13, 10, 26, 10];
5
+ if (!sig.every((b, i) => buf[i] === b)) throw new Error("not a PNG");
6
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
7
+ let pos = 8;
8
+ let width = 0;
9
+ let height = 0;
10
+ let depth = 0;
11
+ let colour = 0;
12
+ let interlace = 0;
13
+ const idat = [];
14
+ while (pos < buf.length) {
15
+ const len = view.getUint32(pos);
16
+ const type = String.fromCharCode(...buf.subarray(pos + 4, pos + 8));
17
+ const data = buf.subarray(pos + 8, pos + 8 + len);
18
+ if (type === "IHDR") {
19
+ width = view.getUint32(pos + 8);
20
+ height = view.getUint32(pos + 12);
21
+ depth = data[8];
22
+ colour = data[9];
23
+ interlace = data[12];
24
+ } else if (type === "IDAT") idat.push(data);
25
+ else if (type === "IEND") break;
26
+ pos += 12 + len;
27
+ }
28
+ const channels = { 0: 1, 2: 3, 4: 2, 6: 4 }[colour];
29
+ if (depth !== 8 || !channels || interlace !== 0) throw new Error(`unsupported PNG (depth ${depth}, colour type ${colour}${interlace ? ", interlaced" : ""}); export it as 8-bit RGB`);
30
+ const raw = inflateSync(Buffer.concat(idat.map((d) => Buffer.from(d))));
31
+ const stride = width * channels;
32
+ const out = new Uint8Array(width * height * 4);
33
+ const prev = new Uint8Array(stride);
34
+ const line = new Uint8Array(stride);
35
+ for (let y = 0; y < height; y += 1) {
36
+ const filter = raw[y * (stride + 1)];
37
+ const src = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1));
38
+ for (let x = 0; x < stride; x += 1) {
39
+ const a = x >= channels ? line[x - channels] : 0;
40
+ const b = prev[x];
41
+ const c = x >= channels ? prev[x - channels] : 0;
42
+ let v = src[x];
43
+ if (filter === 1) v += a;
44
+ else if (filter === 2) v += b;
45
+ else if (filter === 3) v += a + b >> 1;
46
+ else if (filter === 4) {
47
+ const p = a + b - c;
48
+ const pa = Math.abs(p - a);
49
+ const pb = Math.abs(p - b);
50
+ const pc = Math.abs(p - c);
51
+ v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
52
+ }
53
+ line[x] = v & 255;
54
+ }
55
+ for (let x = 0; x < width; x += 1) {
56
+ const i = x * channels;
57
+ const o = (y * width + x) * 4;
58
+ if (channels >= 3) {
59
+ out[o] = line[i];
60
+ out[o + 1] = line[i + 1];
61
+ out[o + 2] = line[i + 2];
62
+ out[o + 3] = channels === 4 ? line[i + 3] : 255;
63
+ } else {
64
+ out[o] = out[o + 1] = out[o + 2] = line[i];
65
+ out[o + 3] = channels === 2 ? line[i + 1] : 255;
66
+ }
67
+ }
68
+ prev.set(line);
69
+ }
70
+ return { width, height, rgba: out };
71
+ }
72
+
73
+ export {
74
+ decodePng
75
+ };
@@ -1,21 +1,24 @@
1
1
  import {
2
- groundName,
3
- hueName,
4
- loadNull,
5
- shapeName
6
- } from "./chunk-3OZQ4C5R.js";
2
+ loadNull
3
+ } from "./chunk-ESCVTKPN.js";
4
+ import {
5
+ readSnapshot
6
+ } from "./chunk-CZDOMIVS.js";
7
7
  import {
8
8
  parseFlags,
9
9
  toJson
10
- } from "./chunk-2AJB27BJ.js";
10
+ } from "./chunk-3VTPI7BU.js";
11
11
  import {
12
+ COMPONENT_ROLES,
13
+ auditSnapshot,
14
+ closestComponents,
15
+ estateComponentPairs,
12
16
  fingerprint,
13
- fingerprintDistance
14
- } from "./chunk-QMYERCBB.js";
15
- import {
16
- SNAPSHOT_VERSION,
17
- auditSnapshot
18
- } from "./chunk-3NGWXZZE.js";
17
+ fingerprintDistance,
18
+ groundName,
19
+ hueName,
20
+ shapeName
21
+ } from "./chunk-GMVXADJ6.js";
19
22
 
20
23
  // src/estate/cli.ts
21
24
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -78,7 +81,7 @@ function siblingLine(briefs) {
78
81
  }
79
82
 
80
83
  // src/estate/cli.ts
81
- var USAGE = "usage: craft estate add <url | snapshot.json> --id <id> [--client <name>] | craft estate compare [<url | snapshot.json | id>] [--null <dir>] [--json] [--strict]";
84
+ var USAGE = "usage: craft estate add <url | snapshot.json> --id <id> [--client <name>] | craft estate compare [<url | snapshot.json | id>] [--null <dir>] [--component <role>] [--json] [--strict]";
82
85
  function loadEstate(path) {
83
86
  if (!existsSync(path)) return emptyEstate();
84
87
  const data = JSON.parse(readFileSync(path, "utf8"));
@@ -88,12 +91,11 @@ function loadEstate(path) {
88
91
  async function snapshotOf(target, cwd) {
89
92
  const local = resolve(cwd, target);
90
93
  if (/\.json$/i.test(target) && existsSync(local)) {
91
- const snap = JSON.parse(readFileSync(local, "utf8"));
92
- if (snap.version !== SNAPSHOT_VERSION) throw new Error(`${target} is not a snapshot`);
94
+ const snap = readSnapshot(JSON.parse(readFileSync(local, "utf8")), target);
93
95
  return snap;
94
96
  }
95
97
  if (!/^https?:\/\//.test(target)) throw new Error(`${target} is neither a URL, a snapshot file, nor a site in the register`);
96
- const { snapshotUrl } = await import("./audit-QIPXL7E4.js");
98
+ const { snapshotUrl } = await import("./audit-5TYDYRXV.js");
97
99
  return snapshotUrl(target);
98
100
  }
99
101
  var line = (distance, sibling, shared) => `${distance.toFixed(2)}${sibling ? " SIBLING" : ""}${shared.length ? ` shares ${shared.join(", ")}` : ""}`;
@@ -109,6 +111,21 @@ function formatPairs(pairs, at, sites) {
109
111
  for (const p of pairs) lines.push(` ${`${p.a} + ${p.b}`.padEnd(34)} ${line(p.distance, p.sibling, p.shared)}`);
110
112
  return lines.join("\n");
111
113
  }
114
+ function componentRole(name) {
115
+ if (!COMPONENT_ROLES.includes(name)) throw new Error(`--component takes one of ${COMPONENT_ROLES.join(", ")}`);
116
+ return name;
117
+ }
118
+ var withRole = (register, role) => register.sites.filter((s) => (s.fingerprint.sections ?? []).some((x) => x.role === role)).length;
119
+ function formatComponentPairs(role, pairs, measured, sites) {
120
+ const lines = [`craft estate: ${role} components, ${measured} of ${sites} sites have one, closest pairs first (no sibling line yet: ranked, not judged)`];
121
+ for (const p of pairs) lines.push(` ${`${p.a} + ${p.b}`.padEnd(34)} ${p.distance.toFixed(2)}${p.shared.length ? ` shares ${p.shared.join(", ")}` : ""}`);
122
+ if (pairs.length === 0) {
123
+ lines.push(
124
+ measured < 2 ? ` fewer than two sites have a ${role} section measured. Roles come from snapshot version 2: re-add the sites from a fresh snapshot.` : " no pairs"
125
+ );
126
+ }
127
+ return lines.join("\n");
128
+ }
112
129
  async function runEstate(args, io) {
113
130
  try {
114
131
  const flags = parseFlags(args);
@@ -135,6 +152,24 @@ async function runEstate(args, io) {
135
152
  io.out(formatMatches(flags.id, compareToEstate(fp, next, { exclude: flags.id, siblingAt: at }).slice(0, 3), at));
136
153
  return 0;
137
154
  }
155
+ if (sub === "compare" && flags.component) {
156
+ const role = componentRole(flags.component);
157
+ if (!target) {
158
+ const pairs2 = estateComponentPairs(register, role);
159
+ const measured = withRole(register, role);
160
+ io.out(flags.json ? toJson({ role, measured, sites: register.sites.length, pairs: pairs2 }) : formatComponentPairs(role, pairs2, measured, register.sites.length));
161
+ return 0;
162
+ }
163
+ const known = register.sites.find((s) => s.id === target);
164
+ const fp = known ? known.fingerprint : fingerprint(await snapshotOf(target, io.cwd));
165
+ if (!(fp.sections ?? []).some((x) => x.role === role)) throw new Error(`${known?.id ?? target} has no ${role} section measured (roles need snapshot version 2)`);
166
+ const pairs = register.sites.filter((s) => s.id !== known?.id).flatMap((s) => {
167
+ const c = closestComponents(fp, s.fingerprint, role);
168
+ return c ? [{ a: known?.id ?? target, b: s.id, ...c }] : [];
169
+ }).sort((x, y) => x.distance - y.distance);
170
+ io.out(flags.json ? toJson({ role, measured: withRole(register, role), sites: register.sites.length, pairs }) : formatComponentPairs(role, pairs, withRole(register, role), register.sites.length));
171
+ return 0;
172
+ }
138
173
  if (sub === "compare") {
139
174
  if (!target) {
140
175
  const pairs = estatePairs(register, at);
@@ -160,5 +195,6 @@ export {
160
195
  loadEstate,
161
196
  formatMatches,
162
197
  formatPairs,
198
+ formatComponentPairs,
163
199
  runEstate
164
200
  };