@heroiclands/package-build 24.0.1 → 24.0.3

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/assets/preview/gitea.ini +1 -1
  3. package/assets/styles/content-base.css +33 -0
  4. package/bin/content-commands.mjs +17 -17
  5. package/content-config.mjs +1 -52
  6. package/docs/api.md +20 -13
  7. package/docs/authoring/links-and-markup.md +165 -17
  8. package/docs/commands.md +20 -20
  9. package/docs/configuration.md +29 -69
  10. package/docs/content-format.md +4 -2
  11. package/docs/guides/site.md +3 -1
  12. package/docs/guides/writing-in-invented-scripts.md +68 -6
  13. package/docs/reference/format-details.md +160 -118
  14. package/docs/reference/note-types.md +49 -29
  15. package/docs/source-preview.md +2 -2
  16. package/engine/content-blocks.mjs +6 -2
  17. package/engine/content-charset.mjs +1 -0
  18. package/engine/content-format.yaml +2 -0
  19. package/engine/content-index.mjs +5 -1
  20. package/engine/content-render.mjs +2 -1
  21. package/engine/content-scripts.mjs +268 -21
  22. package/engine/content-scroll.mjs +389 -0
  23. package/engine/dictionary-layout.mjs +16 -3
  24. package/engine/font-outlines.mjs +394 -0
  25. package/engine/foreign-catalog.mjs +11 -122
  26. package/engine/language-data.mjs +154 -63
  27. package/engine/language-rows.mjs +35 -9
  28. package/engine/language-terms.mjs +39 -9
  29. package/engine/map-lexicon.mjs +31 -6
  30. package/engine/note-vocabulary.mjs +58 -6
  31. package/engine/pdf-build.mjs +9 -178
  32. package/engine/pdf-fonts.mjs +1 -1
  33. package/engine/pdf-render.mjs +52 -11
  34. package/engine/run-rows.mjs +45 -3
  35. package/engine/script-direction.mjs +67 -0
  36. package/engine/script-emphasis-terms.mjs +11 -3
  37. package/engine/script-font-delivery.mjs +42 -9
  38. package/engine/script-fonts.mjs +7 -0
  39. package/engine/site-reach.mjs +23 -0
  40. package/engine/site-render.mjs +18 -3
  41. package/engine/verse-metre.mjs +1 -0
  42. package/engine/written-in.mjs +208 -0
  43. package/package.json +2 -1
  44. package/site-theme/static/css/style.css +7 -0
  45. package/types/content-config.d.mts +1 -19
  46. package/types/engine/content-scripts.d.mts +31 -6
  47. package/types/engine/content-scroll.d.mts +179 -0
  48. package/types/engine/dictionary-layout.d.mts +5 -0
  49. package/types/engine/font-outlines.d.mts +67 -0
  50. package/types/engine/foreign-catalog.d.mts +0 -38
  51. package/types/engine/language-data.d.mts +13 -0
  52. package/types/engine/language-rows.d.mts +5 -4
  53. package/types/engine/language-terms.d.mts +7 -0
  54. package/types/engine/map-lexicon.d.mts +4 -3
  55. package/types/engine/pdf-build.d.mts +5 -27
  56. package/types/engine/pdf-fonts.d.mts +10 -0
  57. package/types/engine/run-rows.d.mts +28 -6
  58. package/types/engine/script-direction.d.mts +10 -0
  59. package/types/engine/script-emphasis-terms.d.mts +10 -3
  60. package/types/engine/script-font-delivery.d.mts +5 -3
  61. package/types/engine/script-fonts.d.mts +9 -0
  62. package/types/engine/site-reach.d.mts +11 -0
  63. package/types/engine/written-in.d.mts +76 -0
  64. package/engine/asset-replacement.mjs +0 -93
  65. package/types/engine/asset-replacement.d.mts +0 -22
@@ -0,0 +1,394 @@
1
+ /*
2
+ * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
+ *
5
+ * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
+ * You may copy, modify, and distribute it under the terms of that license.
7
+ *
8
+ * For full terms, see the LICENSE.md file in the project root or visit:
9
+ * https://www.gnu.org/licenses/gpl-3.0.html
10
+ *
11
+ * SPDX-License-Identifier: GPL-3.0-or-later
12
+ */
13
+
14
+ /**
15
+ * **A face's outlines, drawn as SVG.** The book sets a `scroll` fence as panel
16
+ * images because Typst has no vertical writing, and draws each character from
17
+ * the face's own TrueType outlines, so a panel needs no renderer and no font
18
+ * on the machine that compiles the book.
19
+ *
20
+ * - **`cmap`** (formats 4 and 12) gives each code point its glyph.
21
+ * - **`loca` and `glyf`** give each glyph's quadratic contours, a composite
22
+ * glyph's components moved and scaled into place.
23
+ * - **`hmtx`** gives a glyph's width, which centres it in its column.
24
+ * - **`vmtx`**, where the face has it, gives the space above a glyph in a
25
+ * column one em tall; a face without it is centred on its ascender and
26
+ * descender.
27
+ *
28
+ * A panel is one SVG: each glyph drawn once as a path and placed by `<use>`,
29
+ * the columns read from the right, `rows` ems tall and
30
+ * {@link module:engine/content-scroll.SCROLL_PITCH} ems from column to column.
31
+ * A character the face does not draw, and whitespace, leave their cell empty.
32
+ *
33
+ * @module
34
+ */
35
+
36
+ import fs from "node:fs";
37
+
38
+ import { SCROLL_PITCH } from "./content-scroll.mjs";
39
+ import { tableDirectory } from "./pdf-fonts.mjs";
40
+
41
+ /**
42
+ * One glyph, in font units, its y axis pointing down from the baseline.
43
+ *
44
+ * @typedef {object} Glyph
45
+ * @property {number} index - Its glyph index.
46
+ * @property {string} d - Its outline as SVG path data; `""` for none.
47
+ * @property {number} advance - Its horizontal advance.
48
+ * @property {number} top - From the top of its cell to its baseline.
49
+ */
50
+
51
+ /**
52
+ * A face's outlines.
53
+ *
54
+ * @typedef {object} Outlines
55
+ * @property {number} unitsPerEm - Its units per em.
56
+ * @property {(point: number) => Glyph|undefined} glyph - The glyph a code
57
+ * point is drawn with, or `undefined` when the face does not draw it.
58
+ */
59
+
60
+ /** @type {Map<string, Outlines>} */
61
+ const cache = new Map();
62
+
63
+ /**
64
+ * Code point → glyph index, from a `cmap` of format 4 or 12.
65
+ *
66
+ * @param {Buffer} buf - The font file.
67
+ * @param {{offset: number}} table - The `cmap` record.
68
+ * @returns {Map<number, number>} Glyph indices by code point.
69
+ */
70
+ function codepointGlyphs(buf, table) {
71
+ const out = new Map();
72
+ const at = table.offset;
73
+ const numTables = buf.readUInt16BE(at + 2);
74
+ for (let i = 0; i < numTables; i += 1) {
75
+ const sub = at + buf.readUInt32BE(at + 4 + i * 8 + 4);
76
+ const format = buf.readUInt16BE(sub);
77
+ if (format === 4) {
78
+ const segX2 = buf.readUInt16BE(sub + 6);
79
+ const endAt = sub + 14;
80
+ const startAt = endAt + segX2 + 2;
81
+ const deltaAt = startAt + segX2;
82
+ const rangeAt = deltaAt + segX2;
83
+ for (let s = 0; s < segX2 / 2; s += 1) {
84
+ const end = buf.readUInt16BE(endAt + s * 2);
85
+ const start = buf.readUInt16BE(startAt + s * 2);
86
+ const delta = buf.readInt16BE(deltaAt + s * 2);
87
+ const rangeOffset = buf.readUInt16BE(rangeAt + s * 2);
88
+ if (start === 0xffff) continue;
89
+ for (let cp = start; cp <= end; cp += 1) {
90
+ let glyph;
91
+ if (rangeOffset === 0) glyph = (cp + delta) & 0xffff;
92
+ else {
93
+ glyph = buf.readUInt16BE(rangeAt + s * 2 + rangeOffset + (cp - start) * 2);
94
+ if (glyph) glyph = (glyph + delta) & 0xffff;
95
+ }
96
+ if (glyph && !out.has(cp)) out.set(cp, glyph);
97
+ }
98
+ }
99
+ } else if (format === 12) {
100
+ const groups = buf.readUInt32BE(sub + 12);
101
+ for (let g = 0; g < groups; g += 1) {
102
+ const row = sub + 16 + g * 12;
103
+ const start = buf.readUInt32BE(row);
104
+ const end = Math.min(buf.readUInt32BE(row + 4), start + 0x1_0000);
105
+ const first = buf.readUInt32BE(row + 8);
106
+ for (let cp = start; cp <= end; cp += 1)
107
+ if (!out.has(cp)) out.set(cp, first + (cp - start));
108
+ }
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+
114
+ /**
115
+ * A point of a contour.
116
+ *
117
+ * @typedef {{x: number, y: number, on: boolean}} Point
118
+ */
119
+
120
+ /**
121
+ * A glyph's contours, its components resolved.
122
+ *
123
+ * @param {Buffer} buf - The font file.
124
+ * @param {number} glyf - The `glyf` table's offset.
125
+ * @param {(index: number) => [number, number]} span - A glyph's offset in
126
+ * `glyf` and the offset past it.
127
+ * @param {number} index - The glyph.
128
+ * @param {number} depth - How deep in a composite this glyph sits.
129
+ * @returns {Point[][]} Its contours.
130
+ */
131
+ function contours(buf, glyf, span, index, depth = 0) {
132
+ const [from, to] = span(index);
133
+ if (to <= from || depth > 8) return [];
134
+ const at = glyf + from;
135
+ const count = buf.readInt16BE(at);
136
+ if (count >= 0) {
137
+ const ends = [];
138
+ for (let c = 0; c < count; c += 1) ends.push(buf.readUInt16BE(at + 10 + c * 2));
139
+ const points = count ? ends[count - 1] + 1 : 0;
140
+ let p = at + 10 + count * 2;
141
+ p += 2 + buf.readUInt16BE(p);
142
+ const flags = [];
143
+ while (flags.length < points) {
144
+ const flag = buf.readUInt8(p++);
145
+ flags.push(flag);
146
+ if (flag & 8) {
147
+ const repeat = buf.readUInt8(p++);
148
+ for (let r = 0; r < repeat; r += 1) flags.push(flag);
149
+ }
150
+ }
151
+ const read = (short, same) => {
152
+ const values = [];
153
+ let value = 0;
154
+ for (const flag of flags) {
155
+ if (flag & short) {
156
+ const delta = buf.readUInt8(p++);
157
+ value += flag & same ? delta : -delta;
158
+ } else if (!(flag & same)) {
159
+ value += buf.readInt16BE(p);
160
+ p += 2;
161
+ }
162
+ values.push(value);
163
+ }
164
+ return values;
165
+ };
166
+ const xs = read(2, 16);
167
+ const ys = read(4, 32);
168
+ const out = [];
169
+ let start = 0;
170
+ for (const end of ends) {
171
+ const contour = [];
172
+ for (let k = start; k <= end; k += 1)
173
+ contour.push({ x: xs[k], y: ys[k], on: Boolean(flags[k] & 1) });
174
+ out.push(contour);
175
+ start = end + 1;
176
+ }
177
+ return out;
178
+ }
179
+ const out = [];
180
+ let p = at + 10;
181
+ for (;;) {
182
+ const flags = buf.readUInt16BE(p);
183
+ const component = buf.readUInt16BE(p + 2);
184
+ p += 4;
185
+ let dx;
186
+ let dy;
187
+ if (flags & 1) {
188
+ dx = buf.readInt16BE(p);
189
+ dy = buf.readInt16BE(p + 2);
190
+ p += 4;
191
+ } else {
192
+ dx = buf.readInt8(p);
193
+ dy = buf.readInt8(p + 1);
194
+ p += 2;
195
+ }
196
+ if (!(flags & 2)) {
197
+ dx = 0;
198
+ dy = 0;
199
+ }
200
+ let [a, b, c, d] = [1, 0, 0, 1];
201
+ const f2dot14 = (offset) => buf.readInt16BE(offset) / 16384;
202
+ if (flags & 8) {
203
+ a = d = f2dot14(p);
204
+ p += 2;
205
+ } else if (flags & 0x40) {
206
+ a = f2dot14(p);
207
+ d = f2dot14(p + 2);
208
+ p += 4;
209
+ } else if (flags & 0x80) {
210
+ a = f2dot14(p);
211
+ b = f2dot14(p + 2);
212
+ c = f2dot14(p + 4);
213
+ d = f2dot14(p + 6);
214
+ p += 8;
215
+ }
216
+ for (const contour of contours(buf, glyf, span, component, depth + 1))
217
+ out.push(
218
+ contour.map(({ x, y, on }) => ({
219
+ x: a * x + c * y + dx,
220
+ y: b * x + d * y + dy,
221
+ on,
222
+ })),
223
+ );
224
+ if (!(flags & 0x20)) break;
225
+ }
226
+ return out;
227
+ }
228
+
229
+ /** A number as path data writes it. */
230
+ const num = (value) => String(Math.round(value * 100) / 100);
231
+
232
+ /**
233
+ * A contour of quadratic segments as SVG path data, its y axis flipped so it
234
+ * points down from the baseline.
235
+ *
236
+ * @param {Point[]} contour - The contour.
237
+ * @returns {string} The path data.
238
+ */
239
+ function contourPath(contour) {
240
+ if (!contour.length) return "";
241
+ const points = contour.map(({ x, y, on }) => ({ x, y: -y, on }));
242
+ const mid = (p, q) => ({ x: (p.x + q.x) / 2, y: (p.y + q.y) / 2, on: true });
243
+ let first = points.findIndex((point) => point.on);
244
+ let start;
245
+ let ordered;
246
+ if (first < 0) {
247
+ start = mid(points[points.length - 1], points[0]);
248
+ ordered = points;
249
+ } else {
250
+ start = points[first];
251
+ ordered = [...points.slice(first + 1), ...points.slice(0, first)];
252
+ }
253
+ let d = `M${num(start.x)} ${num(start.y)}`;
254
+ let control;
255
+ for (const point of [...ordered, start]) {
256
+ if (point.on) {
257
+ d +=
258
+ control ?
259
+ `Q${num(control.x)} ${num(control.y)} ${num(point.x)} ${num(point.y)}`
260
+ : `L${num(point.x)} ${num(point.y)}`;
261
+ control = undefined;
262
+ } else if (control) {
263
+ const between = mid(control, point);
264
+ d += `Q${num(control.x)} ${num(control.y)} ${num(between.x)} ${num(between.y)}`;
265
+ control = point;
266
+ } else control = point;
267
+ }
268
+ return `${d}Z`;
269
+ }
270
+
271
+ /**
272
+ * The outlines of a TrueType face, read once per file.
273
+ *
274
+ * @param {string} file - The `.ttf`.
275
+ * @returns {Outlines} Its outlines.
276
+ * @throws {Error} When the file is not a TrueType face with outlines.
277
+ */
278
+ export function fontOutlines(file) {
279
+ const known = cache.get(file);
280
+ if (known) return known;
281
+ const buf = fs.readFileSync(file);
282
+ const tables = tableDirectory(buf);
283
+ for (const tag of ["head", "maxp", "hhea", "hmtx", "loca", "glyf", "cmap"])
284
+ if (!tables.has(tag))
285
+ throw new Error(
286
+ `${file}: error: the face has no \`${tag}\` table to draw a scroll from`,
287
+ );
288
+ const head = /** @type {{offset: number}} */ (tables.get("head")).offset;
289
+ const unitsPerEm = buf.readUInt16BE(head + 18);
290
+ const longOffsets = buf.readInt16BE(head + 50) === 1;
291
+ const numGlyphs = buf.readUInt16BE(
292
+ /** @type {{offset: number}} */ (tables.get("maxp")).offset + 4,
293
+ );
294
+ const hhea = /** @type {{offset: number}} */ (tables.get("hhea")).offset;
295
+ const ascender = buf.readInt16BE(hhea + 4);
296
+ const descender = buf.readInt16BE(hhea + 6);
297
+ const hMetrics = buf.readUInt16BE(hhea + 34);
298
+ const hmtx = /** @type {{offset: number}} */ (tables.get("hmtx")).offset;
299
+ const loca = /** @type {{offset: number}} */ (tables.get("loca")).offset;
300
+ const glyf = /** @type {{offset: number}} */ (tables.get("glyf")).offset;
301
+ const vhea = tables.get("vhea");
302
+ const vmtx = tables.get("vmtx");
303
+ const vMetrics = vhea && vmtx ? buf.readUInt16BE(vhea.offset + 34) : 0;
304
+ const span = (index) =>
305
+ index >= numGlyphs ? [0, 0]
306
+ : longOffsets ? [buf.readUInt32BE(loca + index * 4), buf.readUInt32BE(loca + index * 4 + 4)]
307
+ : [buf.readUInt16BE(loca + index * 2) * 2, buf.readUInt16BE(loca + index * 2 + 2) * 2];
308
+ const glyphs = codepointGlyphs(buf, /** @type {{offset: number}} */ (tables.get("cmap")));
309
+ /** @type {Map<number, Glyph>} */
310
+ const drawn = new Map();
311
+ const glyphOf = (index) => {
312
+ const had = drawn.get(index);
313
+ if (had) return had;
314
+ const advance = buf.readUInt16BE(hmtx + Math.min(index, hMetrics - 1) * 4);
315
+ const [from, to] = span(index);
316
+ const yMax = to > from ? buf.readInt16BE(glyf + from + 8) : 0;
317
+ let top;
318
+ if (vmtx && vMetrics) {
319
+ const tsb =
320
+ index < vMetrics ?
321
+ buf.readInt16BE(vmtx.offset + index * 4 + 2)
322
+ : buf.readInt16BE(vmtx.offset + vMetrics * 4 + (index - vMetrics) * 2);
323
+ top = yMax + tsb;
324
+ } else top = ascender + (unitsPerEm - (ascender - descender)) / 2;
325
+ const glyph = {
326
+ index,
327
+ d: contours(buf, glyf, span, index).map(contourPath).join(""),
328
+ advance,
329
+ top,
330
+ };
331
+ drawn.set(index, glyph);
332
+ return glyph;
333
+ };
334
+ const outlines = {
335
+ unitsPerEm,
336
+ glyph: (point) => {
337
+ const index = glyphs.get(point);
338
+ return index === undefined ? undefined : glyphOf(index);
339
+ },
340
+ };
341
+ cache.set(file, outlines);
342
+ return outlines;
343
+ }
344
+
345
+ /** The book's rubric red, which its preamble names `book-rubric`. */
346
+ export const BOOK_RUBRIC = "#9c281b";
347
+
348
+ /** The book's ink, which its preamble names `book-ink`. */
349
+ export const BOOK_INK = "#241f1a";
350
+
351
+ /** The colour of plain text and of text set in the rubric. */
352
+ const INK = Object.freeze({ plain: BOOK_INK, rubric: BOOK_RUBRIC });
353
+
354
+ /**
355
+ * One panel of a scroll as SVG: its columns read from the right, `rows` ems
356
+ * tall, each character centred in its column, in red where it is set in the
357
+ * rubric and with a line beside it, on the right, where it is overlined.
358
+ *
359
+ * @param {import("./content-scroll.mjs").ScrollCell[][]} panel - Its columns,
360
+ * the first the rightmost.
361
+ * @param {number} rows - The characters in a column.
362
+ * @param {Outlines} outlines - The face.
363
+ * @returns {string} The SVG.
364
+ */
365
+ export function scrollPanelSvg(panel, rows, outlines) {
366
+ const em = outlines.unitsPerEm;
367
+ const pitch = SCROLL_PITCH * em;
368
+ const width = panel.length * pitch;
369
+ const height = rows * em;
370
+ const defs = new Map();
371
+ const marks = [];
372
+ panel.forEach((column, at) => {
373
+ const centre = width - (at + 0.5) * pitch;
374
+ column.forEach((cell, row) => {
375
+ const cellTop = row * em;
376
+ if (cell.emphasis === "overline")
377
+ marks.push(
378
+ `<rect x="${num(centre + 0.55 * em)}" y="${num(cellTop)}" width="${num(0.05 * em)}" height="${num(em)}" fill="${INK.plain}"/>`,
379
+ );
380
+ const point = cell.text.codePointAt(0);
381
+ if (point === undefined || /\s/u.test(cell.text)) return;
382
+ const glyph = outlines.glyph(point);
383
+ if (!glyph?.d) return;
384
+ defs.set(glyph.index, `<path id="g${glyph.index}" d="${glyph.d}"/>`);
385
+ marks.push(
386
+ `<use href="#g${glyph.index}" x="${num(centre - glyph.advance / 2)}" y="${num(cellTop + glyph.top)}" fill="${cell.emphasis === "rubric" ? INK.rubric : INK.plain}"/>`,
387
+ );
388
+ });
389
+ });
390
+ return (
391
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${num(width)} ${num(height)}" width="${num(width)}" height="${num(height)}">` +
392
+ `<defs>${[...defs.values()].join("")}</defs>${marks.join("")}</svg>`
393
+ );
394
+ }
@@ -38,12 +38,6 @@
38
38
  * local pack's output — the resolution logic needs no knowledge of where an
39
39
  * item came from.
40
40
  *
41
- * `assetArchive: true` is the sibling flag, for a dependency whose bytes are
42
- * wanted and whose items are not: an asset-replacement source such as
43
- * `thalornaaltart` ships no Item packs at all. It unpacks the same release
44
- * archive without extracting anything from it, so a relationship declaring
45
- * both flags on one entry is fetched once rather than twice.
46
- *
47
41
  * **The network is never touched by a compile.** Fetching is its own command
48
42
  * (`package-build deps fetch`), and a compile whose cache is cold fails saying
49
43
  * so. A build that silently downloads is not reproducible, fails strangely
@@ -78,22 +72,17 @@ import {
78
72
  const STAMP = ".complete";
79
73
 
80
74
  /**
81
- * Every declared relationship carrying a given flag, in declaration order.
82
- *
83
- * Shared by {@link itemCatalogRelationships} and
84
- * {@link assetArchiveRelationships}: both read the same shape out of the same
85
- * walk, filtered on a different key.
75
+ * Every declared relationship that opted into supplying an item catalogue.
86
76
  *
87
77
  * @param {object} config - The resolved build configuration.
88
- * @param {"itemCatalog"|"assetArchive"} flag - Which boolean to filter on.
89
78
  * @returns {Array<{id: string, manifest: string, kind: string, verified: string|undefined}>}
90
- * The opted-in relationships.
79
+ * The opted-in relationships, in declaration order.
91
80
  */
92
- function flaggedRelationships(config, flag) {
81
+ export function itemCatalogRelationships(config) {
93
82
  const out = [];
94
83
  for (const [kind, entries] of Object.entries(config.relationships ?? {})) {
95
84
  for (const rel of entries ?? []) {
96
- if (rel[flag]) {
85
+ if (rel.itemCatalog) {
97
86
  out.push({
98
87
  id: rel.id,
99
88
  manifest: rel.manifest,
@@ -106,29 +95,6 @@ function flaggedRelationships(config, flag) {
106
95
  return out;
107
96
  }
108
97
 
109
- /**
110
- * Every declared relationship that opted into supplying an item catalogue.
111
- *
112
- * @param {object} config - The resolved build configuration.
113
- * @returns {Array<{id: string, manifest: string, kind: string, verified: string|undefined}>}
114
- * The opted-in relationships, in declaration order.
115
- */
116
- export function itemCatalogRelationships(config) {
117
- return flaggedRelationships(config, "itemCatalog");
118
- }
119
-
120
- /**
121
- * Every declared relationship that opted into supplying its release archive
122
- * for asset bytes alone, with no item catalogue built from it.
123
- *
124
- * @param {object} config - The resolved build configuration.
125
- * @returns {Array<{id: string, manifest: string, kind: string, verified: string|undefined}>}
126
- * The opted-in relationships, in declaration order.
127
- */
128
- export function assetArchiveRelationships(config) {
129
- return flaggedRelationships(config, "assetArchive");
130
- }
131
-
132
98
  /**
133
99
  * The cache directory for one dependency at one version.
134
100
  *
@@ -401,21 +367,14 @@ export function pinnedManifestUrl(url, verified) {
401
367
  }
402
368
 
403
369
  /**
404
- * Resolve a dependency's manifest, pinned to `compatibility.verified` the way
405
- * both fetch paths require.
406
- *
407
- * Shared by {@link fetchCatalog} and {@link fetchAssetArchive}: both read the
408
- * same manifest before deciding what to unpack from it.
370
+ * Resolve a dependency's manifest, pinned to `compatibility.verified`.
409
371
  *
410
372
  * @param {{id: string, manifest: string, verified?: string}} rel - The
411
373
  * declared relationship.
412
- * @param {string} subject - What floats when no version is verified —
413
- * "catalogue" or "archive" — named in the warning a reader would otherwise
414
- * get no word for.
415
374
  * @returns {Promise<{manifest: object, version: string}>} The manifest and
416
375
  * its resolved version.
417
376
  */
418
- async function resolveManifest(rel, subject) {
377
+ async function resolveManifest(rel) {
419
378
  const { url, pinned } = pinnedManifestUrl(rel.manifest, rel.verified);
420
379
  const manifest = await fetchManifest(url);
421
380
  const version = manifest.version;
@@ -432,7 +391,7 @@ async function resolveManifest(rel, subject) {
432
391
  }
433
392
  if (!rel.verified) {
434
393
  log.warn(
435
- `${rel.id}: no \`compatibility.verified\`, so its ${subject} floats ` +
394
+ `${rel.id}: no \`compatibility.verified\`, so its catalogue floats ` +
436
395
  `with whatever ${url} currently serves`,
437
396
  );
438
397
  }
@@ -449,7 +408,7 @@ async function resolveManifest(rel, subject) {
449
408
  * @returns {Promise<string>} The dependency's cache directory.
450
409
  */
451
410
  export async function fetchCatalog(config, rel) {
452
- const { manifest, version } = await resolveManifest(rel, "catalogue");
411
+ const { manifest, version } = await resolveManifest(rel);
453
412
  const dir = catalogDir(config, rel.id, version);
454
413
  if (isComplete(dir)) {
455
414
  log.info(`${rel.id}@${version}: already cached`);
@@ -475,61 +434,6 @@ export async function fetchCatalog(config, rel) {
475
434
  return dir;
476
435
  }
477
436
 
478
- /**
479
- * Whether an asset-archive-only cache is present and complete.
480
- *
481
- * No item catalogue is built on this path, so completeness never asks for
482
- * `item-packs.json` — an asset-only dependency, such as `thalornaaltart`,
483
- * ships none.
484
- *
485
- * @param {string} dir - The dependency's cache directory.
486
- * @returns {boolean} True when the archive was fetched to completion.
487
- */
488
- const isArchiveComplete = (dir) => fs.existsSync(path.join(dir, STAMP));
489
-
490
- /**
491
- * Fetch one dependency's release archive for its asset bytes alone, building
492
- * no item catalogue from it.
493
- *
494
- * Idempotent: a complete cache for the resolved version is left alone. Where a
495
- * relationship also declares `itemCatalog: true`, {@link fetchCatalog} already
496
- * unpacked this same archive into this same cache directory, and
497
- * {@link fetchAllCatalogs} does not call this function for it — the two never
498
- * download the same bytes twice.
499
- *
500
- * @param {object} config - The resolved build configuration.
501
- * @param {{id: string, manifest: string, verified?: string}} rel - The
502
- * declared relationship.
503
- * @returns {Promise<string>} The dependency's cache directory.
504
- */
505
- export async function fetchAssetArchive(config, rel) {
506
- const { manifest, version } = await resolveManifest(rel, "archive");
507
- const dir = catalogDir(config, rel.id, version);
508
- if (isArchiveComplete(dir)) {
509
- log.info(`${rel.id}@${version}: already cached`);
510
- return dir;
511
- }
512
-
513
- const download = manifest.download;
514
- if (!download) {
515
- throw new Error(`${rel.id}@${version}: its manifest declares no \`download\``);
516
- }
517
-
518
- // Rebuild from empty: a previous run may have died partway, and a stale
519
- // half-tree is worse than no tree.
520
- fs.rmSync(dir, { recursive: true, force: true });
521
- const raw = path.join(dir, "package");
522
- fs.mkdirSync(raw, { recursive: true });
523
-
524
- log.info(`${rel.id}@${version}: downloading ${download}`);
525
- await downloadAndUnzip(download, raw);
526
-
527
- cacheSchemaArtifact(raw, dir);
528
- // Last, so a fetch that died partway is never mistaken for a complete one.
529
- fs.writeFileSync(path.join(dir, STAMP), `${version}\n`);
530
- return dir;
531
- }
532
-
533
437
  /**
534
438
  * Locate a declared pack inside an unpacked archive.
535
439
  *
@@ -811,33 +715,18 @@ export async function fetchAllMetadata(config) {
811
715
  /**
812
716
  * Fetch every opted-in dependency. The `deps fetch` command.
813
717
  *
814
- * A relationship declaring both `itemCatalog: true` and `assetArchive: true`
815
- * is fetched once: {@link fetchCatalog} already unpacks the archive on its
816
- * way to extracting the item catalogue, so this skips the archive-only fetch
817
- * for any id {@link fetchCatalog} already handled.
818
718
  *
819
719
  * @param {object} config - The resolved build configuration.
820
720
  * @returns {Promise<number>} How many dependencies were fetched.
821
721
  */
822
722
  export async function fetchAllCatalogs(config) {
823
723
  const itemRels = itemCatalogRelationships(config);
824
- const archiveRels = assetArchiveRelationships(config);
825
- if (!itemRels.length && !archiveRels.length) {
826
- log.info(
827
- "No relationship declares `itemCatalog: true` or `assetArchive: true`; " +
828
- "nothing to fetch.",
829
- );
724
+ if (!itemRels.length) {
725
+ log.info("No relationship declares `itemCatalog: true`; nothing to fetch.");
830
726
  return 0;
831
727
  }
832
728
  for (const rel of itemRels) await fetchCatalog(config, rel);
833
- const alreadyFetched = new Set(itemRels.map((rel) => rel.id));
834
- let archiveFetched = 0;
835
- for (const rel of archiveRels) {
836
- if (alreadyFetched.has(rel.id)) continue;
837
- await fetchAssetArchive(config, rel);
838
- archiveFetched++;
839
- }
840
- return itemRels.length + archiveFetched;
729
+ return itemRels.length;
841
730
  }
842
731
 
843
732
  /**