@domandigital/craft 0.15.0 → 0.17.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 +18 -0
- package/CHARACTER.md +51 -0
- package/COPY.md +5 -0
- package/README.md +8 -0
- package/REGISTERS.md +163 -0
- package/dist/{audit-MTUC24XU.js → audit-4SFSLCKY.js} +3 -2
- package/dist/audit.cjs +1 -1
- package/dist/audit.d.cts +7 -0
- package/dist/audit.d.ts +7 -0
- package/dist/audit.js +1 -1
- package/dist/chunk-4QWEAL4G.js +1595 -0
- package/dist/{chunk-YDURAOYR.js → chunk-6TSDYETQ.js} +4 -2
- package/dist/{chunk-2IQ5OXFR.js → chunk-DARKHE6A.js} +7 -5
- package/dist/{cli-FCKHYAVZ.js → chunk-NTIPJ5U7.js} +2 -185
- package/dist/{chunk-MUVFECFC.js → chunk-O5B5ELJH.js} +46 -21
- package/dist/chunk-QIEDBL4J.js +9 -0
- package/dist/{chunk-QU6Q2QZZ.js → chunk-RJVG5KA6.js} +8 -6
- package/dist/{chunk-OUQ25LY2.js → chunk-SL5VZJ2J.js} +1 -1
- package/dist/{chunk-LFNSB6W7.js → chunk-WFB6F3BN.js} +66 -1633
- package/dist/{chunk-RXXRXV3V.js → chunk-ZUYERAFM.js} +178 -18
- package/dist/{cli-FLG3ZCAF.js → cli-BN4IT6N2.js} +13 -5
- package/dist/{cli-22TSWAX6.js → cli-GXIW7F3E.js} +7 -5
- package/dist/cli-JB4X5LAG.js +1573 -0
- package/dist/{cli-5ACKICBO.js → cli-JZRQBCD4.js} +7 -5
- package/dist/cli-MDW7JTEF.js +246 -0
- package/dist/cli-V44QEVS6.js +193 -0
- package/dist/{cli-7OZCYOQY.js → cli-Y7Y5IROK.js} +8 -5
- package/dist/cli.js +4 -2
- package/dist/index.cjs +1597 -101
- package/dist/index.d.cts +311 -2
- package/dist/index.d.ts +311 -2
- package/dist/index.js +1569 -101
- package/package.json +3 -2
|
@@ -2,31 +2,42 @@ import {
|
|
|
2
2
|
generate
|
|
3
3
|
} from "./chunk-OQXTQQAU.js";
|
|
4
4
|
import {
|
|
5
|
-
parseFlags
|
|
5
|
+
parseFlags
|
|
6
|
+
} from "./chunk-O5B5ELJH.js";
|
|
7
|
+
import {
|
|
6
8
|
toJson
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QIEDBL4J.js";
|
|
8
10
|
import {
|
|
9
11
|
readSnapshot
|
|
10
12
|
} from "./chunk-WUACK2HX.js";
|
|
11
13
|
import {
|
|
12
14
|
CATALOGUE_VERSION,
|
|
15
|
+
MIN_RUNS,
|
|
13
16
|
NULL_VERSION,
|
|
14
17
|
auditSnapshot,
|
|
15
18
|
checkCopy,
|
|
16
19
|
extractHtml,
|
|
17
|
-
extractStrings,
|
|
18
20
|
fingerprint,
|
|
19
21
|
harvest,
|
|
20
22
|
hueSwatch,
|
|
21
23
|
nullPrompt,
|
|
22
24
|
scanSource
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-WFB6F3BN.js";
|
|
26
|
+
import {
|
|
27
|
+
extractStrings
|
|
28
|
+
} from "./chunk-4QWEAL4G.js";
|
|
24
29
|
|
|
25
30
|
// src/null/cli.ts
|
|
26
31
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
27
|
-
import {
|
|
32
|
+
import { createServer } from "http";
|
|
33
|
+
import { extname, join, relative, resolve, sep } from "path";
|
|
28
34
|
import { pathToFileURL } from "url";
|
|
29
35
|
var readJson = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
36
|
+
var NULL_USAGE = [
|
|
37
|
+
'usage: craft null build --brief "<text>" --out <dir> [--runs 20] [--parallel 4] [--model <name>]',
|
|
38
|
+
' craft null import <dir> --builder <name> --brief "<text>" [--out <dir>] [--model <name>]',
|
|
39
|
+
' craft null prompt --brief "<text>"'
|
|
40
|
+
].join("\n");
|
|
30
41
|
var pad = (n) => String(n).padStart(2, "0");
|
|
31
42
|
async function pool(items, size, work) {
|
|
32
43
|
const queue = [...items];
|
|
@@ -38,6 +49,15 @@ function nullPath(target, cwd) {
|
|
|
38
49
|
const abs = resolve(cwd, target);
|
|
39
50
|
return existsSync(abs) && statSync(abs).isDirectory() ? join(abs, "null.json") : abs;
|
|
40
51
|
}
|
|
52
|
+
function expandNullTargets(targets, cwd) {
|
|
53
|
+
return targets.flatMap((t) => {
|
|
54
|
+
const abs = resolve(cwd, t);
|
|
55
|
+
if (existsSync(abs) && statSync(abs).isDirectory() && !existsSync(join(abs, "null.json"))) {
|
|
56
|
+
return readdirSync(abs).sort().map((d) => join(abs, d, "null.json")).filter((p) => existsSync(p));
|
|
57
|
+
}
|
|
58
|
+
return [t];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
41
61
|
function loadNull(target, cwd) {
|
|
42
62
|
const path = nullPath(target, cwd);
|
|
43
63
|
if (!existsSync(path)) throw new Error(`no null model at ${target}. Build one with craft null build.`);
|
|
@@ -45,10 +65,12 @@ function loadNull(target, cwd) {
|
|
|
45
65
|
if (model.version !== NULL_VERSION) throw new Error(`${target} is null model version ${String(model.version)}; this craft reads ${NULL_VERSION}`);
|
|
46
66
|
return model;
|
|
47
67
|
}
|
|
48
|
-
function measureRun(id, html, snapshot) {
|
|
68
|
+
function measureRun(id, html, snapshot, styles = []) {
|
|
49
69
|
const file = { path: `${id}.html`, text: html };
|
|
50
|
-
|
|
51
|
-
const
|
|
70
|
+
snapshot = { ...snapshot, home: snapshot.home ?? true };
|
|
71
|
+
const tells = new Set([...auditSnapshot(snapshot).findings, ...scanSource([file, ...styles]).findings].map((f) => f.tell));
|
|
72
|
+
let copy = [...new Set(extractStrings(file).filter((b) => !b.literal).map((b) => b.text.replace(/\s+/g, " ").trim()).filter(Boolean))];
|
|
73
|
+
if (copy.length === 0) copy = [...new Set(snapshot.sections.flatMap((s) => s.text ?? []).map((t) => t.replace(/\s+/g, " ").trim()).filter(Boolean))];
|
|
52
74
|
return { id, fingerprint: fingerprint(snapshot), tells: [...tells].sort(), copy };
|
|
53
75
|
}
|
|
54
76
|
async function build(flags, io) {
|
|
@@ -98,7 +120,7 @@ async function build(flags, io) {
|
|
|
98
120
|
const toShoot = ids.filter((id) => existsSync(join(pages, `${id}.html`)) && !existsSync(join(shots, `${id}.json`)));
|
|
99
121
|
if (toShoot.length) {
|
|
100
122
|
io.out(`craft null: snapshotting ${toShoot.length} page${toShoot.length === 1 ? "" : "s"}`);
|
|
101
|
-
const { snapshotUrls } = await import("./audit-
|
|
123
|
+
const { snapshotUrls } = await import("./audit-4SFSLCKY.js");
|
|
102
124
|
const results = await snapshotUrls(toShoot.map((id) => pathToFileURL(join(pages, `${id}.html`)).href));
|
|
103
125
|
results.forEach((r, i) => {
|
|
104
126
|
const id = toShoot[i];
|
|
@@ -127,6 +149,142 @@ async function build(flags, io) {
|
|
|
127
149
|
}
|
|
128
150
|
return 0;
|
|
129
151
|
}
|
|
152
|
+
function findImportPages(dir) {
|
|
153
|
+
const pages = [];
|
|
154
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
155
|
+
if (e.isFile() && /\.html?$/i.test(e.name)) pages.push({ id: e.name.replace(/\.html?$/i, ""), root: dir, entry: e.name });
|
|
156
|
+
else if (e.isDirectory() && existsSync(join(dir, e.name, "index.html"))) pages.push({ id: e.name, root: join(dir, e.name), entry: "index.html" });
|
|
157
|
+
}
|
|
158
|
+
const ids = pages.map((p) => p.id);
|
|
159
|
+
const twice = ids.find((id, i) => ids.indexOf(id) !== i);
|
|
160
|
+
if (twice) throw new Error(`"${twice}" is both a file and a folder; rename one`);
|
|
161
|
+
return pages.sort((a, b) => a.id.localeCompare(b.id));
|
|
162
|
+
}
|
|
163
|
+
var TYPES = {
|
|
164
|
+
".html": "text/html",
|
|
165
|
+
".htm": "text/html",
|
|
166
|
+
".css": "text/css",
|
|
167
|
+
".js": "text/javascript",
|
|
168
|
+
".mjs": "text/javascript",
|
|
169
|
+
".json": "application/json",
|
|
170
|
+
".svg": "image/svg+xml",
|
|
171
|
+
".png": "image/png",
|
|
172
|
+
".jpg": "image/jpeg",
|
|
173
|
+
".jpeg": "image/jpeg",
|
|
174
|
+
".webp": "image/webp",
|
|
175
|
+
".avif": "image/avif",
|
|
176
|
+
".gif": "image/gif",
|
|
177
|
+
".ico": "image/x-icon",
|
|
178
|
+
".woff": "font/woff",
|
|
179
|
+
".woff2": "font/woff2",
|
|
180
|
+
".ttf": "font/ttf",
|
|
181
|
+
".otf": "font/otf"
|
|
182
|
+
};
|
|
183
|
+
function serveDir(root) {
|
|
184
|
+
const server = createServer((req, res) => {
|
|
185
|
+
let file;
|
|
186
|
+
try {
|
|
187
|
+
file = resolve(root, `.${decodeURIComponent(new URL(req.url ?? "/", "http://local").pathname)}`);
|
|
188
|
+
} catch {
|
|
189
|
+
res.statusCode = 400;
|
|
190
|
+
return void res.end();
|
|
191
|
+
}
|
|
192
|
+
if (file !== root && !file.startsWith(root + sep)) {
|
|
193
|
+
res.statusCode = 403;
|
|
194
|
+
return void res.end();
|
|
195
|
+
}
|
|
196
|
+
if (existsSync(file) && statSync(file).isDirectory()) file = join(file, "index.html");
|
|
197
|
+
if (!existsSync(file)) {
|
|
198
|
+
res.statusCode = 404;
|
|
199
|
+
return void res.end();
|
|
200
|
+
}
|
|
201
|
+
res.setHeader("content-type", TYPES[extname(file).toLowerCase()] ?? "application/octet-stream");
|
|
202
|
+
res.end(readFileSync(file));
|
|
203
|
+
});
|
|
204
|
+
return new Promise((done, fail) => {
|
|
205
|
+
server.once("error", fail);
|
|
206
|
+
server.listen(
|
|
207
|
+
0,
|
|
208
|
+
"127.0.0.1",
|
|
209
|
+
() => done({
|
|
210
|
+
base: `http://127.0.0.1:${server.address().port}`,
|
|
211
|
+
close: () => new Promise((closed) => server.close(() => closed()))
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function stylesheets(root, id) {
|
|
217
|
+
const found = [];
|
|
218
|
+
const walk = (dir) => {
|
|
219
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
220
|
+
const path = join(dir, e.name);
|
|
221
|
+
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) walk(path);
|
|
222
|
+
else if (e.isFile() && e.name.endsWith(".css") && statSync(path).size <= 1e6) found.push({ path: `${id}/${relative(root, path)}`, text: readFileSync(path, "utf8") });
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
walk(root);
|
|
226
|
+
return found;
|
|
227
|
+
}
|
|
228
|
+
async function importPages(flags, io) {
|
|
229
|
+
const usage = 'usage: craft null import <dir> --builder <name> --brief "<who, where, what they do>" [--out <dir>] [--model <name>]';
|
|
230
|
+
const from = flags.positional[1];
|
|
231
|
+
const brief = flags.brief?.trim();
|
|
232
|
+
if (!from || flags.positional.length > 2 || !flags.builder || !brief || brief.split(/\s+/).length < 5) throw new Error(usage);
|
|
233
|
+
const dir = resolve(io.cwd, from);
|
|
234
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) throw new Error(`no directory at ${from}`);
|
|
235
|
+
const pages = findImportPages(dir);
|
|
236
|
+
if (pages.length < MIN_RUNS) throw new Error(`${from} holds ${pages.length} page${pages.length === 1 ? "" : "s"}; a null model needs at least ${MIN_RUNS}`);
|
|
237
|
+
const out = resolve(io.cwd, flags.out ?? from);
|
|
238
|
+
const shots = join(out, "snapshots");
|
|
239
|
+
mkdirSync(shots, { recursive: true });
|
|
240
|
+
const failed = [];
|
|
241
|
+
const toShoot = pages.filter((p) => !existsSync(join(shots, `${p.id}.json`)));
|
|
242
|
+
if (toShoot.length) {
|
|
243
|
+
io.out(`craft null: snapshotting ${toShoot.length} page${toShoot.length === 1 ? "" : "s"} from ${flags.builder}`);
|
|
244
|
+
const { snapshotUrls } = await import("./audit-4SFSLCKY.js");
|
|
245
|
+
const servers = /* @__PURE__ */ new Map();
|
|
246
|
+
try {
|
|
247
|
+
for (const p of toShoot) if (!servers.has(p.root)) servers.set(p.root, await serveDir(p.root));
|
|
248
|
+
const urls = toShoot.map((p) => `${servers.get(p.root)?.base}/${p.entry === "index.html" ? "" : encodeURIComponent(p.entry)}`);
|
|
249
|
+
const results = await snapshotUrls(urls);
|
|
250
|
+
results.forEach((r, i) => {
|
|
251
|
+
const p = toShoot[i];
|
|
252
|
+
if (r.snapshot) writeFileSync(join(shots, `${p.id}.json`), `${JSON.stringify({ ...r.snapshot, url: relative(out, join(p.root, p.entry)) }, null, 2)}
|
|
253
|
+
`);
|
|
254
|
+
else failed.push(`${p.id}: the page would not render: ${r.error}`);
|
|
255
|
+
});
|
|
256
|
+
} finally {
|
|
257
|
+
await Promise.all([...servers.values()].map((s) => s.close()));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const measured = pages.filter((p) => existsSync(join(shots, `${p.id}.json`))).map(
|
|
261
|
+
(p) => measureRun(
|
|
262
|
+
p.id,
|
|
263
|
+
readFileSync(join(p.root, p.entry), "utf8"),
|
|
264
|
+
readSnapshot(readJson(join(shots, `${p.id}.json`)), `${p.id}.json`),
|
|
265
|
+
p.root === dir ? [] : stylesheets(p.root, p.id)
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
if (measured.length < MIN_RUNS) {
|
|
269
|
+
for (const f of failed) io.err(` ${f}`);
|
|
270
|
+
throw new Error(`only ${measured.length} of ${pages.length} pages rendered; a null model needs at least ${MIN_RUNS}`);
|
|
271
|
+
}
|
|
272
|
+
const model = {
|
|
273
|
+
version: NULL_VERSION,
|
|
274
|
+
brief,
|
|
275
|
+
prompt: nullPrompt(brief),
|
|
276
|
+
model: flags.model ?? "unknown",
|
|
277
|
+
builder: flags.builder,
|
|
278
|
+
catalogueVersion: CATALOGUE_VERSION,
|
|
279
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
280
|
+
runs: measured
|
|
281
|
+
};
|
|
282
|
+
writeFileSync(join(out, "null.json"), `${JSON.stringify(model, null, 2)}
|
|
283
|
+
`);
|
|
284
|
+
io.out(`craft null: ${measured.length} of ${pages.length} pages from ${flags.builder} in ${relative(io.cwd, join(out, "null.json")) || "null.json"}`);
|
|
285
|
+
for (const f of failed) io.err(` ${f}`);
|
|
286
|
+
return measured.length < pages.length ? 1 : 0;
|
|
287
|
+
}
|
|
130
288
|
function copyTell(phrase) {
|
|
131
289
|
const text = `We found that ${phrase} was there.`;
|
|
132
290
|
const hit = checkCopy([{ path: "phrase.md", text }]).findings[0];
|
|
@@ -162,7 +320,13 @@ async function runNull(args, io) {
|
|
|
162
320
|
try {
|
|
163
321
|
const flags = parseFlags(args);
|
|
164
322
|
if (typeof flags === "string") throw new Error(flags);
|
|
165
|
-
if (flags.positional[0]
|
|
323
|
+
if (flags.positional[0] === "import") return await importPages(flags, io);
|
|
324
|
+
if (flags.positional[0] === "prompt") {
|
|
325
|
+
if (!flags.brief || flags.brief.trim().split(/\s+/).length < 5) throw new Error('usage: craft null prompt --brief "<who, where, what they do>"');
|
|
326
|
+
io.out(nullPrompt(flags.brief));
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
if (flags.positional[0] !== "build") throw new Error(NULL_USAGE);
|
|
166
330
|
return await build(flags, io);
|
|
167
331
|
} catch (error) {
|
|
168
332
|
io.err(`craft: ${error.message}`);
|
|
@@ -174,14 +338,7 @@ function runHarvest(args, io) {
|
|
|
174
338
|
if (typeof flags === "string") throw new Error(flags);
|
|
175
339
|
const targets = flags.positional.slice(1);
|
|
176
340
|
if (targets.length === 0) throw new Error("usage: craft tells harvest <null.json | dir>... [--share 0.25] [--json]");
|
|
177
|
-
const
|
|
178
|
-
const abs = resolve(io.cwd, t);
|
|
179
|
-
if (existsSync(abs) && statSync(abs).isDirectory() && !existsSync(join(abs, "null.json"))) {
|
|
180
|
-
return readdirSync(abs).map((d) => join(abs, d, "null.json")).filter((p) => existsSync(p));
|
|
181
|
-
}
|
|
182
|
-
return [t];
|
|
183
|
-
});
|
|
184
|
-
const models = paths.map((p) => loadNull(p, io.cwd));
|
|
341
|
+
const models = expandNullTargets(targets, io.cwd).map((p) => loadNull(p, io.cwd));
|
|
185
342
|
const minShare = flags.share === void 0 ? void 0 : Number(flags.share);
|
|
186
343
|
if (minShare !== void 0 && !(minShare > 0 && minShare <= 1)) throw new Error("--share needs a number above 0 and at most 1");
|
|
187
344
|
const candidates = harvest(models, { minShare, copyTell });
|
|
@@ -191,8 +348,11 @@ function runHarvest(args, io) {
|
|
|
191
348
|
|
|
192
349
|
export {
|
|
193
350
|
nullPath,
|
|
351
|
+
expandNullTargets,
|
|
194
352
|
loadNull,
|
|
195
353
|
measureRun,
|
|
354
|
+
findImportPages,
|
|
355
|
+
serveDir,
|
|
196
356
|
formatHarvest,
|
|
197
357
|
describeTypicality,
|
|
198
358
|
runNull,
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
2
|
describeTypicality,
|
|
3
|
+
expandNullTargets,
|
|
4
|
+
findImportPages,
|
|
3
5
|
formatHarvest,
|
|
4
6
|
loadNull,
|
|
5
7
|
measureRun,
|
|
6
8
|
nullPath,
|
|
7
9
|
runHarvest,
|
|
8
|
-
runNull
|
|
9
|
-
|
|
10
|
+
runNull,
|
|
11
|
+
serveDir
|
|
12
|
+
} from "./chunk-ZUYERAFM.js";
|
|
10
13
|
import "./chunk-OQXTQQAU.js";
|
|
11
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-O5B5ELJH.js";
|
|
15
|
+
import "./chunk-QIEDBL4J.js";
|
|
12
16
|
import "./chunk-WUACK2HX.js";
|
|
13
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-WFB6F3BN.js";
|
|
18
|
+
import "./chunk-4QWEAL4G.js";
|
|
14
19
|
import "./chunk-QYBAVU3P.js";
|
|
15
20
|
export {
|
|
16
21
|
describeTypicality,
|
|
22
|
+
expandNullTargets,
|
|
23
|
+
findImportPages,
|
|
17
24
|
formatHarvest,
|
|
18
25
|
loadNull,
|
|
19
26
|
measureRun,
|
|
20
27
|
nullPath,
|
|
21
28
|
runHarvest,
|
|
22
|
-
runNull
|
|
29
|
+
runNull,
|
|
30
|
+
serveDir
|
|
23
31
|
};
|
|
@@ -4,13 +4,15 @@ import {
|
|
|
4
4
|
formatPairs,
|
|
5
5
|
loadEstate,
|
|
6
6
|
runEstate
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-RJVG5KA6.js";
|
|
8
|
+
import "./chunk-6TSDYETQ.js";
|
|
9
|
+
import "./chunk-ZUYERAFM.js";
|
|
10
10
|
import "./chunk-OQXTQQAU.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-O5B5ELJH.js";
|
|
12
|
+
import "./chunk-QIEDBL4J.js";
|
|
12
13
|
import "./chunk-WUACK2HX.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-WFB6F3BN.js";
|
|
15
|
+
import "./chunk-4QWEAL4G.js";
|
|
14
16
|
import "./chunk-QYBAVU3P.js";
|
|
15
17
|
export {
|
|
16
18
|
formatComponentPairs,
|