@domandigital/craft 0.12.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 +20 -0
- package/CHARACTER.md +92 -1
- package/dist/{audit-5QWFLZ6L.js → audit-5TYDYRXV.js} +6 -3
- package/dist/audit.cjs +613 -88
- package/dist/audit.d.cts +81 -1
- package/dist/audit.d.ts +81 -1
- package/dist/audit.js +611 -88
- package/dist/{chunk-J3QUROJ5.js → chunk-3VTPI7BU.js} +9 -8
- package/dist/{chunk-ZKVJ7K6X.js → chunk-CZDOMIVS.js} +1 -1
- package/dist/chunk-ESCVTKPN.js +217 -0
- package/dist/{chunk-AL6MRG6V.js → chunk-GMVXADJ6.js} +2912 -2102
- package/dist/{chunk-PU5G5DUC.js → chunk-IMB4V34S.js} +26 -13
- package/dist/{chunk-CDAJUS5U.js → chunk-K2BZN4GA.js} +49 -14
- package/dist/{chunk-ULPI335S.js → chunk-ZLDA2SYB.js} +168 -8
- package/dist/{chunk-S3NZAVU6.js → chunk-ZZ5CKOJP.js} +1 -1
- package/dist/cli-BKJMLD3H.js +18 -0
- package/dist/{cli-ZPJIWVEP.js → cli-DRKAMFAH.js} +5 -7
- package/dist/{cli-C3JLFXMG.js → cli-ILH7OO5G.js} +4 -5
- package/dist/{cli-ZB2VJ7CY.js → cli-J5ECRCD4.js} +13 -15
- package/dist/cli-N2IDXH7W.js +20 -0
- package/dist/cli.js +2 -2
- package/dist/index.cjs +929 -401
- package/dist/index.d.cts +408 -196
- package/dist/index.d.ts +408 -196
- package/dist/index.js +911 -401
- package/package.json +1 -1
- package/word-lists.json +1 -1
- package/dist/chunk-LHGY43LL.js +0 -462
- package/dist/chunk-PZ4MF2VV.js +0 -117
- package/dist/cli-2DY3V3SF.js +0 -17
- package/dist/cli-QM2AQQMF.js +0 -19
|
@@ -1,15 +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
|
-
} from "./chunk-LHGY43LL.js";
|
|
7
|
+
loadNull
|
|
8
|
+
} from "./chunk-ESCVTKPN.js";
|
|
10
9
|
import {
|
|
11
10
|
readSnapshot
|
|
12
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-CZDOMIVS.js";
|
|
13
12
|
import {
|
|
14
13
|
SOURCE_FILE,
|
|
15
14
|
finish,
|
|
@@ -19,14 +18,14 @@ import {
|
|
|
19
18
|
prepareReport,
|
|
20
19
|
readPaths,
|
|
21
20
|
toJson
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import {
|
|
24
|
-
fingerprint
|
|
25
|
-
} from "./chunk-PZ4MF2VV.js";
|
|
21
|
+
} from "./chunk-3VTPI7BU.js";
|
|
26
22
|
import {
|
|
27
23
|
auditSnapshot,
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
componentTypicalities,
|
|
25
|
+
fingerprint,
|
|
26
|
+
scanSource,
|
|
27
|
+
typicality
|
|
28
|
+
} from "./chunk-GMVXADJ6.js";
|
|
30
29
|
|
|
31
30
|
// src/audit/cli.ts
|
|
32
31
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -51,6 +50,12 @@ function parseViewports(value) {
|
|
|
51
50
|
}
|
|
52
51
|
|
|
53
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
|
+
}
|
|
54
59
|
var fmt = (o) => o ? `oklch(${o.l.toFixed(3)} ${o.c.toFixed(3)} ${o.h.toFixed(1)})` : "none";
|
|
55
60
|
function describeFingerprint(fp) {
|
|
56
61
|
return [
|
|
@@ -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,
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
loadNull,
|
|
5
|
-
shapeName
|
|
6
|
-
} from "./chunk-LHGY43LL.js";
|
|
2
|
+
loadNull
|
|
3
|
+
} from "./chunk-ESCVTKPN.js";
|
|
7
4
|
import {
|
|
8
5
|
readSnapshot
|
|
9
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CZDOMIVS.js";
|
|
10
7
|
import {
|
|
11
8
|
parseFlags,
|
|
12
9
|
toJson
|
|
13
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3VTPI7BU.js";
|
|
14
11
|
import {
|
|
12
|
+
COMPONENT_ROLES,
|
|
13
|
+
auditSnapshot,
|
|
14
|
+
closestComponents,
|
|
15
|
+
estateComponentPairs,
|
|
15
16
|
fingerprint,
|
|
16
|
-
fingerprintDistance
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "./chunk-
|
|
17
|
+
fingerprintDistance,
|
|
18
|
+
groundName,
|
|
19
|
+
hueName,
|
|
20
|
+
shapeName
|
|
21
|
+
} from "./chunk-GMVXADJ6.js";
|
|
21
22
|
|
|
22
23
|
// src/estate/cli.ts
|
|
23
24
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -80,7 +81,7 @@ function siblingLine(briefs) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
// src/estate/cli.ts
|
|
83
|
-
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]";
|
|
84
85
|
function loadEstate(path) {
|
|
85
86
|
if (!existsSync(path)) return emptyEstate();
|
|
86
87
|
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -94,7 +95,7 @@ async function snapshotOf(target, cwd) {
|
|
|
94
95
|
return snap;
|
|
95
96
|
}
|
|
96
97
|
if (!/^https?:\/\//.test(target)) throw new Error(`${target} is neither a URL, a snapshot file, nor a site in the register`);
|
|
97
|
-
const { snapshotUrl } = await import("./audit-
|
|
98
|
+
const { snapshotUrl } = await import("./audit-5TYDYRXV.js");
|
|
98
99
|
return snapshotUrl(target);
|
|
99
100
|
}
|
|
100
101
|
var line = (distance, sibling, shared) => `${distance.toFixed(2)}${sibling ? " SIBLING" : ""}${shared.length ? ` shares ${shared.join(", ")}` : ""}`;
|
|
@@ -110,6 +111,21 @@ function formatPairs(pairs, at, sites) {
|
|
|
110
111
|
for (const p of pairs) lines.push(` ${`${p.a} + ${p.b}`.padEnd(34)} ${line(p.distance, p.sibling, p.shared)}`);
|
|
111
112
|
return lines.join("\n");
|
|
112
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
|
+
}
|
|
113
129
|
async function runEstate(args, io) {
|
|
114
130
|
try {
|
|
115
131
|
const flags = parseFlags(args);
|
|
@@ -136,6 +152,24 @@ async function runEstate(args, io) {
|
|
|
136
152
|
io.out(formatMatches(flags.id, compareToEstate(fp, next, { exclude: flags.id, siblingAt: at }).slice(0, 3), at));
|
|
137
153
|
return 0;
|
|
138
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
|
+
}
|
|
139
173
|
if (sub === "compare") {
|
|
140
174
|
if (!target) {
|
|
141
175
|
const pairs = estatePairs(register, at);
|
|
@@ -161,5 +195,6 @@ export {
|
|
|
161
195
|
loadEstate,
|
|
162
196
|
formatMatches,
|
|
163
197
|
formatPairs,
|
|
198
|
+
formatComponentPairs,
|
|
164
199
|
runEstate
|
|
165
200
|
};
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
decodePng
|
|
3
3
|
} from "./chunk-JYNXGCYZ.js";
|
|
4
|
-
import {
|
|
5
|
-
fingerprint
|
|
6
|
-
} from "./chunk-PZ4MF2VV.js";
|
|
7
4
|
import {
|
|
8
5
|
SNAPSHOT_VERSION,
|
|
9
|
-
auditSnapshot
|
|
10
|
-
|
|
6
|
+
auditSnapshot,
|
|
7
|
+
fingerprint
|
|
8
|
+
} from "./chunk-GMVXADJ6.js";
|
|
11
9
|
|
|
12
10
|
// src/audit/collect.ts
|
|
13
11
|
function collectInPage() {
|
|
@@ -277,22 +275,50 @@ function collectInPage() {
|
|
|
277
275
|
if (cards >= 3 && iconCards >= 2) return "features";
|
|
278
276
|
return kind;
|
|
279
277
|
};
|
|
278
|
+
const partsOf = (el) => {
|
|
279
|
+
const inside = Array.from(el.querySelectorAll("*")).slice(0, 1500).filter(shown);
|
|
280
|
+
const badges = [];
|
|
281
|
+
for (const n of inside) {
|
|
282
|
+
const t = ownText(n);
|
|
283
|
+
if (t.length < 2 || t.length > 24 || badges.length >= 5) continue;
|
|
284
|
+
const cs = style(n);
|
|
285
|
+
const r = box(n);
|
|
286
|
+
const radius = px(cs.borderTopLeftRadius);
|
|
287
|
+
const drawn = painted(cs.backgroundColor) || px(cs.borderTopWidth) >= 1;
|
|
288
|
+
if (drawn && r.height <= 40 && r.width <= 240 && radius >= r.height / 3 && !isControl(n)) badges.push(t);
|
|
289
|
+
}
|
|
290
|
+
const avatars = inside.filter((n) => {
|
|
291
|
+
if (n.tagName !== "IMG") return false;
|
|
292
|
+
const r = box(n);
|
|
293
|
+
const rad = style(n).borderTopLeftRadius;
|
|
294
|
+
const round = rad.endsWith("%") ? parseFloat(rad) >= 40 : px(rad) >= r.width * 0.4;
|
|
295
|
+
return r.width <= 96 && r.width >= 16 && Math.abs(r.width - r.height) <= r.width * 0.15 && round;
|
|
296
|
+
}).length;
|
|
297
|
+
const carousel = inside.some((n) => {
|
|
298
|
+
const cs = style(n);
|
|
299
|
+
if (n.getAttribute("aria-roledescription") === "carousel" || cs.scrollSnapType && cs.scrollSnapType !== "none") return true;
|
|
300
|
+
if (/^(auto|scroll|hidden)$/.test(cs.overflowX) && n.children.length >= 3 && n.scrollWidth > n.clientWidth * 1.2) return true;
|
|
301
|
+
return /^(BUTTON|A)$/.test(n.tagName) && /\b(prev|previous|next)\b/i.test(n.getAttribute("aria-label") ?? "");
|
|
302
|
+
});
|
|
303
|
+
return { badges, avatars, carousel };
|
|
304
|
+
};
|
|
280
305
|
const kept = blocks.slice(0, 40);
|
|
281
306
|
const sections = kept.map((el, index) => {
|
|
282
307
|
const b = box(el);
|
|
283
308
|
const heading = el.querySelector("h1, h2, h3");
|
|
284
309
|
const label = (heading?.textContent ?? el.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 60);
|
|
285
|
-
const
|
|
310
|
+
const images2 = Array.from(el.querySelectorAll("img, svg")).filter((i) => shown(i) && box(i).height <= 90);
|
|
286
311
|
const textChars = (el.textContent ?? "").replace(/\s+/g, "").length;
|
|
287
312
|
const numbers = Array.from(el.querySelectorAll("*")).filter((n) => /^[£$€]?\d[\d,.]*\s?(?:[%+kKmM★]|\/5|\+)?$/.test(ownText(n)) && px(style(n).fontSize) >= 24).length;
|
|
288
313
|
const { cards, iconCards } = cardsIn(el);
|
|
289
314
|
let kind = "text";
|
|
290
315
|
if (marqueeTargets.some((t) => el.contains(t))) kind = "marquee";
|
|
291
316
|
else if (h1 && el.contains(h1) || index === 0 && b.top < vh) kind = "hero";
|
|
292
|
-
else if (
|
|
317
|
+
else if (images2.length >= 4 && textChars < 300) kind = "logos";
|
|
293
318
|
else if (numbers >= 3 && textChars < 600) kind = "stats";
|
|
294
319
|
else if (cards >= 3) kind = "cards";
|
|
295
320
|
const geometry = geometryOf(el, b);
|
|
321
|
+
const parts = partsOf(el);
|
|
296
322
|
return {
|
|
297
323
|
top: Math.round(b.top),
|
|
298
324
|
height: Math.round(b.height),
|
|
@@ -302,7 +328,9 @@ function collectInPage() {
|
|
|
302
328
|
iconCards,
|
|
303
329
|
hiddenAtLoad: b.top > vh && hiddenAtLoad(el),
|
|
304
330
|
role: roleOf(el, kind, geometry, b, cards, iconCards, index === kept.length - 1),
|
|
305
|
-
geometry
|
|
331
|
+
geometry,
|
|
332
|
+
...parts,
|
|
333
|
+
figures: numbers
|
|
306
334
|
};
|
|
307
335
|
});
|
|
308
336
|
if (header && sections.length === 0) {
|
|
@@ -384,6 +412,81 @@ function collectInPage() {
|
|
|
384
412
|
const width = para.getBoundingClientRect().width - px(cs.paddingLeft) - px(cs.paddingRight);
|
|
385
413
|
if (avg > 0 && width > 0) bodyLineLengthCh = Math.round(width / avg);
|
|
386
414
|
}
|
|
415
|
+
const unwrap = (raw) => {
|
|
416
|
+
if (raw.startsWith("data:")) return raw.slice(0, 64);
|
|
417
|
+
try {
|
|
418
|
+
const u = new URL(raw, location.href);
|
|
419
|
+
const inner = /\/_(?:next|vercel)\/image$/.test(u.pathname) ? u.searchParams.get("url") : null;
|
|
420
|
+
if (inner) return new URL(inner, location.href).href;
|
|
421
|
+
const cf = u.pathname.match(/^\/cdn-cgi\/image\/[^/]+\/(.+)$/);
|
|
422
|
+
if (cf) return /^https?:\/\//.test(cf[1]) ? cf[1] : new URL(`/${cf[1]}`, location.href).href;
|
|
423
|
+
return u.href;
|
|
424
|
+
} catch {
|
|
425
|
+
return raw.slice(0, 200);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
const roundBox = (el, w) => {
|
|
429
|
+
const rad = style(el).borderTopLeftRadius;
|
|
430
|
+
return rad.endsWith("%") ? parseFloat(rad) >= 40 : px(rad) >= w * 0.4;
|
|
431
|
+
};
|
|
432
|
+
const sectionOf = (el) => {
|
|
433
|
+
const i = kept.findIndex((k) => k.contains(el));
|
|
434
|
+
return i === -1 ? null : i;
|
|
435
|
+
};
|
|
436
|
+
const roleOfImage = (el, src, w, h, alt) => {
|
|
437
|
+
const vector = /\.svg(?:[?#]|$)/i.test(src) || src.startsWith("data:image/svg");
|
|
438
|
+
const square = Math.abs(w - h) <= w * 0.15;
|
|
439
|
+
if (w >= 16 && w <= 96 && square && (roundBox(el, w) || el.parentElement !== null && roundBox(el.parentElement, w))) return "avatar";
|
|
440
|
+
const section = sectionOf(el);
|
|
441
|
+
const hint = `${alt ?? ""} ${src} ${typeof el.className === "string" ? el.className : ""}`;
|
|
442
|
+
if (/\blogo\b/i.test(hint) || section !== null && sections[section]?.kind === "logos" || el.closest("header, footer, nav") && h <= 120) return "logo";
|
|
443
|
+
if (Math.max(w, h) <= (vector ? 120 : 72)) return "icon";
|
|
444
|
+
if (vector) return "illustration";
|
|
445
|
+
return w >= 160 && h >= 120 ? "photo" : "icon";
|
|
446
|
+
};
|
|
447
|
+
const images = [];
|
|
448
|
+
const seenImage = /* @__PURE__ */ new Set();
|
|
449
|
+
const addImage = (el, raw, alt, decorative, background) => {
|
|
450
|
+
const b = box(el);
|
|
451
|
+
if (!raw || b.width < 8 || b.height < 8) return;
|
|
452
|
+
const src = unwrap(raw);
|
|
453
|
+
const key = `${src} ${Math.round(b.top)} ${Math.round(b.left)}`;
|
|
454
|
+
if (seenImage.has(key)) return;
|
|
455
|
+
seenImage.add(key);
|
|
456
|
+
let host = null;
|
|
457
|
+
try {
|
|
458
|
+
host = src.startsWith("data:") ? null : new URL(src).hostname;
|
|
459
|
+
} catch {
|
|
460
|
+
host = null;
|
|
461
|
+
}
|
|
462
|
+
images.push({
|
|
463
|
+
src,
|
|
464
|
+
host,
|
|
465
|
+
width: Math.round(b.width),
|
|
466
|
+
height: Math.round(b.height),
|
|
467
|
+
top: Math.round(b.top),
|
|
468
|
+
role: roleOfImage(el, src, b.width, b.height, alt),
|
|
469
|
+
alt,
|
|
470
|
+
decorative,
|
|
471
|
+
section: sectionOf(el),
|
|
472
|
+
background
|
|
473
|
+
});
|
|
474
|
+
};
|
|
475
|
+
for (const img of Array.from(document.querySelectorAll("img")).slice(0, 400)) {
|
|
476
|
+
if (!shown(img)) continue;
|
|
477
|
+
const alt = img.getAttribute("alt");
|
|
478
|
+
const decorative = alt === "" || img.getAttribute("aria-hidden") === "true" || /^(?:presentation|none)$/.test(img.getAttribute("role") ?? "");
|
|
479
|
+
addImage(img, img.currentSrc || img.getAttribute("src") || "", alt, decorative, false);
|
|
480
|
+
}
|
|
481
|
+
for (const el of all) {
|
|
482
|
+
const bgImage = style(el).backgroundImage || "";
|
|
483
|
+
if (!bgImage.includes("url(")) continue;
|
|
484
|
+
const b = box(el);
|
|
485
|
+
if (b.width < 160 || b.height < 120 || !shown(el)) continue;
|
|
486
|
+
const m = bgImage.match(/url\(\s*["']?([^"')]+)["']?\s*\)/);
|
|
487
|
+
if (m) addImage(el, m[1], null, true, true);
|
|
488
|
+
}
|
|
489
|
+
images.sort((a, b) => b.width * b.height - a.width * a.height);
|
|
387
490
|
const toList = (m) => Array.from(m, ([value, v]) => ({ value, area: Math.round(v.area), chars: v.chars })).sort((a, b) => b.area - a.area || b.chars - a.chars);
|
|
388
491
|
const nums = (s) => Array.from(s).sort((a, b) => a - b);
|
|
389
492
|
return {
|
|
@@ -403,6 +506,7 @@ function collectInPage() {
|
|
|
403
506
|
return mean > 0 ? Math.round(sd / mean * 1e3) / 1e3 : null;
|
|
404
507
|
})(),
|
|
405
508
|
effects,
|
|
509
|
+
images: images.slice(0, 60),
|
|
406
510
|
motion: {
|
|
407
511
|
hiddenSections: sections.filter((s) => s.hiddenAtLoad).length,
|
|
408
512
|
sections: sections.length,
|
|
@@ -494,7 +598,60 @@ function visualMeasures(img, maxSamples = 1e6) {
|
|
|
494
598
|
};
|
|
495
599
|
}
|
|
496
600
|
|
|
601
|
+
// src/snapshot/provenance.ts
|
|
602
|
+
var PROVENANCE_SCAN_BYTES = 256 * 1024;
|
|
603
|
+
var AI_SOURCE_TYPES = ["compositeWithTrainedAlgorithmicMedia", "trainedAlgorithmicMedia"];
|
|
604
|
+
var MARKER = new RegExp(`(?:${AI_SOURCE_TYPES.join("|")})`);
|
|
605
|
+
var C2PA_BOX = /jumd[\s\S]{16,24}c2pa/;
|
|
606
|
+
var C2PA_PNG = /caBX/;
|
|
607
|
+
function latin1(bytes) {
|
|
608
|
+
let out = "";
|
|
609
|
+
for (let i = 0; i < bytes.length; i += 8192) {
|
|
610
|
+
out += String.fromCharCode(...bytes.subarray(i, Math.min(bytes.length, i + 8192)));
|
|
611
|
+
}
|
|
612
|
+
return out;
|
|
613
|
+
}
|
|
614
|
+
function xmpPackets(text) {
|
|
615
|
+
const out = [];
|
|
616
|
+
const open = /<x:xmpmeta\b|<\?xpacket begin/g;
|
|
617
|
+
let m;
|
|
618
|
+
while (m = open.exec(text)) {
|
|
619
|
+
const close = text.indexOf(m[0].startsWith("<x:") ? "</x:xmpmeta>" : "<?xpacket end", m.index);
|
|
620
|
+
out.push([m.index, close === -1 ? text.length : close]);
|
|
621
|
+
if (close === -1) break;
|
|
622
|
+
open.lastIndex = close;
|
|
623
|
+
}
|
|
624
|
+
return out;
|
|
625
|
+
}
|
|
626
|
+
function scanProvenance(bytes) {
|
|
627
|
+
const head = bytes.subarray(0, PROVENANCE_SCAN_BYTES);
|
|
628
|
+
const text = latin1(head);
|
|
629
|
+
const c2pa = C2PA_BOX.test(text) || text.startsWith("\x89PNG") && C2PA_PNG.test(text);
|
|
630
|
+
const m = MARKER.exec(text);
|
|
631
|
+
if (!m) return { digitalSourceType: null, source: null, c2pa, bytes: head.length };
|
|
632
|
+
const inXmp = xmpPackets(text).some(([a, b]) => m.index >= a && m.index < b);
|
|
633
|
+
return { digitalSourceType: m[0], source: inXmp ? "xmp" : c2pa ? "c2pa" : "other", c2pa, bytes: head.length };
|
|
634
|
+
}
|
|
635
|
+
|
|
497
636
|
// src/audit/index.ts
|
|
637
|
+
var PROVENANCE_MAX_IMAGES = 24;
|
|
638
|
+
var PROVENANCE_BUDGET_MS = 2e4;
|
|
639
|
+
async function readProvenance(page, images, timeoutMs) {
|
|
640
|
+
if (!page.request) return;
|
|
641
|
+
const deadline = Date.now() + PROVENANCE_BUDGET_MS;
|
|
642
|
+
const wanted = [...new Set(images.filter((i) => i.role !== "icon" && i.role !== "logo" && /^https?:\/\//.test(i.src)).map((i) => i.src))].slice(0, PROVENANCE_MAX_IMAGES);
|
|
643
|
+
for (const src of wanted) {
|
|
644
|
+
const left = deadline - Date.now();
|
|
645
|
+
if (left < 500) break;
|
|
646
|
+
try {
|
|
647
|
+
const res = await page.request.get(src, { headers: { Range: `bytes=0-${PROVENANCE_SCAN_BYTES - 1}` }, timeout: Math.min(timeoutMs, left) });
|
|
648
|
+
if (!res.ok()) continue;
|
|
649
|
+
const provenance = scanProvenance(await res.body());
|
|
650
|
+
for (const image of images) if (image.src === src) image.provenance = provenance;
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
498
655
|
var VISUAL_MAX_HEIGHT = 6e3;
|
|
499
656
|
async function loadPlaywright() {
|
|
500
657
|
for (const name of ["playwright", "playwright-core"]) {
|
|
@@ -513,6 +670,7 @@ async function snapshotPage(page, url, options = {}) {
|
|
|
513
670
|
await page.waitForTimeout(options.introWindowMs ?? 5e3);
|
|
514
671
|
const late = await page.evaluate(overlayInPage).catch(() => false);
|
|
515
672
|
const data = await page.evaluate(collectInPage);
|
|
673
|
+
if (data.images && options.provenance !== false) await readProvenance(page, data.images, 8e3);
|
|
516
674
|
let visual;
|
|
517
675
|
if (page.screenshot && options.visual !== false) {
|
|
518
676
|
try {
|
|
@@ -578,6 +736,8 @@ async function audit(url, options = {}) {
|
|
|
578
736
|
export {
|
|
579
737
|
collectInPage,
|
|
580
738
|
overlayInPage,
|
|
739
|
+
PROVENANCE_MAX_IMAGES,
|
|
740
|
+
PROVENANCE_BUDGET_MS,
|
|
581
741
|
VISUAL_MAX_HEIGHT,
|
|
582
742
|
loadPlaywright,
|
|
583
743
|
snapshotPage,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatComponentPairs,
|
|
3
|
+
formatMatches,
|
|
4
|
+
formatPairs,
|
|
5
|
+
loadEstate,
|
|
6
|
+
runEstate
|
|
7
|
+
} from "./chunk-K2BZN4GA.js";
|
|
8
|
+
import "./chunk-ESCVTKPN.js";
|
|
9
|
+
import "./chunk-CZDOMIVS.js";
|
|
10
|
+
import "./chunk-3VTPI7BU.js";
|
|
11
|
+
import "./chunk-GMVXADJ6.js";
|
|
12
|
+
export {
|
|
13
|
+
formatComponentPairs,
|
|
14
|
+
formatMatches,
|
|
15
|
+
formatPairs,
|
|
16
|
+
loadEstate,
|
|
17
|
+
runEstate
|
|
18
|
+
};
|
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DIRECTION_VERSION,
|
|
3
3
|
validateDirection
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZZ5CKOJP.js";
|
|
5
5
|
import {
|
|
6
6
|
decodePng
|
|
7
7
|
} from "./chunk-JYNXGCYZ.js";
|
|
8
8
|
import {
|
|
9
9
|
readSnapshot
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CZDOMIVS.js";
|
|
11
11
|
import {
|
|
12
12
|
parseFlags,
|
|
13
13
|
toJson
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import {
|
|
16
|
-
fingerprint
|
|
17
|
-
} from "./chunk-PZ4MF2VV.js";
|
|
14
|
+
} from "./chunk-3VTPI7BU.js";
|
|
18
15
|
import {
|
|
19
16
|
deltaEOk,
|
|
17
|
+
fingerprint,
|
|
20
18
|
formatHex,
|
|
21
19
|
hexToOklch,
|
|
22
20
|
isAiViolet,
|
|
23
21
|
isCream,
|
|
24
22
|
oklchToRgb,
|
|
25
23
|
parseColour
|
|
26
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-GMVXADJ6.js";
|
|
27
25
|
|
|
28
26
|
// src/direction/cli.ts
|
|
29
27
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -6,11 +6,10 @@ import {
|
|
|
6
6
|
nullPath,
|
|
7
7
|
runHarvest,
|
|
8
8
|
runNull
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-AL6MRG6V.js";
|
|
9
|
+
} from "./chunk-ESCVTKPN.js";
|
|
10
|
+
import "./chunk-CZDOMIVS.js";
|
|
11
|
+
import "./chunk-3VTPI7BU.js";
|
|
12
|
+
import "./chunk-GMVXADJ6.js";
|
|
14
13
|
export {
|
|
15
14
|
describeTypicality,
|
|
16
15
|
formatHarvest,
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
validateDirection
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZZ5CKOJP.js";
|
|
4
4
|
import {
|
|
5
5
|
merge
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-IMB4V34S.js";
|
|
7
|
+
import "./chunk-ZLDA2SYB.js";
|
|
8
8
|
import "./chunk-JYNXGCYZ.js";
|
|
9
9
|
import {
|
|
10
10
|
SIBLING_AT,
|
|
11
11
|
compareToEstate,
|
|
12
12
|
loadEstate
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-K2BZN4GA.js";
|
|
14
14
|
import {
|
|
15
|
-
loadNull
|
|
16
|
-
|
|
17
|
-
} from "./chunk-LHGY43LL.js";
|
|
15
|
+
loadNull
|
|
16
|
+
} from "./chunk-ESCVTKPN.js";
|
|
18
17
|
import {
|
|
19
18
|
readSnapshot
|
|
20
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-CZDOMIVS.js";
|
|
21
20
|
import {
|
|
22
21
|
COPY_FILE,
|
|
23
22
|
NOT_COPY_DIR,
|
|
@@ -28,16 +27,15 @@ import {
|
|
|
28
27
|
parseFlags,
|
|
29
28
|
readPaths,
|
|
30
29
|
toJson
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import {
|
|
33
|
-
fingerprint
|
|
34
|
-
} from "./chunk-PZ4MF2VV.js";
|
|
30
|
+
} from "./chunk-3VTPI7BU.js";
|
|
35
31
|
import {
|
|
36
32
|
CATALOGUE,
|
|
37
33
|
auditSnapshot,
|
|
38
34
|
checkCopy,
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
fingerprint,
|
|
36
|
+
scanSource,
|
|
37
|
+
typicality
|
|
38
|
+
} from "./chunk-GMVXADJ6.js";
|
|
41
39
|
|
|
42
40
|
// src/report/cli.ts
|
|
43
41
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -264,7 +262,7 @@ async function runReport(command, args, io) {
|
|
|
264
262
|
if (/\.json$/i.test(target2) && existsSync(local)) {
|
|
265
263
|
snap = readSnapshot(JSON.parse(readFileSync(local, "utf8")), target2);
|
|
266
264
|
} else {
|
|
267
|
-
const { snapshotUrl } = await import("./audit-
|
|
265
|
+
const { snapshotUrl } = await import("./audit-5TYDYRXV.js");
|
|
268
266
|
snap = await snapshotUrl(target2);
|
|
269
267
|
}
|
|
270
268
|
const fp = fingerprint(snap);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describeComponents,
|
|
3
|
+
describeFingerprint,
|
|
4
|
+
merge,
|
|
5
|
+
resolvePages,
|
|
6
|
+
runAudit
|
|
7
|
+
} from "./chunk-IMB4V34S.js";
|
|
8
|
+
import "./chunk-ZLDA2SYB.js";
|
|
9
|
+
import "./chunk-JYNXGCYZ.js";
|
|
10
|
+
import "./chunk-ESCVTKPN.js";
|
|
11
|
+
import "./chunk-CZDOMIVS.js";
|
|
12
|
+
import "./chunk-3VTPI7BU.js";
|
|
13
|
+
import "./chunk-GMVXADJ6.js";
|
|
14
|
+
export {
|
|
15
|
+
describeComponents,
|
|
16
|
+
describeFingerprint,
|
|
17
|
+
merge,
|
|
18
|
+
resolvePages,
|
|
19
|
+
runAudit
|
|
20
|
+
};
|