@domandigital/craft 0.13.0 → 0.14.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 +6 -0
- package/CHARACTER.md +59 -11
- package/COPY.md +5 -0
- package/dist/{audit-5TYDYRXV.js → audit-6WHR7ZTV.js} +3 -2
- package/dist/audit.cjs +669 -92
- package/dist/audit.d.cts +15 -1
- package/dist/audit.d.ts +15 -1
- package/dist/audit.js +669 -92
- package/dist/{chunk-3VTPI7BU.js → chunk-3OTKMKJQ.js} +65 -91
- package/dist/{chunk-IMB4V34S.js → chunk-7T5WR74C.js} +7 -7
- package/dist/{chunk-GMVXADJ6.js → chunk-GQVEYTBY.js} +698 -89
- package/dist/{chunk-ZZ5CKOJP.js → chunk-OOZOEWGD.js} +1 -1
- package/dist/{chunk-K2BZN4GA.js → chunk-PBK6SYH5.js} +7 -7
- package/dist/{chunk-ZLDA2SYB.js → chunk-Q7ILL2HV.js} +47 -6
- package/dist/chunk-QYBAVU3P.js +6 -0
- package/dist/{chunk-CZDOMIVS.js → chunk-WUACK2HX.js} +2 -1
- package/dist/{chunk-ESCVTKPN.js → chunk-XVBR2UPH.js} +6 -6
- package/dist/{cli-BKJMLD3H.js → cli-3CGUKXY4.js} +6 -5
- package/dist/{cli-DRKAMFAH.js → cli-O3CDSONO.js} +7 -6
- package/dist/{cli-J5ECRCD4.js → cli-WG3UJNUF.js} +12 -11
- package/dist/{cli-N2IDXH7W.js → cli-WSHPSWVU.js} +7 -6
- package/dist/{cli-ILH7OO5G.js → cli-Z2IQG3LC.js} +5 -4
- package/dist/cli.js +3 -2
- package/dist/index.cjs +780 -225
- package/dist/index.d.cts +165 -3
- package/dist/index.d.ts +165 -3
- package/dist/index.js +763 -225
- package/dist/migrate-VIFAYIBM.js +9 -0
- package/package.json +1 -1
- package/word-lists.json +1 -1
|
@@ -4,13 +4,19 @@ import {
|
|
|
4
4
|
HOUSE_LOCKED,
|
|
5
5
|
applyHouseGate,
|
|
6
6
|
checkCopy,
|
|
7
|
+
compareCompetitor,
|
|
8
|
+
compareFacts,
|
|
7
9
|
extractCopy,
|
|
8
|
-
extractStrings,
|
|
9
10
|
fileKind,
|
|
11
|
+
formatComparison,
|
|
12
|
+
formatCompetitor,
|
|
10
13
|
holdHouseBlocks,
|
|
11
14
|
houseRule,
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
pageCopy,
|
|
16
|
+
protectedFacts,
|
|
17
|
+
scanSource,
|
|
18
|
+
visibleText
|
|
19
|
+
} from "./chunk-GQVEYTBY.js";
|
|
14
20
|
|
|
15
21
|
// src/character/config.ts
|
|
16
22
|
var DEFAULT_IGNORE = [".claude", ".agents", ".cursor"];
|
|
@@ -205,87 +211,6 @@ function toSarif(report, options) {
|
|
|
205
211
|
};
|
|
206
212
|
}
|
|
207
213
|
|
|
208
|
-
// src/character/facts.ts
|
|
209
|
-
var MONTH = "(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)";
|
|
210
|
-
var PATTERNS = [
|
|
211
|
-
["url", /\bhttps?:\/\/[^\s<>"'`)\]]+|\bwww\.[^\s<>"'`)\]]+/gi],
|
|
212
|
-
["email", /\b[\w.+-]+@[\w-]+(?:\.[\w-]+)+\b/g],
|
|
213
|
-
["phone", /(?:\+44\s?\(?0?\)?\s?|\b0)\d(?:[\s-]?\d){8,9}\b/g],
|
|
214
|
-
["postcode", /\b[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}\b/g],
|
|
215
|
-
["money", /[£$€]\s?\d[\d,]*(?:\.\d{1,2})?(?:\s?(?:k|m|bn)\b)?|\b\d[\d,]*(?:\.\d{1,2})?\s?(?:pounds|GBP)\b/gi],
|
|
216
|
-
["date", new RegExp(`\\b\\d{1,2}(?:st|nd|rd|th)?\\s+${MONTH}\\b(?:\\s+\\d{4})?|\\b${MONTH}\\s+\\d{1,2}(?:st|nd|rd|th)?\\b(?:,?\\s+\\d{4})?|\\b${MONTH}\\s+\\d{4}\\b|\\b\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}\\b|\\b(?:19|20)\\d{2}\\b`, "g")],
|
|
217
|
-
["time", /\b\d{1,2}(?:[:.]\d{2})?\s?(?:am|pm)\b|\b\d{1,2}:\d{2}\b/gi],
|
|
218
|
-
["number", /\b\d[\d,]*(?:\.\d+)?%?|\b\d+(?:\.\d+)?\s?(?:per ?cent)\b/gi]
|
|
219
|
-
];
|
|
220
|
-
var NAME = /[A-Z][a-zA-Z’'&-]*[a-zA-Z](?:\s+(?:of|and|&|the)?\s*[A-Z][a-zA-Z’'&-]*[a-zA-Z])*/g;
|
|
221
|
-
var normalise = (kind, value) => {
|
|
222
|
-
const v = value.trim();
|
|
223
|
-
if (kind === "phone") return v.replace(/[^\d+]/g, "").replace(/^\+44\(?0?\)?/, "0").replace(/^\+44/, "0");
|
|
224
|
-
if (kind === "money" || kind === "number") return v.replace(/[\s,]/g, "").toLowerCase();
|
|
225
|
-
if (kind === "postcode") return v.replace(/\s/g, "").toUpperCase();
|
|
226
|
-
if (kind === "url") return v.replace(/[.,;:]+$/, "").toLowerCase();
|
|
227
|
-
return v.replace(/\s+/g, " ").toLowerCase();
|
|
228
|
-
};
|
|
229
|
-
function visibleText(file) {
|
|
230
|
-
const prose = /\.(md|markdown|mdx|txt)$/i.test(file.path);
|
|
231
|
-
return (prose ? extractCopy(file) : extractStrings(file)).map((b) => b.text).join("\n");
|
|
232
|
-
}
|
|
233
|
-
function protectedFacts(text) {
|
|
234
|
-
const facts = [];
|
|
235
|
-
let rest = text;
|
|
236
|
-
const mask = (start, length) => {
|
|
237
|
-
rest = rest.slice(0, start) + " ".repeat(length) + rest.slice(start + length);
|
|
238
|
-
};
|
|
239
|
-
for (const [kind, pattern] of PATTERNS) {
|
|
240
|
-
for (const m of [...rest.matchAll(pattern)]) {
|
|
241
|
-
const value = m[0].replace(/[.,;:]+$/, "");
|
|
242
|
-
facts.push({ kind, value, key: `${kind}:${normalise(kind, value)}` });
|
|
243
|
-
mask(m.index ?? 0, m[0].length);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
const lines = rest.split("\n");
|
|
247
|
-
for (const line of lines) {
|
|
248
|
-
if (/^\s{0,3}#{1,6}\s/.test(line)) continue;
|
|
249
|
-
for (const m of line.matchAll(NAME)) {
|
|
250
|
-
const at = m.index ?? 0;
|
|
251
|
-
const before = line.slice(0, at).trimEnd();
|
|
252
|
-
const opensSentence = before === "" || /[.!?:"“(>*-]$/.test(before) || /^\s*(?:[-*+]|\d+\.)$/.test(before);
|
|
253
|
-
const word = m[0];
|
|
254
|
-
const acronym = /^[A-Z]{2,}$/.test(word);
|
|
255
|
-
if (opensSentence && !acronym && !/\s/.test(word)) continue;
|
|
256
|
-
if (word === "I") continue;
|
|
257
|
-
facts.push({ kind: "name", value: word, key: `name:${normalise("name", word)}` });
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return facts;
|
|
261
|
-
}
|
|
262
|
-
var unique = (facts) => [...new Map(facts.map((f) => [f.key, f])).values()];
|
|
263
|
-
function compareFacts(before, after) {
|
|
264
|
-
const a = unique(protectedFacts(before));
|
|
265
|
-
const b = unique(protectedFacts(after));
|
|
266
|
-
const inA = new Set(a.map((f) => f.key));
|
|
267
|
-
const inB = new Set(b.map((f) => f.key));
|
|
268
|
-
return {
|
|
269
|
-
before: a.length,
|
|
270
|
-
after: b.length,
|
|
271
|
-
lost: a.filter((f) => !inB.has(f.key)),
|
|
272
|
-
added: b.filter((f) => !inA.has(f.key))
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
function formatComparison(result, beforePath, afterPath) {
|
|
276
|
-
const lines = [`craft copy compare: ${beforePath} -> ${afterPath}`, ` ${result.before} protected facts before, ${result.after} after`];
|
|
277
|
-
if (result.lost.length) {
|
|
278
|
-
lines.push("", ` Lost (${result.lost.length}): restore each, or say why it went`);
|
|
279
|
-
for (const f of result.lost) lines.push(` ${f.kind.padEnd(9)} ${f.value}`);
|
|
280
|
-
}
|
|
281
|
-
if (result.added.length) {
|
|
282
|
-
lines.push("", ` Added (${result.added.length}): each needs a source, or it is invented`);
|
|
283
|
-
for (const f of result.added) lines.push(` ${f.kind.padEnd(9)} ${f.value}`);
|
|
284
|
-
}
|
|
285
|
-
if (!result.lost.length && !result.added.length) lines.push("", " Every fact kept, nothing added.");
|
|
286
|
-
return lines.join("\n");
|
|
287
|
-
}
|
|
288
|
-
|
|
289
214
|
// src/character/claims.ts
|
|
290
215
|
var CLAIM_KINDS = /* @__PURE__ */ new Set(["money", "number", "date"]);
|
|
291
216
|
var PROPER = "[A-Z][\\w&.\u2019'-]*(?:\\s+(?:of|and|&|the|for)?\\s*[A-Z][\\w&.\u2019'-]*)*";
|
|
@@ -445,6 +370,7 @@ Usage
|
|
|
445
370
|
craft scan [paths...] [--staged] [--json] [--strict] [--direction <file>] [ci options]
|
|
446
371
|
craft copy [paths...] [--gate] [--json] [--strict] [--direction <file>] [ci options]
|
|
447
372
|
craft copy compare <before> <after> [--json]
|
|
373
|
+
craft copy compare <url | snapshot.json> --competitor <url | snapshot.json> [--brief "<text>"] [--json]
|
|
448
374
|
craft copy claims <paths...> [--json]
|
|
449
375
|
craft tells list [--json]
|
|
450
376
|
craft tells harvest <null.json | dir>... [--share 0.25] [--json]
|
|
@@ -468,6 +394,11 @@ copy The prose in Markdown, markup and content files.
|
|
|
468
394
|
compare lists the protected facts (prices, numbers, dates, times,
|
|
469
395
|
phones, emails, links, postcodes, names) a rewrite lost or added.
|
|
470
396
|
Exit 1 if any: restore it, source it, or say why.
|
|
397
|
+
With --competitor, reads both rendered pages instead and lists the
|
|
398
|
+
hero and service sentences holding no specific (place, name,
|
|
399
|
+
number, price, job) the other page does not also say. The brief's
|
|
400
|
+
terms come from --brief, else art-direction.json, else --null's
|
|
401
|
+
null.json. A measure, not a gate: always exit 0.
|
|
471
402
|
claims lists every sentence holding a price, figure, date or named
|
|
472
403
|
source, marked sourced or UNSOURCED, for a person to check against
|
|
473
404
|
the primary source. It cannot tell true from false. Always exit 0.
|
|
@@ -529,7 +460,8 @@ var VALUE_FLAGS = {
|
|
|
529
460
|
"--sarif": "sarif",
|
|
530
461
|
"--pages": "pages",
|
|
531
462
|
"--viewport": "viewport",
|
|
532
|
-
"--component": "component"
|
|
463
|
+
"--component": "component",
|
|
464
|
+
"--competitor": "competitor"
|
|
533
465
|
};
|
|
534
466
|
function parseFlags(args) {
|
|
535
467
|
const flags = { positional: [], json: false, strict: false, staged: false, gate: false, updateBaseline: false };
|
|
@@ -637,28 +569,69 @@ function finish(report, flags, title, io) {
|
|
|
637
569
|
var SOURCE_FILE = (p) => ["markup", "script", "css"].includes(fileKind(p)) && !/\.d\.ts$|\.min\.js$/.test(p);
|
|
638
570
|
var COPY_FILE = (p) => (["markup", "prose", "script"].includes(fileKind(p)) || /\.jsonl?$/i.test(p)) && !NOT_SITE_COPY.test(p.split("/").pop() ?? p) && !/(?:^|\/)(?:package(?:-lock)?|tsconfig[\w.-]*|art-direction)\.json$/.test(p);
|
|
639
571
|
var NOT_COPY_DIR = (name) => name.startsWith("_");
|
|
572
|
+
async function pageSnapshot(target, cwd) {
|
|
573
|
+
const local = resolve(cwd, target);
|
|
574
|
+
if (/\.json$/i.test(target) && existsSync(local)) {
|
|
575
|
+
const { readSnapshot } = await import("./migrate-VIFAYIBM.js");
|
|
576
|
+
return readSnapshot(JSON.parse(readFileSync(local, "utf8")), target);
|
|
577
|
+
}
|
|
578
|
+
if (!/^https?:\/\//.test(target)) throw new Error(`${target} is neither a URL nor a snapshot file`);
|
|
579
|
+
const { snapshotUrl } = await import("./audit-6WHR7ZTV.js");
|
|
580
|
+
return snapshotUrl(target);
|
|
581
|
+
}
|
|
582
|
+
async function briefFor(flags, cwd) {
|
|
583
|
+
if (flags.brief) return flags.brief;
|
|
584
|
+
const direction = resolve(cwd, flags.direction ?? "art-direction.json");
|
|
585
|
+
if (existsSync(direction)) {
|
|
586
|
+
const brief = JSON.parse(readFileSync(direction, "utf8")).brief;
|
|
587
|
+
if (typeof brief === "string" && brief.trim()) return brief;
|
|
588
|
+
} else if (flags.direction) throw new Error(`no such file: ${flags.direction}`);
|
|
589
|
+
if (flags.null) {
|
|
590
|
+
const { loadNull } = await import("./cli-Z2IQG3LC.js");
|
|
591
|
+
return loadNull(flags.null, cwd).brief;
|
|
592
|
+
}
|
|
593
|
+
return void 0;
|
|
594
|
+
}
|
|
595
|
+
async function compareWithCompetitor(flags, io) {
|
|
596
|
+
try {
|
|
597
|
+
if (flags.positional.length !== 1 || !flags.competitor) throw new Error('usage: craft copy compare <url | snapshot.json> --competitor <url | snapshot.json> [--brief "<text>"] [--json]');
|
|
598
|
+
const brief = await briefFor(flags, io.cwd);
|
|
599
|
+
const [ours, theirs] = await Promise.all([pageSnapshot(flags.positional[0], io.cwd), pageSnapshot(flags.competitor, io.cwd)]);
|
|
600
|
+
const copy = [ours, theirs].map((snap, i) => {
|
|
601
|
+
const page = pageCopy(snap);
|
|
602
|
+
if (!page) throw new Error(`${[flags.positional[0], flags.competitor][i]} has no page text: snapshot it again with this craft`);
|
|
603
|
+
return page;
|
|
604
|
+
});
|
|
605
|
+
const result = compareCompetitor(copy[0], copy[1], brief);
|
|
606
|
+
io.out(flags.json ? toJson(result) : formatCompetitor(result));
|
|
607
|
+
return 0;
|
|
608
|
+
} catch (error) {
|
|
609
|
+
io.err(`craft: ${error.message}`);
|
|
610
|
+
return 2;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
640
613
|
function run(argv, io) {
|
|
641
614
|
const [command, ...rest] = argv;
|
|
642
615
|
if (command === "direction") {
|
|
643
|
-
return import("./cli-
|
|
616
|
+
return import("./cli-O3CDSONO.js").then((m) => m.runDirection(rest, io));
|
|
644
617
|
}
|
|
645
618
|
if (command === "report" || command === "retrofit") {
|
|
646
|
-
return import("./cli-
|
|
619
|
+
return import("./cli-WG3UJNUF.js").then((m) => m.runReport(command, rest, io));
|
|
647
620
|
}
|
|
648
621
|
if (command === "estate") {
|
|
649
|
-
return import("./cli-
|
|
622
|
+
return import("./cli-3CGUKXY4.js").then((m) => m.runEstate(rest, io));
|
|
650
623
|
}
|
|
651
624
|
if (command === "null") {
|
|
652
|
-
return import("./cli-
|
|
625
|
+
return import("./cli-Z2IQG3LC.js").then((m) => m.runNull(rest, io));
|
|
653
626
|
}
|
|
654
627
|
if (command === "tells" && rest[0] === "harvest") {
|
|
655
|
-
return import("./cli-
|
|
628
|
+
return import("./cli-Z2IQG3LC.js").then((m) => m.runHarvest(rest, io)).catch((error) => {
|
|
656
629
|
io.err(`craft: ${error.message}`);
|
|
657
630
|
return 2;
|
|
658
631
|
});
|
|
659
632
|
}
|
|
660
633
|
if (command === "snapshot" || command === "audit") {
|
|
661
|
-
return import("./cli-
|
|
634
|
+
return import("./cli-WSHPSWVU.js").then((m) => m.runAudit(command, rest, io));
|
|
662
635
|
}
|
|
663
636
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
664
637
|
io.out(HELP);
|
|
@@ -690,6 +663,7 @@ function run(argv, io) {
|
|
|
690
663
|
if (command === "copy" && rest[0] === "compare") {
|
|
691
664
|
const flags = parseFlags(rest.slice(1));
|
|
692
665
|
if (typeof flags === "string") throw new Error(flags);
|
|
666
|
+
if (flags.competitor) return compareWithCompetitor(flags, io);
|
|
693
667
|
if (flags.positional.length !== 2) throw new Error("usage: craft copy compare <before> <after> [--json]");
|
|
694
668
|
const [before, after] = flags.positional.map((p) => {
|
|
695
669
|
const abs = resolve(io.cwd, p);
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
snapshotUrl,
|
|
3
3
|
snapshotUrls
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Q7ILL2HV.js";
|
|
5
5
|
import {
|
|
6
6
|
describeTypicality,
|
|
7
7
|
loadNull
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
readSnapshot
|
|
11
|
-
} from "./chunk-CZDOMIVS.js";
|
|
8
|
+
} from "./chunk-XVBR2UPH.js";
|
|
12
9
|
import {
|
|
13
10
|
SOURCE_FILE,
|
|
14
11
|
finish,
|
|
@@ -18,14 +15,17 @@ import {
|
|
|
18
15
|
prepareReport,
|
|
19
16
|
readPaths,
|
|
20
17
|
toJson
|
|
21
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-3OTKMKJQ.js";
|
|
19
|
+
import {
|
|
20
|
+
readSnapshot
|
|
21
|
+
} from "./chunk-WUACK2HX.js";
|
|
22
22
|
import {
|
|
23
23
|
auditSnapshot,
|
|
24
24
|
componentTypicalities,
|
|
25
25
|
fingerprint,
|
|
26
26
|
scanSource,
|
|
27
27
|
typicality
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-GQVEYTBY.js";
|
|
29
29
|
|
|
30
30
|
// src/audit/cli.ts
|
|
31
31
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|