@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.
- package/CHANGELOG.md +36 -0
- package/CHARACTER.md +100 -1
- package/COPY.md +6 -2
- package/README.md +5 -0
- package/dist/{audit-QIPXL7E4.js → audit-5TYDYRXV.js} +9 -3
- package/dist/audit.cjs +881 -95
- package/dist/audit.d.cts +161 -4
- package/dist/audit.d.ts +161 -4
- package/dist/audit.js +878 -95
- package/dist/{chunk-2AJB27BJ.js → chunk-3VTPI7BU.js} +26 -11
- package/dist/chunk-CZDOMIVS.js +19 -0
- package/dist/chunk-ESCVTKPN.js +217 -0
- package/dist/{chunk-3NGWXZZE.js → chunk-GMVXADJ6.js} +2929 -2082
- package/dist/{chunk-MSQ57PJU.js → chunk-IMB4V34S.js} +31 -18
- package/dist/chunk-JYNXGCYZ.js +75 -0
- package/dist/{chunk-HOHHQB4N.js → chunk-K2BZN4GA.js} +52 -16
- package/dist/{chunk-JP75P2DU.js → chunk-ZLDA2SYB.js} +342 -16
- package/dist/{chunk-JWHC67ZS.js → chunk-ZZ5CKOJP.js} +121 -1
- package/dist/cli-BKJMLD3H.js +18 -0
- package/dist/{cli-U3SZVHSK.js → cli-DRKAMFAH.js} +12 -80
- package/dist/{cli-4ESXOLKV.js → cli-ILH7OO5G.js} +4 -4
- package/dist/{cli-DBZMVYYV.js → cli-J5ECRCD4.js} +17 -17
- package/dist/cli-N2IDXH7W.js +20 -0
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1291 -502
- package/dist/index.d.cts +586 -200
- package/dist/index.d.ts +586 -200
- package/dist/index.js +1263 -502
- package/licences.json +104 -0
- package/package.json +3 -2
- package/word-lists.json +1 -1
- package/dist/chunk-3OZQ4C5R.js +0 -459
- package/dist/chunk-QMYERCBB.js +0 -105
- package/dist/cli-T5RD2BTK.js +0 -17
- package/dist/cli-VREYWCT4.js +0 -16
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
snapshotUrl,
|
|
3
3
|
snapshotUrls
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZLDA2SYB.js";
|
|
5
5
|
import {
|
|
6
6
|
describeTypicality,
|
|
7
|
-
loadNull
|
|
8
|
-
|
|
9
|
-
|
|
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-
|
|
20
|
-
import {
|
|
21
|
-
fingerprint
|
|
22
|
-
} from "./chunk-QMYERCBB.js";
|
|
21
|
+
} from "./chunk-3VTPI7BU.js";
|
|
23
22
|
import {
|
|
24
|
-
SNAPSHOT_VERSION,
|
|
25
23
|
auditSnapshot,
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
85
|
-
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
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-
|
|
10
|
+
} from "./chunk-3VTPI7BU.js";
|
|
11
11
|
import {
|
|
12
|
+
COMPONENT_ROLES,
|
|
13
|
+
auditSnapshot,
|
|
14
|
+
closestComponents,
|
|
15
|
+
estateComponentPairs,
|
|
12
16
|
fingerprint,
|
|
13
|
-
fingerprintDistance
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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-
|
|
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
|
};
|