@domandigital/craft 0.9.0 → 0.11.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 +26 -0
- package/CHARACTER.md +2 -1
- package/COPY.md +6 -0
- package/README.md +41 -0
- package/dist/{audit-5P65UMFL.js → audit-QIPXL7E4.js} +3 -3
- package/dist/audit.cjs +67 -11
- package/dist/audit.js +67 -11
- package/dist/chunk-2AJB27BJ.js +743 -0
- package/dist/{chunk-ZT3WLN7R.js → chunk-3NGWXZZE.js} +64 -10
- package/dist/{chunk-VKQDOUHO.js → chunk-3OZQ4C5R.js} +7 -6
- package/dist/{chunk-5LI2VFN5.js → chunk-HOHHQB4N.js} +9 -8
- package/dist/{chunk-BYY32NIN.js → chunk-JP75P2DU.js} +5 -3
- package/dist/{chunk-JRMU6GYX.js → chunk-JWHC67ZS.js} +1 -1
- package/dist/chunk-MSQ57PJU.js +206 -0
- package/dist/{chunk-EHNEGKFR.js → chunk-QMYERCBB.js} +1 -1
- package/dist/{cli-4VAOH7ST.js → cli-4ESXOLKV.js} +4 -4
- package/dist/{cli-V55MTIOP.js → cli-DBZMVYYV.js} +20 -14
- package/dist/cli-T5RD2BTK.js +17 -0
- package/dist/{cli-3XPEGBUR.js → cli-U3SZVHSK.js} +7 -6
- package/dist/cli-VREYWCT4.js +16 -0
- package/dist/cli.js +2 -2
- package/dist/index.cjs +400 -10
- package/dist/index.d.cts +227 -2
- package/dist/index.d.ts +227 -2
- package/dist/index.js +383 -10
- package/package.json +13 -6
- package/word-lists.json +1 -1
- package/dist/chunk-DAENYJUO.js +0 -112
- package/dist/chunk-YLQE35PN.js +0 -364
- package/dist/cli-J7C6TI2B.js +0 -16
- package/dist/cli-WDSVVHKG.js +0 -15
package/dist/chunk-DAENYJUO.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
snapshotUrl
|
|
3
|
-
} from "./chunk-BYY32NIN.js";
|
|
4
|
-
import {
|
|
5
|
-
describeTypicality,
|
|
6
|
-
loadNull,
|
|
7
|
-
typicality
|
|
8
|
-
} from "./chunk-VKQDOUHO.js";
|
|
9
|
-
import {
|
|
10
|
-
SOURCE_FILE,
|
|
11
|
-
finish,
|
|
12
|
-
loadExceptions,
|
|
13
|
-
parseFlags,
|
|
14
|
-
readPaths
|
|
15
|
-
} from "./chunk-YLQE35PN.js";
|
|
16
|
-
import {
|
|
17
|
-
fingerprint
|
|
18
|
-
} from "./chunk-EHNEGKFR.js";
|
|
19
|
-
import {
|
|
20
|
-
SNAPSHOT_VERSION,
|
|
21
|
-
auditSnapshot,
|
|
22
|
-
scanSource
|
|
23
|
-
} from "./chunk-ZT3WLN7R.js";
|
|
24
|
-
|
|
25
|
-
// src/audit/cli.ts
|
|
26
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
27
|
-
import { resolve } from "path";
|
|
28
|
-
var fmt = (o) => o ? `oklch(${o.l.toFixed(3)} ${o.c.toFixed(3)} ${o.h.toFixed(1)})` : "none";
|
|
29
|
-
function describeFingerprint(fp) {
|
|
30
|
-
return [
|
|
31
|
-
"Fingerprint",
|
|
32
|
-
` accent ${fmt(fp.accent)}`,
|
|
33
|
-
` ground ${fmt(fp.ground)}`,
|
|
34
|
-
` display ${fp.display.family} (${fp.display.class})`,
|
|
35
|
-
` body ${fp.body.family} (${fp.body.class})`,
|
|
36
|
-
` roundness ${fp.roundness === null ? "no buttons" : fp.roundness.toFixed(2)}`,
|
|
37
|
-
` motion ${Math.round(fp.motion * 100)}% of sections below the fold reveal on scroll`,
|
|
38
|
-
` effects ${fp.effects.join(", ") || "none"}`,
|
|
39
|
-
` layout ${fp.layout.join(" > ") || "none"}`
|
|
40
|
-
].join("\n");
|
|
41
|
-
}
|
|
42
|
-
function merge(a, b) {
|
|
43
|
-
const byTell = { ...a.summary.byTell };
|
|
44
|
-
for (const [k, v] of Object.entries(b.summary.byTell)) byTell[k] = (byTell[k] ?? 0) + v;
|
|
45
|
-
return {
|
|
46
|
-
catalogueVersion: a.catalogueVersion,
|
|
47
|
-
findings: [...a.findings, ...b.findings],
|
|
48
|
-
rejectedExceptions: a.rejectedExceptions,
|
|
49
|
-
excepted: [...a.excepted, ...b.excepted],
|
|
50
|
-
suppressed: [...a.suppressed, ...b.suppressed],
|
|
51
|
-
summary: {
|
|
52
|
-
files: a.summary.files + b.summary.files,
|
|
53
|
-
findings: a.summary.findings + b.summary.findings,
|
|
54
|
-
byGeneration: { 1: a.summary.byGeneration[1] + b.summary.byGeneration[1], 2: a.summary.byGeneration[2] + b.summary.byGeneration[2], 3: a.summary.byGeneration[3] + b.summary.byGeneration[3] },
|
|
55
|
-
byTell,
|
|
56
|
-
blocking: a.summary.blocking + b.summary.blocking
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function readSnapshot(path) {
|
|
61
|
-
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
62
|
-
if (data.version !== SNAPSHOT_VERSION) throw new Error(`${path} is snapshot version ${String(data.version)}; this craft reads ${SNAPSHOT_VERSION}`);
|
|
63
|
-
return data;
|
|
64
|
-
}
|
|
65
|
-
async function runAudit(command, args, io) {
|
|
66
|
-
try {
|
|
67
|
-
const flags = parseFlags(args);
|
|
68
|
-
if (typeof flags === "string") throw new Error(flags);
|
|
69
|
-
const target = flags.positional[0];
|
|
70
|
-
if (!target) throw new Error(`usage: craft ${command} <url${command === "audit" ? " | snapshot.json" : ""}>`);
|
|
71
|
-
const viewport = flags.width || flags.height ? { width: Number(flags.width ?? 1440), height: Number(flags.height ?? 900) } : void 0;
|
|
72
|
-
const local = resolve(io.cwd, target);
|
|
73
|
-
const snapshot = command === "audit" && /\.json$/i.test(target) && existsSync(local) ? readSnapshot(local) : await snapshotUrl(target, { viewport });
|
|
74
|
-
if (flags.out) writeFileSync(resolve(io.cwd, flags.out), `${JSON.stringify(snapshot, null, 2)}
|
|
75
|
-
`);
|
|
76
|
-
if (command === "snapshot") {
|
|
77
|
-
if (!flags.out) io.out(JSON.stringify(snapshot, null, 2));
|
|
78
|
-
else io.out(`craft snapshot: ${snapshot.url} saved to ${flags.out}`);
|
|
79
|
-
return 0;
|
|
80
|
-
}
|
|
81
|
-
const exceptions = loadExceptions(flags, flags.repo ? resolve(io.cwd, flags.repo) : io.cwd);
|
|
82
|
-
let report = auditSnapshot(snapshot, { exceptions });
|
|
83
|
-
if (flags.repo) {
|
|
84
|
-
const repo = resolve(io.cwd, flags.repo);
|
|
85
|
-
report = merge(scanSource(readPaths(["."], repo, SOURCE_FILE), { exceptions }), report);
|
|
86
|
-
}
|
|
87
|
-
const fp = fingerprint(snapshot);
|
|
88
|
-
const nulls = flags.null ? flags.null.split(",").map((p) => loadNull(p.trim(), io.cwd)) : [];
|
|
89
|
-
const typical = nulls.length ? typicality(fp, nulls.flatMap((m, i) => m.runs.map((r) => nulls.length > 1 ? { ...r, id: `${i + 1}/${r.id}` } : r))) : null;
|
|
90
|
-
if (flags.json) {
|
|
91
|
-
io.out(JSON.stringify({ report, fingerprint: fp, typicality: typical, snapshot: flags.out ?? null }, null, 2));
|
|
92
|
-
return report.summary.blocking > 0 || flags.strict && report.summary.findings > 0 ? 1 : 0;
|
|
93
|
-
}
|
|
94
|
-
const code = finish(report, { ...flags, json: false }, `craft audit ${snapshot.url}`, io);
|
|
95
|
-
io.out("");
|
|
96
|
-
io.out(describeFingerprint(fp));
|
|
97
|
-
if (typical) {
|
|
98
|
-
io.out("");
|
|
99
|
-
io.out(describeTypicality(typical, nulls.reduce((s, m) => s + m.runs.length, 0), nulls.length));
|
|
100
|
-
}
|
|
101
|
-
return code;
|
|
102
|
-
} catch (error) {
|
|
103
|
-
io.err(`craft: ${error.message}`);
|
|
104
|
-
return 2;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export {
|
|
109
|
-
describeFingerprint,
|
|
110
|
-
merge,
|
|
111
|
-
runAudit
|
|
112
|
-
};
|
package/dist/chunk-YLQE35PN.js
DELETED
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CATALOGUE,
|
|
3
|
-
CATALOGUE_VERSION,
|
|
4
|
-
applyHouseGate,
|
|
5
|
-
checkCopy,
|
|
6
|
-
extractCopy,
|
|
7
|
-
extractStrings,
|
|
8
|
-
fileKind,
|
|
9
|
-
houseRule,
|
|
10
|
-
scanSource
|
|
11
|
-
} from "./chunk-ZT3WLN7R.js";
|
|
12
|
-
|
|
13
|
-
// src/character/cli.ts
|
|
14
|
-
import { execFileSync } from "child_process";
|
|
15
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
16
|
-
import { join, relative, resolve } from "path";
|
|
17
|
-
|
|
18
|
-
// src/character/facts.ts
|
|
19
|
-
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)?)";
|
|
20
|
-
var PATTERNS = [
|
|
21
|
-
["url", /\bhttps?:\/\/[^\s<>"'`)\]]+|\bwww\.[^\s<>"'`)\]]+/gi],
|
|
22
|
-
["email", /\b[\w.+-]+@[\w-]+(?:\.[\w-]+)+\b/g],
|
|
23
|
-
["phone", /(?:\+44\s?\(?0?\)?\s?|\b0)\d(?:[\s-]?\d){8,9}\b/g],
|
|
24
|
-
["postcode", /\b[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}\b/g],
|
|
25
|
-
["money", /[£$€]\s?\d[\d,]*(?:\.\d{1,2})?(?:\s?(?:k|m|bn)\b)?|\b\d[\d,]*(?:\.\d{1,2})?\s?(?:pounds|GBP)\b/gi],
|
|
26
|
-
["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")],
|
|
27
|
-
["time", /\b\d{1,2}(?:[:.]\d{2})?\s?(?:am|pm)\b|\b\d{1,2}:\d{2}\b/gi],
|
|
28
|
-
["number", /\b\d[\d,]*(?:\.\d+)?%?|\b\d+(?:\.\d+)?\s?(?:per ?cent)\b/gi]
|
|
29
|
-
];
|
|
30
|
-
var NAME = /[A-Z][a-zA-Z’'&-]*[a-zA-Z](?:\s+(?:of|and|&|the)?\s*[A-Z][a-zA-Z’'&-]*[a-zA-Z])*/g;
|
|
31
|
-
var normalise = (kind, value) => {
|
|
32
|
-
const v = value.trim();
|
|
33
|
-
if (kind === "phone") return v.replace(/[^\d+]/g, "").replace(/^\+44\(?0?\)?/, "0").replace(/^\+44/, "0");
|
|
34
|
-
if (kind === "money" || kind === "number") return v.replace(/[\s,]/g, "").toLowerCase();
|
|
35
|
-
if (kind === "postcode") return v.replace(/\s/g, "").toUpperCase();
|
|
36
|
-
if (kind === "url") return v.replace(/[.,;:]+$/, "").toLowerCase();
|
|
37
|
-
return v.replace(/\s+/g, " ").toLowerCase();
|
|
38
|
-
};
|
|
39
|
-
function visibleText(file) {
|
|
40
|
-
const prose = /\.(md|markdown|mdx|txt)$/i.test(file.path);
|
|
41
|
-
return (prose ? extractCopy(file) : extractStrings(file)).map((b) => b.text).join("\n");
|
|
42
|
-
}
|
|
43
|
-
function protectedFacts(text) {
|
|
44
|
-
const facts = [];
|
|
45
|
-
let rest = text;
|
|
46
|
-
const mask = (start, length) => {
|
|
47
|
-
rest = rest.slice(0, start) + " ".repeat(length) + rest.slice(start + length);
|
|
48
|
-
};
|
|
49
|
-
for (const [kind, pattern] of PATTERNS) {
|
|
50
|
-
for (const m of [...rest.matchAll(pattern)]) {
|
|
51
|
-
const value = m[0].replace(/[.,;:]+$/, "");
|
|
52
|
-
facts.push({ kind, value, key: `${kind}:${normalise(kind, value)}` });
|
|
53
|
-
mask(m.index ?? 0, m[0].length);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const lines = rest.split("\n");
|
|
57
|
-
for (const line of lines) {
|
|
58
|
-
if (/^\s{0,3}#{1,6}\s/.test(line)) continue;
|
|
59
|
-
for (const m of line.matchAll(NAME)) {
|
|
60
|
-
const at = m.index ?? 0;
|
|
61
|
-
const before = line.slice(0, at).trimEnd();
|
|
62
|
-
const opensSentence = before === "" || /[.!?:"“(>*-]$/.test(before) || /^\s*(?:[-*+]|\d+\.)$/.test(before);
|
|
63
|
-
const word = m[0];
|
|
64
|
-
const acronym = /^[A-Z]{2,}$/.test(word);
|
|
65
|
-
if (opensSentence && !acronym && !/\s/.test(word)) continue;
|
|
66
|
-
if (word === "I") continue;
|
|
67
|
-
facts.push({ kind: "name", value: word, key: `name:${normalise("name", word)}` });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return facts;
|
|
71
|
-
}
|
|
72
|
-
var unique = (facts) => [...new Map(facts.map((f) => [f.key, f])).values()];
|
|
73
|
-
function compareFacts(before, after) {
|
|
74
|
-
const a = unique(protectedFacts(before));
|
|
75
|
-
const b = unique(protectedFacts(after));
|
|
76
|
-
const inA = new Set(a.map((f) => f.key));
|
|
77
|
-
const inB = new Set(b.map((f) => f.key));
|
|
78
|
-
return {
|
|
79
|
-
before: a.length,
|
|
80
|
-
after: b.length,
|
|
81
|
-
lost: a.filter((f) => !inB.has(f.key)),
|
|
82
|
-
added: b.filter((f) => !inA.has(f.key))
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function formatComparison(result, beforePath, afterPath) {
|
|
86
|
-
const lines = [`craft copy compare: ${beforePath} -> ${afterPath}`, ` ${result.before} protected facts before, ${result.after} after`];
|
|
87
|
-
if (result.lost.length) {
|
|
88
|
-
lines.push("", ` Lost (${result.lost.length}): restore each, or say why it went`);
|
|
89
|
-
for (const f of result.lost) lines.push(` ${f.kind.padEnd(9)} ${f.value}`);
|
|
90
|
-
}
|
|
91
|
-
if (result.added.length) {
|
|
92
|
-
lines.push("", ` Added (${result.added.length}): each needs a source, or it is invented`);
|
|
93
|
-
for (const f of result.added) lines.push(` ${f.kind.padEnd(9)} ${f.value}`);
|
|
94
|
-
}
|
|
95
|
-
if (!result.lost.length && !result.added.length) lines.push("", " Every fact kept, nothing added.");
|
|
96
|
-
return lines.join("\n");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// src/character/format.ts
|
|
100
|
-
function formatReport(report, title) {
|
|
101
|
-
const lines = [];
|
|
102
|
-
let current = "";
|
|
103
|
-
for (const f of report.findings) {
|
|
104
|
-
if (f.path !== current) {
|
|
105
|
-
current = f.path;
|
|
106
|
-
lines.push("", f.path);
|
|
107
|
-
}
|
|
108
|
-
lines.push(` ${String(f.line).padStart(4)} ${f.tell} (gen ${f.generation}, ${f.severity}) ${f.message}`);
|
|
109
|
-
lines.push(` ${f.excerpt}`);
|
|
110
|
-
}
|
|
111
|
-
const fixes = new Map(report.findings.map((f) => [f.tell, f.fix]));
|
|
112
|
-
if (fixes.size > 0) {
|
|
113
|
-
lines.push("", "What to do instead:");
|
|
114
|
-
for (const [tell, fix] of fixes) lines.push(` ${tell}: ${fix}`);
|
|
115
|
-
}
|
|
116
|
-
for (const r of report.rejectedExceptions) {
|
|
117
|
-
lines.push("", `Exception not applied (${r.exception.tell}): ${r.reason}`);
|
|
118
|
-
}
|
|
119
|
-
for (const e of report.excepted) {
|
|
120
|
-
lines.push("", `Excepted: ${e.count} \xD7 ${e.tell}, because ${e.because}`);
|
|
121
|
-
}
|
|
122
|
-
if (report.suppressed.length > 0) {
|
|
123
|
-
lines.push("", `Suppressed: ${report.suppressed.length} marked copy-ok or craft-ok (${report.suppressed.map((s) => `${s.path}:${s.line}`).join(", ")})`);
|
|
124
|
-
}
|
|
125
|
-
const { summary } = report;
|
|
126
|
-
const gens = `gen 1: ${summary.byGeneration[1]}, gen 2: ${summary.byGeneration[2]}, gen 3: ${summary.byGeneration[3]}`;
|
|
127
|
-
lines.push(
|
|
128
|
-
"",
|
|
129
|
-
`${title}: ${summary.findings} finding${summary.findings === 1 ? "" : "s"} in ${summary.files} file${summary.files === 1 ? "" : "s"} (${gens}). Catalogue ${report.catalogueVersion}.`
|
|
130
|
-
);
|
|
131
|
-
return lines.join("\n").replace(/^\n/, "");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// src/character/cli.ts
|
|
135
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", ".next", ".turbo", ".vercel", "coverage", "vendor", ".cache"]);
|
|
136
|
-
var MAX_BYTES = 1e6;
|
|
137
|
-
var NOT_SITE_COPY = /^(?:README|CHANGELOG|CLAUDE|AGENTS|CONTRIBUTING|LICENSE|CODE_OF_CONDUCT|SECURITY)(?:\.[a-z]+)?$/i;
|
|
138
|
-
var HELP = `craft: find the AI look and say what to do instead.
|
|
139
|
-
|
|
140
|
-
Usage
|
|
141
|
-
craft scan [paths...] [--staged] [--json] [--strict] [--direction <file>]
|
|
142
|
-
craft copy <paths...> [--gate] [--json] [--strict] [--direction <file>]
|
|
143
|
-
craft copy compare <before> <after> [--json]
|
|
144
|
-
craft tells list [--json]
|
|
145
|
-
craft tells harvest <null.json | dir>... [--share 0.25] [--json]
|
|
146
|
-
craft snapshot <url> [--out <file>] [--width <px>] [--height <px>]
|
|
147
|
-
craft audit <url | snapshot.json> [--repo <dir>] [--null <null.json>] [--out <file>] [--json] [--strict]
|
|
148
|
-
craft direction init [--snapshot <file>] [--client <name>] [--out <file>]
|
|
149
|
-
craft direction validate [--snapshot <file>] [--direction <file>] [--json]
|
|
150
|
-
craft direction propose [--snapshot <file>] [--estate <estate.json | dir>] [--out <file>]
|
|
151
|
-
craft null build --brief "<text>" --out <dir> [--runs 20] [--parallel 4] [--model <name>]
|
|
152
|
-
craft estate add <url | snapshot.json> --id <id> [--client <name>] [--estate <file>]
|
|
153
|
-
craft estate compare [<url | snapshot.json | id>] [--null <dir>] [--json] [--strict]
|
|
154
|
-
craft report <url | snapshot.json> [--repo <dir>] [--null <dir>] [--estate <file>] [--direction <file>] [--json] [--strict]
|
|
155
|
-
craft retrofit <url | snapshot.json> [the same] [--out RETROFIT.md]
|
|
156
|
-
|
|
157
|
-
scan Markup, component code and stylesheets (source and compiled CSS).
|
|
158
|
-
--staged reads the git index, for a pre-commit hook.
|
|
159
|
-
copy The prose in Markdown, markup and content files.
|
|
160
|
-
--gate applies the house copy policy: the blocking tier of COPY.md
|
|
161
|
-
fails the run. What copy-check and the pre-commit hook run.
|
|
162
|
-
compare lists the protected facts (prices, numbers, dates, times,
|
|
163
|
-
phones, emails, links, postcodes, names) a rewrite lost or added.
|
|
164
|
-
Exit 1 if any: restore it, source it, or say why.
|
|
165
|
-
tells The catalogue this build judges against. harvest reads null models
|
|
166
|
-
for choices the model keeps making that the catalogue does not know.
|
|
167
|
-
snapshot Render a page in a browser and save what it looks like, as JSON.
|
|
168
|
-
audit Judge a rendered page (live, or a saved snapshot) and fingerprint it.
|
|
169
|
-
--repo also scans that site's source, so one report covers both.
|
|
170
|
-
--null scores how typical it is against a null model (comma-separate
|
|
171
|
-
several to pool them).
|
|
172
|
-
snapshot and audit need Playwright: npm i -D playwright.
|
|
173
|
-
direction The site's art-direction.json: every choice with a reason from the
|
|
174
|
-
client's world. init writes today's choices with empty reasons;
|
|
175
|
-
validate applies the reason rule; propose reads colours off the
|
|
176
|
-
sources' PNG photos and drafts choices for a person to confirm.
|
|
177
|
-
null The counterfactual: about 20 pages \`claude -p\` builds from the brief
|
|
178
|
-
alone, snapshotted and fingerprinted into null.json. Resumable.
|
|
179
|
-
estate The register of shipped sites (estate.json). add fingerprints a site;
|
|
180
|
-
compare lists the nearest, and marks siblings: two sites closer than
|
|
181
|
-
two pages Claude builds for one brief. --strict exits 1 on a sibling.
|
|
182
|
-
report Every signal for one site: tells, typicality, the estate, the reasons.
|
|
183
|
-
A verdict (default, mixed, decided, unproven) and what to do, in order.
|
|
184
|
-
Anything not given is "not measured", and never counts as a pass.
|
|
185
|
-
retrofit The report as a checklist: decide, then change type, colour, shape,
|
|
186
|
-
effects, motion and copy. Never the page grammar.
|
|
187
|
-
|
|
188
|
-
Exceptions come from art-direction.json in the working directory, or --direction.
|
|
189
|
-
Every tell ships as warn: exit 1 only on a block, or on any finding with --strict.`;
|
|
190
|
-
var VALUE_FLAGS = {
|
|
191
|
-
"--direction": "direction",
|
|
192
|
-
"--out": "out",
|
|
193
|
-
"--repo": "repo",
|
|
194
|
-
"--width": "width",
|
|
195
|
-
"--height": "height",
|
|
196
|
-
"--snapshot": "snapshot",
|
|
197
|
-
"--estate": "estate",
|
|
198
|
-
"--client": "client",
|
|
199
|
-
"--brief": "brief",
|
|
200
|
-
"--runs": "runs",
|
|
201
|
-
"--parallel": "parallel",
|
|
202
|
-
"--model": "model",
|
|
203
|
-
"--share": "share",
|
|
204
|
-
"--null": "null",
|
|
205
|
-
"--id": "id"
|
|
206
|
-
};
|
|
207
|
-
function parseFlags(args) {
|
|
208
|
-
const flags = { positional: [], json: false, strict: false, staged: false, gate: false };
|
|
209
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
210
|
-
const a = args[i];
|
|
211
|
-
if (a === "--json") flags.json = true;
|
|
212
|
-
else if (a === "--strict") flags.strict = true;
|
|
213
|
-
else if (a === "--staged") flags.staged = true;
|
|
214
|
-
else if (a === "--gate") flags.gate = true;
|
|
215
|
-
else if (a in VALUE_FLAGS) {
|
|
216
|
-
const value = args[i + 1];
|
|
217
|
-
i += 1;
|
|
218
|
-
if (!value || value.startsWith("--")) return `${a} needs a value`;
|
|
219
|
-
flags[VALUE_FLAGS[a]] = value;
|
|
220
|
-
} else if (a.startsWith("--")) return `unknown option ${a}`;
|
|
221
|
-
else flags.positional.push(a);
|
|
222
|
-
}
|
|
223
|
-
return flags;
|
|
224
|
-
}
|
|
225
|
-
function walk(root, cwd, into, wanted, skipDir = (_) => false) {
|
|
226
|
-
const stat = statSync(root);
|
|
227
|
-
if (stat.isFile()) {
|
|
228
|
-
if (stat.size <= MAX_BYTES) into.push({ path: relative(cwd, root) || root, text: readFileSync(root, "utf8") });
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
232
|
-
if (entry.isDirectory()) {
|
|
233
|
-
if (!SKIP_DIRS.has(entry.name) && !skipDir(entry.name)) walk(join(root, entry.name), cwd, into, wanted, skipDir);
|
|
234
|
-
} else if (entry.isFile() && wanted(entry.name)) {
|
|
235
|
-
walk(join(root, entry.name), cwd, into, wanted, skipDir);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function readPaths(paths, cwd, wanted, skipDir) {
|
|
240
|
-
const files = [];
|
|
241
|
-
for (const p of paths) {
|
|
242
|
-
const abs = resolve(cwd, p);
|
|
243
|
-
if (!existsSync(abs)) throw new Error(`no such path: ${p}`);
|
|
244
|
-
walk(abs, cwd, files, wanted, skipDir);
|
|
245
|
-
}
|
|
246
|
-
return files;
|
|
247
|
-
}
|
|
248
|
-
function readStaged(cwd, wanted) {
|
|
249
|
-
const names = execFileSync("git", ["diff", "--cached", "--name-only", "--diff-filter=ACMR", "-z"], { cwd, encoding: "utf8" }).split("\0").filter((p) => p && wanted(p));
|
|
250
|
-
return names.map((path) => ({
|
|
251
|
-
path,
|
|
252
|
-
// The index, not the working tree: the commit is what gets checked.
|
|
253
|
-
text: execFileSync("git", ["show", `:${path}`], { cwd, encoding: "utf8", maxBuffer: MAX_BYTES * 4 })
|
|
254
|
-
}));
|
|
255
|
-
}
|
|
256
|
-
function loadExceptions(flags, cwd) {
|
|
257
|
-
const path = resolve(cwd, flags.direction ?? "art-direction.json");
|
|
258
|
-
if (!existsSync(path)) {
|
|
259
|
-
if (flags.direction) throw new Error(`no such file: ${flags.direction}`);
|
|
260
|
-
return [];
|
|
261
|
-
}
|
|
262
|
-
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
263
|
-
return Array.isArray(data.exceptions) ? data.exceptions : [];
|
|
264
|
-
}
|
|
265
|
-
function finish(report, flags, title, io) {
|
|
266
|
-
io.out(flags.json ? JSON.stringify(report, null, 2) : formatReport(report, title));
|
|
267
|
-
if (report.summary.blocking > 0) return 1;
|
|
268
|
-
if (flags.strict && report.summary.findings > 0) return 1;
|
|
269
|
-
return 0;
|
|
270
|
-
}
|
|
271
|
-
var SOURCE_FILE = (p) => ["markup", "script", "css"].includes(fileKind(p)) && !/\.d\.ts$|\.min\.js$/.test(p);
|
|
272
|
-
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);
|
|
273
|
-
var NOT_COPY_DIR = (name) => name.startsWith("_");
|
|
274
|
-
function run(argv, io) {
|
|
275
|
-
const [command, ...rest] = argv;
|
|
276
|
-
if (command === "direction") {
|
|
277
|
-
return import("./cli-3XPEGBUR.js").then((m) => m.runDirection(rest, io));
|
|
278
|
-
}
|
|
279
|
-
if (command === "report" || command === "retrofit") {
|
|
280
|
-
return import("./cli-V55MTIOP.js").then((m) => m.runReport(command, rest, io));
|
|
281
|
-
}
|
|
282
|
-
if (command === "estate") {
|
|
283
|
-
return import("./cli-J7C6TI2B.js").then((m) => m.runEstate(rest, io));
|
|
284
|
-
}
|
|
285
|
-
if (command === "null") {
|
|
286
|
-
return import("./cli-4VAOH7ST.js").then((m) => m.runNull(rest, io));
|
|
287
|
-
}
|
|
288
|
-
if (command === "tells" && rest[0] === "harvest") {
|
|
289
|
-
return import("./cli-4VAOH7ST.js").then((m) => m.runHarvest(rest, io)).catch((error) => {
|
|
290
|
-
io.err(`craft: ${error.message}`);
|
|
291
|
-
return 2;
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
if (command === "snapshot" || command === "audit") {
|
|
295
|
-
return import("./cli-WDSVVHKG.js").then((m) => m.runAudit(command, rest, io));
|
|
296
|
-
}
|
|
297
|
-
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
298
|
-
io.out(HELP);
|
|
299
|
-
return command ? 0 : 2;
|
|
300
|
-
}
|
|
301
|
-
try {
|
|
302
|
-
if (command === "tells") {
|
|
303
|
-
const flags = parseFlags(rest);
|
|
304
|
-
if (typeof flags === "string") throw new Error(flags);
|
|
305
|
-
if (flags.positional[0] !== "list") throw new Error("usage: craft tells list [--json] | craft tells harvest <null.json>...");
|
|
306
|
-
if (flags.json) {
|
|
307
|
-
const data = CATALOGUE.map(({ id, name, generation, surface, severity, why, fix }) => ({
|
|
308
|
-
id,
|
|
309
|
-
name,
|
|
310
|
-
generation,
|
|
311
|
-
surface,
|
|
312
|
-
severity,
|
|
313
|
-
why,
|
|
314
|
-
fix,
|
|
315
|
-
...surface === "copy" ? { house: houseRule(id, name).tier, houseLabel: houseRule(id, name).label } : {}
|
|
316
|
-
}));
|
|
317
|
-
io.out(JSON.stringify({ version: CATALOGUE_VERSION, tells: data }, null, 2));
|
|
318
|
-
} else {
|
|
319
|
-
io.out(`Catalogue ${CATALOGUE_VERSION}`);
|
|
320
|
-
for (const t of CATALOGUE) io.out(` ${t.id.padEnd(24)} gen ${t.generation} ${t.surface.padEnd(6)} ${t.name}`);
|
|
321
|
-
}
|
|
322
|
-
return 0;
|
|
323
|
-
}
|
|
324
|
-
if (command === "copy" && rest[0] === "compare") {
|
|
325
|
-
const flags = parseFlags(rest.slice(1));
|
|
326
|
-
if (typeof flags === "string") throw new Error(flags);
|
|
327
|
-
if (flags.positional.length !== 2) throw new Error("usage: craft copy compare <before> <after> [--json]");
|
|
328
|
-
const [before, after] = flags.positional.map((p) => {
|
|
329
|
-
const abs = resolve(io.cwd, p);
|
|
330
|
-
if (!existsSync(abs)) throw new Error(`no such path: ${p}`);
|
|
331
|
-
return visibleText({ path: p, text: readFileSync(abs, "utf8") });
|
|
332
|
-
});
|
|
333
|
-
const result = compareFacts(before, after);
|
|
334
|
-
io.out(flags.json ? JSON.stringify(result, null, 2) : formatComparison(result, flags.positional[0], flags.positional[1]));
|
|
335
|
-
return result.lost.length || result.added.length ? 1 : 0;
|
|
336
|
-
}
|
|
337
|
-
if (command === "scan" || command === "copy") {
|
|
338
|
-
const flags = parseFlags(rest);
|
|
339
|
-
if (typeof flags === "string") throw new Error(flags);
|
|
340
|
-
const wanted = command === "scan" ? SOURCE_FILE : COPY_FILE;
|
|
341
|
-
if (command === "copy" && flags.positional.length === 0 && !flags.staged) throw new Error("usage: craft copy <paths...>");
|
|
342
|
-
const files = flags.staged ? readStaged(io.cwd, wanted) : readPaths(flags.positional.length > 0 ? flags.positional : ["."], io.cwd, wanted, command === "copy" ? NOT_COPY_DIR : void 0);
|
|
343
|
-
const exceptions = loadExceptions(flags, io.cwd);
|
|
344
|
-
let report = command === "scan" ? scanSource(files, { exceptions }) : checkCopy(files, { exceptions });
|
|
345
|
-
if (command === "copy" && flags.gate) report = applyHouseGate(report);
|
|
346
|
-
return finish(report, flags, command === "scan" ? "craft scan" : "craft copy", io);
|
|
347
|
-
}
|
|
348
|
-
throw new Error(`unknown command "${command}". Run craft --help.`);
|
|
349
|
-
} catch (error) {
|
|
350
|
-
io.err(`craft: ${error.message}`);
|
|
351
|
-
return 2;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export {
|
|
356
|
-
parseFlags,
|
|
357
|
-
readPaths,
|
|
358
|
-
loadExceptions,
|
|
359
|
-
finish,
|
|
360
|
-
SOURCE_FILE,
|
|
361
|
-
COPY_FILE,
|
|
362
|
-
NOT_COPY_DIR,
|
|
363
|
-
run
|
|
364
|
-
};
|
package/dist/cli-J7C6TI2B.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatMatches,
|
|
3
|
-
formatPairs,
|
|
4
|
-
loadEstate,
|
|
5
|
-
runEstate
|
|
6
|
-
} from "./chunk-5LI2VFN5.js";
|
|
7
|
-
import "./chunk-VKQDOUHO.js";
|
|
8
|
-
import "./chunk-YLQE35PN.js";
|
|
9
|
-
import "./chunk-EHNEGKFR.js";
|
|
10
|
-
import "./chunk-ZT3WLN7R.js";
|
|
11
|
-
export {
|
|
12
|
-
formatMatches,
|
|
13
|
-
formatPairs,
|
|
14
|
-
loadEstate,
|
|
15
|
-
runEstate
|
|
16
|
-
};
|
package/dist/cli-WDSVVHKG.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describeFingerprint,
|
|
3
|
-
merge,
|
|
4
|
-
runAudit
|
|
5
|
-
} from "./chunk-DAENYJUO.js";
|
|
6
|
-
import "./chunk-BYY32NIN.js";
|
|
7
|
-
import "./chunk-VKQDOUHO.js";
|
|
8
|
-
import "./chunk-YLQE35PN.js";
|
|
9
|
-
import "./chunk-EHNEGKFR.js";
|
|
10
|
-
import "./chunk-ZT3WLN7R.js";
|
|
11
|
-
export {
|
|
12
|
-
describeFingerprint,
|
|
13
|
-
merge,
|
|
14
|
-
runAudit
|
|
15
|
-
};
|