@heroiclands/package-build 8.1.0 → 10.0.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 +748 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +479 -123
- package/bin/package-build.mjs +27 -69
- package/bin/report.mjs +1 -2
- package/bundle.mjs +2 -10
- package/config.mjs +31 -106
- package/container.mjs +13 -57
- package/content-config.mjs +300 -188
- package/coverage.mjs +14 -55
- package/deploy.mjs +4 -13
- package/docs/content-format.md +1418 -0
- package/e2e.mjs +16 -55
- package/engine/address-charset.mjs +62 -0
- package/engine/address-diff.mjs +1 -4
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +203 -31
- package/engine/code-fences.mjs +4 -13
- package/engine/compendiums.mjs +13 -37
- package/engine/content-address.mjs +6 -10
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +144 -99
- package/engine/content-lint.mjs +12 -16
- package/engine/content-slug.mjs +2 -6
- package/engine/content-tables.mjs +26 -79
- package/engine/diagnostics.mjs +37 -15
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-reference.mjs +6 -20
- package/engine/field-spec.mjs +49 -45
- package/engine/foreign-catalog.mjs +7 -22
- package/engine/foreign-manifests.mjs +1 -4
- package/engine/frontmatter-lint.mjs +347 -43
- package/engine/frontmatter.mjs +3 -8
- package/engine/generate.mjs +36 -20
- package/engine/helpers.mjs +54 -81
- package/engine/homepage.mjs +4 -15
- package/engine/ids.mjs +21 -12
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -9
- package/engine/journals.mjs +4 -14
- package/engine/kb-manifest.mjs +41 -24
- package/engine/macros.mjs +2 -10
- package/engine/manifest-emit.mjs +6 -17
- package/engine/map-notes.mjs +53 -87
- package/engine/note-claims.mjs +383 -0
- package/engine/note-package.mjs +1 -4
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +56 -60
- package/engine/pack-router.mjs +18 -8
- package/engine/prose-config.mjs +20 -4
- package/engine/prose-lint.mjs +61 -17
- package/engine/region-events.mjs +1 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scene-levels.mjs +8 -22
- package/engine/scenes.mjs +31 -47
- package/engine/schema-check.mjs +348 -7
- package/engine/schema-extract.mjs +11 -39
- package/engine/site-build.mjs +13 -38
- package/engine/site-index.mjs +40 -35
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +115 -92
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +147 -183
- package/index.mjs +1 -5
- package/lang.mjs +1 -3
- package/manifest.mjs +10 -37
- package/markdownlint-config.mjs +1 -5
- package/package.json +6 -2
- package/sohl/actors.mjs +251 -68
- package/sohl/being-info.mjs +3 -6
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +4 -6
- package/sohl/item-builders.mjs +1 -3
- package/sohl/item-fields.mjs +16 -34
- package/sohl/items.mjs +111 -17
- package/sohl/kb-passes.mjs +29 -39
- package/sohl/note-schemas.mjs +11 -7
- package/sohl/skill-base.mjs +7 -23
- package/stage.mjs +3 -13
- package/templates.mjs +4 -15
- package/types/bundle.d.mts +1 -1
- package/types/container.d.mts +2 -2
- package/types/content-config.d.mts +48 -4
- package/types/coverage.d.mts +1 -1
- package/types/e2e.d.mts +4 -4
- package/types/engine/address-charset.d.mts +45 -0
- package/types/engine/alias-index.d.mts +122 -0
- package/types/engine/base-compiler.d.mts +132 -4
- package/types/engine/content-address.d.mts +2 -2
- package/types/engine/content-format-check.d.mts +163 -0
- package/types/engine/content-format.d.mts +101 -0
- package/types/engine/content-links.d.mts +16 -1
- package/types/engine/content-lint.d.mts +6 -0
- package/types/engine/diagnostics.d.mts +29 -0
- package/types/engine/document-subtypes.d.mts +233 -0
- package/types/engine/field-spec.d.mts +76 -23
- package/types/engine/frontmatter-lint.d.mts +47 -2
- package/types/engine/generate.d.mts +15 -2
- package/types/engine/helpers.d.mts +22 -14
- package/types/engine/ids.d.mts +10 -0
- package/types/engine/index.d.mts +5 -0
- package/types/engine/item-registry.d.mts +21 -2
- package/types/engine/kb-manifest.d.mts +35 -8
- package/types/engine/map-notes.d.mts +21 -11
- package/types/engine/note-claims.d.mts +113 -0
- package/types/engine/note-vocabulary.d.mts +251 -0
- package/types/engine/pack-config.d.mts +4 -3
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/prose-lint.d.mts +6 -2
- package/types/engine/retired-fields.d.mts +73 -2
- package/types/engine/schema-check.d.mts +182 -0
- package/types/engine/schema-extract.d.mts +1 -1
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/system-block.d.mts +281 -0
- package/types/engine/web-wikilinks.d.mts +23 -12
- package/types/engine/wikilink-syntax.d.mts +29 -0
- package/types/manifest.d.mts +1 -1
- package/types/sohl/actors.d.mts +62 -6
- package/types/sohl/document-subtypes.d.mts +14 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/items.d.mts +21 -0
package/bin/content-build.mjs
CHANGED
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
* npx content-build package clean [pack] [entry]
|
|
40
40
|
* npx content-build docs item-fields [--out <path>] [--title <title>]
|
|
41
41
|
* npx content-build lint [root] [--no-references]
|
|
42
|
+
* npx content-build content-format schema --schema <system>=<path>
|
|
43
|
+
* npx content-build content-format fields [--fields <system>]
|
|
44
|
+
* npx content-build content-format notes [root] [--strict]
|
|
42
45
|
* npx content-build links [root] [--manifests <dir>]
|
|
43
46
|
* npx content-build format [paths..] [--write]
|
|
44
47
|
* npx content-build markdown [paths..] [--fix]
|
|
@@ -59,11 +62,7 @@ import log from "loglevel";
|
|
|
59
62
|
import prefix from "loglevel-plugin-prefix";
|
|
60
63
|
import yargs from "yargs";
|
|
61
64
|
import { hideBin } from "yargs/helpers";
|
|
62
|
-
import {
|
|
63
|
-
compilePacks,
|
|
64
|
-
cleanPacks,
|
|
65
|
-
unpackPacks,
|
|
66
|
-
} from "../engine/compendiums.mjs";
|
|
65
|
+
import { compilePacks, cleanPacks, unpackPacks } from "../engine/compendiums.mjs";
|
|
67
66
|
import { loadPackConfig } from "../engine/pack-config.mjs";
|
|
68
67
|
import {
|
|
69
68
|
fetchAllCatalogs,
|
|
@@ -73,6 +72,12 @@ import {
|
|
|
73
72
|
import { renderItemFieldReference } from "../engine/field-reference.mjs";
|
|
74
73
|
import { lintContentTree } from "../engine/content-lint.mjs";
|
|
75
74
|
import { lintFrontmatter } from "../engine/frontmatter-lint.mjs";
|
|
75
|
+
import { loadContentFormat } from "../engine/content-format.mjs";
|
|
76
|
+
import {
|
|
77
|
+
checkDeclaredFields,
|
|
78
|
+
checkSchemaTargets,
|
|
79
|
+
measureCorpus,
|
|
80
|
+
} from "../engine/content-format-check.mjs";
|
|
76
81
|
import {
|
|
77
82
|
compareFields,
|
|
78
83
|
resolveSchemaArtifact,
|
|
@@ -83,9 +88,13 @@ import {
|
|
|
83
88
|
// set — an adventure module ships skills, beings and magic swords — so no
|
|
84
89
|
// consumer gets a subset (#19, #20).
|
|
85
90
|
import { NOTE_SCHEMAS } from "../sohl/note-schemas.mjs";
|
|
91
|
+
// The shipped declarations, so this repository can check its own specification
|
|
92
|
+
// against them without standing up a consumer's configuration (#136).
|
|
93
|
+
import { ITEM_FIELDS } from "../sohl/item-fields.mjs";
|
|
86
94
|
// The engine's own types, merged under the registry's so the vocabulary stands
|
|
87
95
|
// in a package that configures no `itemBuilders` at all (#51).
|
|
88
96
|
import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
|
|
97
|
+
import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
|
|
89
98
|
import { checkFormatting, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
90
99
|
import { emitLinkManifest } from "../engine/manifest-emit.mjs";
|
|
91
100
|
import {
|
|
@@ -93,12 +102,13 @@ import {
|
|
|
93
102
|
gatesFailed,
|
|
94
103
|
formatUnaddressableFinding as formatUnaddressable,
|
|
95
104
|
} from "../engine/site-build.mjs";
|
|
105
|
+
import { auditLinks, buildLinkIndex, walkReachability } from "../engine/content-links.mjs";
|
|
96
106
|
import {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} from "../engine/
|
|
101
|
-
import {
|
|
107
|
+
emitDiagnostic,
|
|
108
|
+
positionInFrontmatter,
|
|
109
|
+
positionOfLiteral,
|
|
110
|
+
} from "../engine/diagnostics.mjs";
|
|
111
|
+
import { reportFindings } from "./report.mjs";
|
|
102
112
|
import {
|
|
103
113
|
readItemAddresses,
|
|
104
114
|
diffItemAddresses,
|
|
@@ -107,6 +117,7 @@ import {
|
|
|
107
117
|
addressFindingMessage,
|
|
108
118
|
} from "../engine/address-diff.mjs";
|
|
109
119
|
import { itemPackJsonDirs } from "../engine/generate.mjs";
|
|
120
|
+
import { walkMarkdownTree } from "../engine/helpers.mjs";
|
|
110
121
|
import {
|
|
111
122
|
formatUnaddressableFinding,
|
|
112
123
|
unaddressableForeignPackages,
|
|
@@ -143,9 +154,7 @@ function configuredPacks() {
|
|
|
143
154
|
* @returns {string} The `version` field of this package's manifest.
|
|
144
155
|
*/
|
|
145
156
|
function ownVersion() {
|
|
146
|
-
return JSON.parse(
|
|
147
|
-
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
148
|
-
).version;
|
|
157
|
+
return JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
// Configure loglevel
|
|
@@ -176,16 +185,23 @@ prefix.apply(log, {
|
|
|
176
185
|
*/
|
|
177
186
|
function reportFailure(err) {
|
|
178
187
|
const message = err instanceof Error ? err.message : String(err);
|
|
179
|
-
if (/** @type {{located?: boolean}} */ (err)?.located)
|
|
180
|
-
console.error(message);
|
|
188
|
+
if (/** @type {{located?: boolean}} */ (err)?.located) console.error(message);
|
|
181
189
|
else log.error(message);
|
|
182
190
|
}
|
|
183
191
|
|
|
192
|
+
/**
|
|
193
|
+
* The declaration sets this package ships, addressable by system id.
|
|
194
|
+
*
|
|
195
|
+
* @type {Record<string, Record<string, readonly object[]>>}
|
|
196
|
+
*/
|
|
197
|
+
const SHIPPED_ITEM_FIELDS = { sohl: ITEM_FIELDS };
|
|
198
|
+
|
|
184
199
|
const argv = yargs(hideBin(process.argv))
|
|
185
200
|
.command(packageCommand())
|
|
186
201
|
.command(depsCommand())
|
|
187
202
|
.command(docsCommand())
|
|
188
203
|
.command(lintCommand())
|
|
204
|
+
.command(contentFormatCommand())
|
|
189
205
|
.command(linksCommand())
|
|
190
206
|
.command(formatCommand())
|
|
191
207
|
.command(markdownCommand())
|
|
@@ -245,13 +261,11 @@ function docsCommand() {
|
|
|
245
261
|
choices: ["item-fields"],
|
|
246
262
|
});
|
|
247
263
|
yargs.option("out", {
|
|
248
|
-
describe:
|
|
249
|
-
"Write to this file instead of the configured location.",
|
|
264
|
+
describe: "Write to this file instead of the configured location.",
|
|
250
265
|
type: "string",
|
|
251
266
|
});
|
|
252
267
|
yargs.option("check", {
|
|
253
|
-
describe:
|
|
254
|
-
"Compare against the file already there; write nothing.",
|
|
268
|
+
describe: "Compare against the file already there; write nothing.",
|
|
255
269
|
type: "boolean",
|
|
256
270
|
default: false,
|
|
257
271
|
});
|
|
@@ -274,13 +288,10 @@ function docsCommand() {
|
|
|
274
288
|
const config = loadPackConfig();
|
|
275
289
|
const spec = config.docs?.itemFields ?? {};
|
|
276
290
|
const destination =
|
|
277
|
-
argv.out ??
|
|
278
|
-
(spec.out ? path.resolve(config.rootDir, spec.out) : null);
|
|
291
|
+
argv.out ?? (spec.out ? path.resolve(config.rootDir, spec.out) : null);
|
|
279
292
|
|
|
280
293
|
const page = `${renderItemFieldReference({
|
|
281
|
-
...((title ?? spec.title) ?
|
|
282
|
-
{ title: title ?? spec.title }
|
|
283
|
-
: {}),
|
|
294
|
+
...((title ?? spec.title) ? { title: title ?? spec.title } : {}),
|
|
284
295
|
...(spec.preamble ? { preamble: spec.preamble } : {}),
|
|
285
296
|
generatedBy: "`content-build docs item-fields`",
|
|
286
297
|
config,
|
|
@@ -296,9 +307,7 @@ function docsCommand() {
|
|
|
296
307
|
}
|
|
297
308
|
const relative = path.relative(config.rootDir, destination);
|
|
298
309
|
const current =
|
|
299
|
-
fs.existsSync(destination) ?
|
|
300
|
-
fs.readFileSync(destination, "utf8")
|
|
301
|
-
: "";
|
|
310
|
+
fs.existsSync(destination) ? fs.readFileSync(destination, "utf8") : "";
|
|
302
311
|
if (current !== page) {
|
|
303
312
|
// Staleness belongs to the whole file, so no line is
|
|
304
313
|
// named — the diagnostics contract drops a field it
|
|
@@ -321,11 +330,130 @@ function docsCommand() {
|
|
|
321
330
|
recursive: true,
|
|
322
331
|
});
|
|
323
332
|
fs.writeFileSync(destination, page);
|
|
333
|
+
log.info(`Wrote ${path.relative(config.rootDir, destination)}`);
|
|
334
|
+
} else {
|
|
335
|
+
process.stdout.write(page);
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
reportFailure(err);
|
|
339
|
+
process.exitCode = 1;
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* `content-build content-format` — check the format specification itself (#130).
|
|
347
|
+
*
|
|
348
|
+
* Two checks, because the specification makes claims about two different
|
|
349
|
+
* worlds, and they fail for different reasons and at different times:
|
|
350
|
+
*
|
|
351
|
+
* - `schema` compares every `system.*` target the document names against the
|
|
352
|
+
* naming system's published `schema.json`. A failure means the specification
|
|
353
|
+
* and the system disagree, which is a defect in one of the two.
|
|
354
|
+
* - `fields` compares the per-type tables against the field declarations that
|
|
355
|
+
* compile them, so the hand-written half cannot drift from the generated one
|
|
356
|
+
* (#136).
|
|
357
|
+
* - `notes` measures a content tree against the vocabulary the document
|
|
358
|
+
* declares per type. During #127 it is the migration's progress bar rather
|
|
359
|
+
* than a gate, so it **reports** by default and `--strict` makes it fatal —
|
|
360
|
+
* turned on one class at a time as each slice lands.
|
|
361
|
+
*
|
|
362
|
+
* Both read the committed document rather than a transcription of it, so
|
|
363
|
+
* editing `docs/content-format.md` changes what they assert.
|
|
364
|
+
*
|
|
365
|
+
* @returns {object} The yargs command module.
|
|
366
|
+
*/
|
|
367
|
+
// eslint-disable-next-line
|
|
368
|
+
function contentFormatCommand() {
|
|
369
|
+
return {
|
|
370
|
+
command: "content-format <action>",
|
|
371
|
+
describe: "Check the content format specification against schemas and notes",
|
|
372
|
+
builder: (yargs) =>
|
|
373
|
+
yargs
|
|
374
|
+
.command(contentFormatSchemaCommand())
|
|
375
|
+
.command(contentFormatFieldsCommand())
|
|
376
|
+
.command(contentFormatNotesCommand())
|
|
377
|
+
.demandCommand(1, "Name an action.")
|
|
378
|
+
.strict(),
|
|
379
|
+
handler: () => {},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* The parsed specification a `content-format` action should read.
|
|
385
|
+
*
|
|
386
|
+
* @param {object} argv - The parsed command line.
|
|
387
|
+
* @returns {import("../engine/content-format.mjs").ContentFormat} The document.
|
|
388
|
+
*/
|
|
389
|
+
function specFrom(argv) {
|
|
390
|
+
return argv.spec ? loadContentFormat(path.resolve(argv.spec)) : loadContentFormat();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* `content-format schema` — every `system.*` target, against a published schema.
|
|
395
|
+
*
|
|
396
|
+
* The schemas are named on the command line as `<system>=<path>`, because a
|
|
397
|
+
* consumer holds one and this repository holds a committed fixture, and neither
|
|
398
|
+
* arrangement should be the one the other has to pretend to. A system the
|
|
399
|
+
* document maps onto but no schema was supplied for is reported as unchecked —
|
|
400
|
+
* HM3 publishes no artifact today, so that is the ordinary case for a fifth of
|
|
401
|
+
* the claims, and a check that quietly skipped them would read as one that
|
|
402
|
+
* passed.
|
|
403
|
+
*
|
|
404
|
+
* @returns {object} The yargs command module.
|
|
405
|
+
*/
|
|
406
|
+
function contentFormatSchemaCommand() {
|
|
407
|
+
return {
|
|
408
|
+
command: "schema",
|
|
409
|
+
describe: "Check every `system.*` target the format names against a published schema.json",
|
|
410
|
+
builder: (yargs) => {
|
|
411
|
+
yargs.option("spec", {
|
|
412
|
+
describe:
|
|
413
|
+
"The specification to read. Defaults to the docs/content-format.md this package ships.",
|
|
414
|
+
type: "string",
|
|
415
|
+
});
|
|
416
|
+
yargs.option("schema", {
|
|
417
|
+
describe:
|
|
418
|
+
"A published schema, as `<system>=<path>`. Repeatable; a system with none is reported unchecked.",
|
|
419
|
+
type: "string",
|
|
420
|
+
array: true,
|
|
421
|
+
demandOption: true,
|
|
422
|
+
});
|
|
423
|
+
},
|
|
424
|
+
handler: (argv) => {
|
|
425
|
+
try {
|
|
426
|
+
const format = specFrom(argv);
|
|
427
|
+
/** @type {Record<string, object>} */
|
|
428
|
+
const schemas = {};
|
|
429
|
+
for (const entry of argv.schema) {
|
|
430
|
+
const at = String(entry).indexOf("=");
|
|
431
|
+
if (at <= 0) {
|
|
432
|
+
log.error(`--schema takes \`<system>=<path>\`, not "${entry}".`);
|
|
433
|
+
process.exitCode = 1;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const system = entry.slice(0, at);
|
|
437
|
+
const file = path.resolve(entry.slice(at + 1));
|
|
438
|
+
schemas[system] = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const { findings, checked, unchecked } = checkSchemaTargets({ format, schemas });
|
|
442
|
+
for (const finding of findings) emitDiagnostic(finding);
|
|
443
|
+
for (const [system, count] of Object.entries(unchecked)) {
|
|
324
444
|
log.info(
|
|
325
|
-
|
|
445
|
+
`${count} claim(s) about ${system} are unchecked — no ` +
|
|
446
|
+
`schema was supplied for it, so nothing here confirms them.`,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
if (findings.length) {
|
|
450
|
+
log.error(
|
|
451
|
+
`${findings.length} of ${checked} checked claim(s) name a ` +
|
|
452
|
+
`field no schema declares.`,
|
|
326
453
|
);
|
|
454
|
+
process.exitCode = 1;
|
|
327
455
|
} else {
|
|
328
|
-
|
|
456
|
+
log.info(`${checked} mapping claim(s) confirmed against the supplied schemas.`);
|
|
329
457
|
}
|
|
330
458
|
} catch (err) {
|
|
331
459
|
reportFailure(err);
|
|
@@ -335,6 +463,198 @@ function docsCommand() {
|
|
|
335
463
|
};
|
|
336
464
|
}
|
|
337
465
|
|
|
466
|
+
/**
|
|
467
|
+
* The field declarations a `content-format fields` run should compare against.
|
|
468
|
+
*
|
|
469
|
+
* Either this package's own shipped registry, named on the command line, or the
|
|
470
|
+
* consuming repository's resolved configuration. Both are real arrangements and
|
|
471
|
+
* neither should have to pretend to be the other: this repository ships the
|
|
472
|
+
* specification *and* the SoHL declarations and configures no content tree,
|
|
473
|
+
* while a consumer configures `itemBuilders` and resolves the specification
|
|
474
|
+
* from its toolchain.
|
|
475
|
+
*
|
|
476
|
+
* @param {object} argv - The parsed command line.
|
|
477
|
+
* @returns {{itemFields: Record<string, readonly object[]>, system: string}}
|
|
478
|
+
* The declarations, and the system column of the mapping tables they compile.
|
|
479
|
+
*/
|
|
480
|
+
function declarationsFrom(argv) {
|
|
481
|
+
if (argv.fields) return { itemFields: SHIPPED_ITEM_FIELDS[argv.fields], system: argv.fields };
|
|
482
|
+
const config = loadPackConfig();
|
|
483
|
+
const system = config.stats?.systemId;
|
|
484
|
+
if (!system) {
|
|
485
|
+
throw new Error(
|
|
486
|
+
"package-build: this repository's configuration names no system, so " +
|
|
487
|
+
"nothing says which column of the format's mapping tables its " +
|
|
488
|
+
"`itemBuilders` declarations compile. Name a shipped set with " +
|
|
489
|
+
"`--fields <system>` instead.",
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return { itemFields: config.itemFields ?? {}, system };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* `content-format fields` — the per-type tables, against the declarations.
|
|
497
|
+
*
|
|
498
|
+
* The specification hand-writes a `data` table under most of its type sections,
|
|
499
|
+
* covering ground {@link module:engine/field-reference} already generates from
|
|
500
|
+
* the `fields` on each `itemBuilders` entry — the duplication that module exists
|
|
501
|
+
* to prevent, one document over (#136).
|
|
502
|
+
*
|
|
503
|
+
* **Checked, not merged.** The document's vocabulary spans note types that
|
|
504
|
+
* produce Scenes, Macros and JournalEntries, which no item registry covers, so
|
|
505
|
+
* there is no wholesale generation to fall back on. What the two *can* be held
|
|
506
|
+
* to is agreement where they both speak: a mapping row saying `data.weight`
|
|
507
|
+
* reaches `system.weightBase` and a declaration writing `weight` to `weightBase`
|
|
508
|
+
* are one statement made twice, and a rename that moves only one of them is a
|
|
509
|
+
* defect. A type only one side describes is named as out of reach rather than
|
|
510
|
+
* skipped in silence.
|
|
511
|
+
*
|
|
512
|
+
* @returns {object} The yargs command module.
|
|
513
|
+
*/
|
|
514
|
+
function contentFormatFieldsCommand() {
|
|
515
|
+
return {
|
|
516
|
+
command: "fields",
|
|
517
|
+
describe: "Check the format's per-type tables against the field declarations",
|
|
518
|
+
builder: (yargs) => {
|
|
519
|
+
yargs.option("spec", {
|
|
520
|
+
describe:
|
|
521
|
+
"The specification to read. Defaults to the docs/content-format.md this package ships.",
|
|
522
|
+
type: "string",
|
|
523
|
+
});
|
|
524
|
+
yargs.option("fields", {
|
|
525
|
+
describe:
|
|
526
|
+
"A declaration set this package ships, by system id. Defaults to the consuming repository's own `itemBuilders`.",
|
|
527
|
+
type: "string",
|
|
528
|
+
choices: Object.keys(SHIPPED_ITEM_FIELDS),
|
|
529
|
+
});
|
|
530
|
+
yargs.option("coverage", {
|
|
531
|
+
describe:
|
|
532
|
+
"List, per type, the fields only one side names. They are not findings — the two vocabularies differ by design until #127 lands.",
|
|
533
|
+
type: "boolean",
|
|
534
|
+
default: false,
|
|
535
|
+
});
|
|
536
|
+
},
|
|
537
|
+
handler: (argv) => {
|
|
538
|
+
try {
|
|
539
|
+
const format = specFrom(argv);
|
|
540
|
+
const { itemFields, system } = declarationsFrom(argv);
|
|
541
|
+
const result = checkDeclaredFields({ format, itemFields, system });
|
|
542
|
+
for (const finding of result.findings) emitDiagnostic(finding);
|
|
543
|
+
|
|
544
|
+
// Named rather than left implicit: a check that silently
|
|
545
|
+
// compared nine of twenty-two types would read as one that
|
|
546
|
+
// covered them all.
|
|
547
|
+
log.info(
|
|
548
|
+
`${result.checked.length} type(s) compared against ${system}'s ` +
|
|
549
|
+
`declarations (${result.fields} field pair(s)).`,
|
|
550
|
+
);
|
|
551
|
+
log.info(
|
|
552
|
+
`${result.skipped.spec.length} type(s) the format declares are ` +
|
|
553
|
+
`out of reach — no \`itemBuilders\` entry covers them: ` +
|
|
554
|
+
`${result.skipped.spec.join(", ")}.`,
|
|
555
|
+
);
|
|
556
|
+
if (result.skipped.registry.length) {
|
|
557
|
+
log.info(
|
|
558
|
+
`${result.skipped.registry.length} declared type(s) the ` +
|
|
559
|
+
`format names no section for: ` +
|
|
560
|
+
`${result.skipped.registry.join(", ")}.`,
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (argv.coverage) {
|
|
564
|
+
for (const entry of result.coverage) {
|
|
565
|
+
log.info(
|
|
566
|
+
`${entry.type}: format only [${entry.specOnly.join(", ")}], ` +
|
|
567
|
+
`declaration only [${entry.registryOnly.join(", ")}]`,
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (result.findings.length) {
|
|
573
|
+
log.error(
|
|
574
|
+
`${result.findings.length} of ${result.fields} compared field ` +
|
|
575
|
+
`pair(s) disagree between the specification and the ` +
|
|
576
|
+
`declaration that compiles them.`,
|
|
577
|
+
);
|
|
578
|
+
process.exitCode = 1;
|
|
579
|
+
}
|
|
580
|
+
} catch (err) {
|
|
581
|
+
reportFailure(err);
|
|
582
|
+
process.exitCode = 1;
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* `content-format notes` — a content tree, against the declared vocabulary.
|
|
590
|
+
*
|
|
591
|
+
* **A report, not a gate.** Every authored note predates the format, so a
|
|
592
|
+
* failing check would be red in every repository from the day it lands and
|
|
593
|
+
* would stay red for the length of #127 — which is a check nobody can act on.
|
|
594
|
+
* `--strict` raises the findings to errors, and #127 turns it on slice by
|
|
595
|
+
* slice as each class of finding reaches zero.
|
|
596
|
+
*
|
|
597
|
+
* @returns {object} The yargs command module.
|
|
598
|
+
*/
|
|
599
|
+
function contentFormatNotesCommand() {
|
|
600
|
+
return {
|
|
601
|
+
command: "notes [root]",
|
|
602
|
+
describe: "Measure a content tree against the vocabulary the format declares (a report)",
|
|
603
|
+
builder: (yargs) => {
|
|
604
|
+
yargs.positional("root", {
|
|
605
|
+
describe: "Content tree to measure. Defaults to the configured contentBase.",
|
|
606
|
+
type: "string",
|
|
607
|
+
});
|
|
608
|
+
yargs.option("spec", {
|
|
609
|
+
describe:
|
|
610
|
+
"The specification to read. Defaults to the docs/content-format.md this package ships.",
|
|
611
|
+
type: "string",
|
|
612
|
+
});
|
|
613
|
+
yargs.option("strict", {
|
|
614
|
+
describe:
|
|
615
|
+
"Fail on the findings instead of reporting them. Turned on per slice of #127, as each class reaches zero.",
|
|
616
|
+
type: "boolean",
|
|
617
|
+
default: false,
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
handler: (argv) => {
|
|
621
|
+
try {
|
|
622
|
+
const config = loadPackConfig();
|
|
623
|
+
const root = argv.root ?? config.paths.content;
|
|
624
|
+
const format = specFrom(argv);
|
|
625
|
+
|
|
626
|
+
const notes = [];
|
|
627
|
+
for (const { frontmatter, absPath } of walkMarkdownTree(root, {
|
|
628
|
+
skipDirectories: config.skipDirectories,
|
|
629
|
+
})) {
|
|
630
|
+
if (!frontmatter || typeof frontmatter.type !== "string") continue;
|
|
631
|
+
notes.push({
|
|
632
|
+
file: absPath,
|
|
633
|
+
fm: frontmatter,
|
|
634
|
+
raw: fs.readFileSync(absPath, "utf8"),
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const { findings, byClass } = measureCorpus(notes, format, {
|
|
639
|
+
strict: argv.strict,
|
|
640
|
+
});
|
|
641
|
+
for (const finding of findings) emitDiagnostic(finding);
|
|
642
|
+
|
|
643
|
+
const counts = Object.entries(byClass).sort(([a], [b]) => (a < b ? -1 : 1));
|
|
644
|
+
for (const [cls, count] of counts) log.info(`${cls}: ${count}`);
|
|
645
|
+
log.info(
|
|
646
|
+
`${findings.length} finding(s) across ${notes.length} note(s) ` +
|
|
647
|
+
`measured against ${format.file}.`,
|
|
648
|
+
);
|
|
649
|
+
if (argv.strict && findings.length) process.exitCode = 1;
|
|
650
|
+
} catch (err) {
|
|
651
|
+
reportFailure(err);
|
|
652
|
+
process.exitCode = 1;
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
338
658
|
/**
|
|
339
659
|
* `content-build lint` — check a content tree's addresses.
|
|
340
660
|
*
|
|
@@ -353,8 +673,7 @@ function lintCommand() {
|
|
|
353
673
|
describe: "Check a content tree's addresses and frontmatter",
|
|
354
674
|
builder: (yargs) => {
|
|
355
675
|
yargs.positional("root", {
|
|
356
|
-
describe:
|
|
357
|
-
"Content tree to lint. Defaults to the configured contentBase.",
|
|
676
|
+
describe: "Content tree to lint. Defaults to the configured contentBase.",
|
|
358
677
|
type: "string",
|
|
359
678
|
});
|
|
360
679
|
yargs.option("references", {
|
|
@@ -389,6 +708,11 @@ function lintCommand() {
|
|
|
389
708
|
});
|
|
390
709
|
const frontmatter = lintFrontmatter(index, {
|
|
391
710
|
schemas: { ...ENGINE_NOTE_SCHEMAS, ...NOTE_SCHEMAS },
|
|
711
|
+
// The closed frontmatter regions (#128). Passed in rather
|
|
712
|
+
// than reached for, so the linter stays a checker of
|
|
713
|
+
// whatever it is handed and this stays the one place that
|
|
714
|
+
// decides which vocabulary a tree is held to.
|
|
715
|
+
vocabulary: NOTE_VOCABULARY,
|
|
392
716
|
references: argv.references,
|
|
393
717
|
});
|
|
394
718
|
|
|
@@ -466,12 +790,23 @@ function lintCommand() {
|
|
|
466
790
|
...frontmatter.findings,
|
|
467
791
|
...schemaFindings,
|
|
468
792
|
];
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
793
|
+
// Only an **error** fails the run. Every finding was an error
|
|
794
|
+
// until #142, so this changed nothing on the day it landed —
|
|
795
|
+
// but a field retired in favour of another is reported while
|
|
796
|
+
// both spellings still compile, and failing a build over a note
|
|
797
|
+
// that produces the correct document would red a tree that has
|
|
798
|
+
// done nothing wrong. `reportFindings` already draws exactly
|
|
799
|
+
// that line, so the rule is the shared one rather than a second
|
|
800
|
+
// copy here. Each finding names its own file.
|
|
801
|
+
const errors = reportFindings(findings, {});
|
|
802
|
+
if (errors) {
|
|
803
|
+
log.error(`${findings.length} finding(s) across ${addresses.notes} note(s).`);
|
|
474
804
|
process.exitCode = 1;
|
|
805
|
+
} else if (findings.length) {
|
|
806
|
+
log.warn(
|
|
807
|
+
`${findings.length} advisory finding(s) across ` +
|
|
808
|
+
`${addresses.notes} note(s); none fails the build.`,
|
|
809
|
+
);
|
|
475
810
|
} else {
|
|
476
811
|
log.info(
|
|
477
812
|
`Addresses and frontmatter are well-formed ` +
|
|
@@ -510,19 +845,16 @@ function formatCommand() {
|
|
|
510
845
|
describe: "Check formatting with the shared Prettier configuration",
|
|
511
846
|
builder: (yargs) => {
|
|
512
847
|
yargs.positional("paths", {
|
|
513
|
-
describe:
|
|
514
|
-
"Files or directories to check. Defaults to the whole repository.",
|
|
848
|
+
describe: "Files or directories to check. Defaults to the whole repository.",
|
|
515
849
|
type: "string",
|
|
516
850
|
});
|
|
517
851
|
yargs.option("write", {
|
|
518
|
-
describe:
|
|
519
|
-
"Rewrite unformatted files in place instead of reporting them.",
|
|
852
|
+
describe: "Rewrite unformatted files in place instead of reporting them.",
|
|
520
853
|
type: "boolean",
|
|
521
854
|
default: false,
|
|
522
855
|
});
|
|
523
856
|
yargs.option("check", {
|
|
524
|
-
describe:
|
|
525
|
-
"Report unformatted files without rewriting them (the default).",
|
|
857
|
+
describe: "Report unformatted files without rewriting them (the default).",
|
|
526
858
|
type: "boolean",
|
|
527
859
|
});
|
|
528
860
|
},
|
|
@@ -532,30 +864,37 @@ function formatCommand() {
|
|
|
532
864
|
// it contradicts `--write`; naming both is a mistake worth
|
|
533
865
|
// saying out loud rather than silently resolving.
|
|
534
866
|
if (argv.check === true && argv.write) {
|
|
535
|
-
log.error(
|
|
536
|
-
"--check and --write ask for opposite things; name one.",
|
|
537
|
-
);
|
|
867
|
+
log.error("--check and --write ask for opposite things; name one.");
|
|
538
868
|
process.exitCode = 1;
|
|
539
869
|
return;
|
|
540
870
|
}
|
|
541
871
|
const root = process.cwd();
|
|
542
|
-
const { findings, checked, written } = await checkFormatting(
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
);
|
|
872
|
+
const { findings, checked, written } = await checkFormatting(root, {
|
|
873
|
+
paths: argv.paths,
|
|
874
|
+
write: argv.write,
|
|
875
|
+
});
|
|
546
876
|
if (argv.write) {
|
|
547
877
|
log.info(
|
|
548
878
|
written.length ?
|
|
549
879
|
`Formatted ${written.length} of ${checked} file(s).`
|
|
550
880
|
: `Already formatted (${checked} file(s)).`,
|
|
551
881
|
);
|
|
882
|
+
// `--write` collects findings too — a file Prettier cannot
|
|
883
|
+
// parse, or one that will not format to a fixpoint — and
|
|
884
|
+
// used to discard them, so a run that had left files
|
|
885
|
+
// unformatted still reported success and exited 0 (#125).
|
|
886
|
+
for (const finding of findings) emitDiagnostic(finding);
|
|
887
|
+
if (findings.length) {
|
|
888
|
+
log.error(
|
|
889
|
+
`${findings.length} of ${checked} file(s) could not be formatted.`,
|
|
890
|
+
);
|
|
891
|
+
process.exitCode = 1;
|
|
892
|
+
}
|
|
552
893
|
return;
|
|
553
894
|
}
|
|
554
895
|
for (const finding of findings) emitDiagnostic(finding);
|
|
555
896
|
if (findings.length) {
|
|
556
|
-
log.error(
|
|
557
|
-
`${findings.length} of ${checked} file(s) are not formatted.`,
|
|
558
|
-
);
|
|
897
|
+
log.error(`${findings.length} of ${checked} file(s) are not formatted.`);
|
|
559
898
|
process.exitCode = 1;
|
|
560
899
|
} else {
|
|
561
900
|
log.info(`Formatting is clean (${checked} file(s)).`);
|
|
@@ -586,8 +925,7 @@ function markdownCommand() {
|
|
|
586
925
|
describe: "Lint markdown with the shared markdownlint rule set",
|
|
587
926
|
builder: (yargs) => {
|
|
588
927
|
yargs.positional("paths", {
|
|
589
|
-
describe:
|
|
590
|
-
"Globs to lint. Defaults to every markdown file in the repository.",
|
|
928
|
+
describe: "Globs to lint. Defaults to every markdown file in the repository.",
|
|
591
929
|
type: "string",
|
|
592
930
|
});
|
|
593
931
|
yargs.option("fix", {
|
|
@@ -635,8 +973,7 @@ function linksCommand() {
|
|
|
635
973
|
describe: "Check that every link in a content tree lands somewhere",
|
|
636
974
|
builder: (yargs) => {
|
|
637
975
|
yargs.positional("root", {
|
|
638
|
-
describe:
|
|
639
|
-
"Content tree to check. Defaults to the configured contentBase.",
|
|
976
|
+
describe: "Content tree to check. Defaults to the configured contentBase.",
|
|
640
977
|
type: "string",
|
|
641
978
|
});
|
|
642
979
|
yargs.option("manifests", {
|
|
@@ -665,9 +1002,7 @@ function linksCommand() {
|
|
|
665
1002
|
message: `unusable link manifest: ${s.reason}`,
|
|
666
1003
|
});
|
|
667
1004
|
}
|
|
668
|
-
log.error(
|
|
669
|
-
"Refresh the vendored copy from that package's own build.",
|
|
670
|
-
);
|
|
1005
|
+
log.error("Refresh the vendored copy from that package's own build.");
|
|
671
1006
|
process.exitCode = 1;
|
|
672
1007
|
return;
|
|
673
1008
|
}
|
|
@@ -675,14 +1010,10 @@ function linksCommand() {
|
|
|
675
1010
|
// Readable is not the same as addressable: a key shape the
|
|
676
1011
|
// lookup cannot parse makes every cross-package link miss, and
|
|
677
1012
|
// the audit then blames the *notes*.
|
|
678
|
-
const drifted = unaddressableForeignPackages(
|
|
679
|
-
index.foreign.index,
|
|
680
|
-
);
|
|
1013
|
+
const drifted = unaddressableForeignPackages(index.foreign.index);
|
|
681
1014
|
if (drifted.length) {
|
|
682
1015
|
for (const f of drifted) {
|
|
683
|
-
console.error(
|
|
684
|
-
formatUnaddressableFinding(f, manifestDir),
|
|
685
|
-
);
|
|
1016
|
+
console.error(formatUnaddressableFinding(f, manifestDir));
|
|
686
1017
|
}
|
|
687
1018
|
process.exitCode = 1;
|
|
688
1019
|
return;
|
|
@@ -691,6 +1022,8 @@ function linksCommand() {
|
|
|
691
1022
|
const {
|
|
692
1023
|
deadAnchors,
|
|
693
1024
|
deadAddresses,
|
|
1025
|
+
deadAliases,
|
|
1026
|
+
aliasCollisions,
|
|
694
1027
|
frontmatterLinks,
|
|
695
1028
|
homepageLinks,
|
|
696
1029
|
usedManifest,
|
|
@@ -706,14 +1039,62 @@ function linksCommand() {
|
|
|
706
1039
|
`heading in ${d.dest.rel} declares`,
|
|
707
1040
|
});
|
|
708
1041
|
}
|
|
1042
|
+
// The pipe says the author meant an address (#131), so all
|
|
1043
|
+
// three of these are errors — but they read differently
|
|
1044
|
+
// because the corrections differ.
|
|
709
1045
|
for (const d of deadAddresses) {
|
|
710
1046
|
emitDiagnostic({
|
|
711
1047
|
file: d.note.file,
|
|
712
1048
|
...positionOfLiteral(d.note.raw, d.text, d.occurrence),
|
|
713
1049
|
severity: "error",
|
|
714
|
-
message:
|
|
1050
|
+
message:
|
|
1051
|
+
d.reason === "not-an-address" ?
|
|
1052
|
+
`"${d.target}" is written as an address — the ` +
|
|
1053
|
+
`"|" says so — but it is not one; write ` +
|
|
1054
|
+
`[[type-shortcode|Text]], or drop the "|" to ` +
|
|
1055
|
+
`name it as an alias within this note's type`
|
|
1056
|
+
: d.reason === "unknown-type" ?
|
|
1057
|
+
`address [[${d.target}]] names no known ` + `content type`
|
|
1058
|
+
: `dead address [[${d.target}]] — no document ` + `has that identity`,
|
|
715
1059
|
});
|
|
716
1060
|
}
|
|
1061
|
+
// An alias that names nothing may be a worldbuilding
|
|
1062
|
+
// placeholder — a long-standing convention in the setting
|
|
1063
|
+
// trees — so it is reported and does not fail the build. The
|
|
1064
|
+
// ambiguous case is the collision below, reported at its
|
|
1065
|
+
// claimants rather than here.
|
|
1066
|
+
for (const d of deadAliases) {
|
|
1067
|
+
emitDiagnostic({
|
|
1068
|
+
file: d.note.file,
|
|
1069
|
+
...positionOfLiteral(d.note.raw, d.text, d.occurrence),
|
|
1070
|
+
severity: "warning",
|
|
1071
|
+
message:
|
|
1072
|
+
d.ambiguous ?
|
|
1073
|
+
`alias [[${d.target}]] is claimed by ` +
|
|
1074
|
+
`${d.claimants.length} ${d.note.type} notes, ` +
|
|
1075
|
+
`so it names none of them; address the ` +
|
|
1076
|
+
`intended one as [[type-shortcode|Text]]`
|
|
1077
|
+
: `unresolved alias [[${d.target}]] — no ` +
|
|
1078
|
+
`${d.note.type} note claims that name`,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
// Reported once per claimant, at the claimant, because the
|
|
1082
|
+
// note that merely cites an ambiguous alias is innocent (#13).
|
|
1083
|
+
for (const c of aliasCollisions) {
|
|
1084
|
+
const others = c.claimants.map((n) => n.rel).join(", ");
|
|
1085
|
+
for (const claimant of c.claimants) {
|
|
1086
|
+
emitDiagnostic({
|
|
1087
|
+
file: claimant.file,
|
|
1088
|
+
...positionInFrontmatter(claimant.raw, "aliases", c.alias),
|
|
1089
|
+
severity: "error",
|
|
1090
|
+
message:
|
|
1091
|
+
`alias "${c.alias}" is claimed by ` +
|
|
1092
|
+
`${c.claimants.length} ${c.type} notes ` +
|
|
1093
|
+
`(${others}), so [[${c.alias}]] names none of ` +
|
|
1094
|
+
`them; rename all but one`,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
717
1098
|
for (const f of frontmatterLinks) {
|
|
718
1099
|
emitDiagnostic({
|
|
719
1100
|
file: f.note.file,
|
|
@@ -738,23 +1119,32 @@ function linksCommand() {
|
|
|
738
1119
|
});
|
|
739
1120
|
}
|
|
740
1121
|
|
|
1122
|
+
// Warnings are reported and do not fail: a dead alias may be a
|
|
1123
|
+
// note not yet written, which is the placeholder convention.
|
|
741
1124
|
const failures =
|
|
742
1125
|
deadAnchors.length +
|
|
743
1126
|
deadAddresses.length +
|
|
1127
|
+
aliasCollisions.reduce((n, c) => n + c.claimants.length, 0) +
|
|
744
1128
|
frontmatterLinks.length +
|
|
745
1129
|
homepageLinks.length;
|
|
746
1130
|
if (failures) {
|
|
747
|
-
log.error(
|
|
748
|
-
`${failures} link problem(s) across ${index.notes.length} note(s).`,
|
|
749
|
-
);
|
|
1131
|
+
log.error(`${failures} link problem(s) across ${index.notes.length} note(s).`);
|
|
750
1132
|
process.exitCode = 1;
|
|
751
1133
|
} else {
|
|
752
1134
|
log.info(
|
|
753
1135
|
`${index.notes.length} notes: every anchor link lands ` +
|
|
754
|
-
`and every
|
|
1136
|
+
`and every address resolves ` +
|
|
755
1137
|
`(${usedManifest.size} cross-package reference(s) ` +
|
|
756
|
-
`via manifest), no
|
|
757
|
-
`every homepage address
|
|
1138
|
+
`via manifest), no alias claimed twice, no ` +
|
|
1139
|
+
`wikilink in frontmatter, every homepage address ` +
|
|
1140
|
+
`resolvable.`,
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (deadAliases.length) {
|
|
1144
|
+
log.warn(
|
|
1145
|
+
`${deadAliases.length} unresolved alias(es) — a bare ` +
|
|
1146
|
+
`[[Name]] naming no note may be a placeholder, so ` +
|
|
1147
|
+
`these are reported rather than failed.`,
|
|
758
1148
|
);
|
|
759
1149
|
}
|
|
760
1150
|
} catch (err) {
|
|
@@ -789,14 +1179,12 @@ function manifestCommand() {
|
|
|
789
1179
|
describe: "Emit this package's cross-package link manifest",
|
|
790
1180
|
builder: (yargs) => {
|
|
791
1181
|
yargs.positional("root", {
|
|
792
|
-
describe:
|
|
793
|
-
"Content tree to read. Defaults to the configured contentBase.",
|
|
1182
|
+
describe: "Content tree to read. Defaults to the configured contentBase.",
|
|
794
1183
|
type: "string",
|
|
795
1184
|
});
|
|
796
1185
|
yargs.option("out", {
|
|
797
1186
|
describe:
|
|
798
|
-
"Directory to write into. Defaults to the configured " +
|
|
799
|
-
"`paths.manifestOut`.",
|
|
1187
|
+
"Directory to write into. Defaults to the configured " + "`paths.manifestOut`.",
|
|
800
1188
|
type: "string",
|
|
801
1189
|
});
|
|
802
1190
|
},
|
|
@@ -825,10 +1213,7 @@ function manifestCommand() {
|
|
|
825
1213
|
// lost its address becomes a dead link in every consumer.
|
|
826
1214
|
for (const s of skipped) {
|
|
827
1215
|
emitDiagnostic({
|
|
828
|
-
file: path.join(
|
|
829
|
-
argv.root ?? config.paths.content,
|
|
830
|
-
s.file,
|
|
831
|
-
),
|
|
1216
|
+
file: path.join(argv.root ?? config.paths.content, s.file),
|
|
832
1217
|
severity: "warning",
|
|
833
1218
|
message: `no address, so it is absent from the manifest: ${s.reason}`,
|
|
834
1219
|
});
|
|
@@ -864,8 +1249,7 @@ function siteCommand() {
|
|
|
864
1249
|
describe: "Build a Hugo content tree from the content tree",
|
|
865
1250
|
builder: (yargs) => {
|
|
866
1251
|
yargs.option("out", {
|
|
867
|
-
describe:
|
|
868
|
-
"Write the mount here instead of the configured `site.out`.",
|
|
1252
|
+
describe: "Write the mount here instead of the configured `site.out`.",
|
|
869
1253
|
type: "string",
|
|
870
1254
|
});
|
|
871
1255
|
},
|
|
@@ -903,26 +1287,16 @@ function siteCommand() {
|
|
|
903
1287
|
}
|
|
904
1288
|
for (const s of gates.staleManifests) {
|
|
905
1289
|
emitDiagnostic({
|
|
906
|
-
file: path.join(
|
|
907
|
-
loadPackConfig().paths.manifests,
|
|
908
|
-
`${s.package}.json`,
|
|
909
|
-
),
|
|
1290
|
+
file: path.join(loadPackConfig().paths.manifests, `${s.package}.json`),
|
|
910
1291
|
severity: "error",
|
|
911
1292
|
message: `unusable link manifest: ${s.reason}`,
|
|
912
1293
|
});
|
|
913
1294
|
}
|
|
914
1295
|
for (const f of gates.unaddressable) {
|
|
915
|
-
console.error(
|
|
916
|
-
formatUnaddressable(
|
|
917
|
-
f,
|
|
918
|
-
loadPackConfig().paths.manifests,
|
|
919
|
-
),
|
|
920
|
-
);
|
|
1296
|
+
console.error(formatUnaddressable(f, loadPackConfig().paths.manifests));
|
|
921
1297
|
}
|
|
922
1298
|
for (const c of gates.conflicts) {
|
|
923
|
-
log.error(
|
|
924
|
-
`address ${c.key} is also published by ${c.package}`,
|
|
925
|
-
);
|
|
1299
|
+
log.error(`address ${c.key} is also published by ${c.package}`);
|
|
926
1300
|
}
|
|
927
1301
|
if (gatesFailed(gates)) {
|
|
928
1302
|
process.exitCode = 1;
|
|
@@ -937,9 +1311,7 @@ function siteCommand() {
|
|
|
937
1311
|
log.error(`bad content table: ${e.reason} (${e.source})`);
|
|
938
1312
|
}
|
|
939
1313
|
for (const e of result.wikiErrors) {
|
|
940
|
-
log.error(
|
|
941
|
-
`bad wikilink [[${e.target}]]: ${e.reason} (${e.file})`,
|
|
942
|
-
);
|
|
1314
|
+
log.error(`bad wikilink [[${e.target}]]: ${e.reason} (${e.file})`);
|
|
943
1315
|
}
|
|
944
1316
|
if (result.tableErrors.length || result.wikiErrors.length) {
|
|
945
1317
|
process.exitCode = 1;
|
|
@@ -989,8 +1361,7 @@ function reachabilityCommand() {
|
|
|
989
1361
|
describe: "Check that every document in a corpus is reachable",
|
|
990
1362
|
builder: (yargs) => {
|
|
991
1363
|
yargs.positional("dir", {
|
|
992
|
-
describe:
|
|
993
|
-
"The corpus directory, relative to the content tree root.",
|
|
1364
|
+
describe: "The corpus directory, relative to the content tree root.",
|
|
994
1365
|
type: "string",
|
|
995
1366
|
});
|
|
996
1367
|
yargs.positional("file", {
|
|
@@ -1008,8 +1379,7 @@ function reachabilityCommand() {
|
|
|
1008
1379
|
default: [],
|
|
1009
1380
|
});
|
|
1010
1381
|
yargs.option("root", {
|
|
1011
|
-
describe:
|
|
1012
|
-
"Content tree to read. Defaults to the configured contentBase.",
|
|
1382
|
+
describe: "Content tree to read. Defaults to the configured contentBase.",
|
|
1013
1383
|
type: "string",
|
|
1014
1384
|
});
|
|
1015
1385
|
},
|
|
@@ -1026,9 +1396,7 @@ function reachabilityCommand() {
|
|
|
1026
1396
|
stopAt: (n) => indexes.has(String(n.fm.shortcode)),
|
|
1027
1397
|
});
|
|
1028
1398
|
|
|
1029
|
-
const total = index.notes.filter((n) =>
|
|
1030
|
-
n.rel.startsWith(`${dir}/`),
|
|
1031
|
-
).length;
|
|
1399
|
+
const total = index.notes.filter((n) => n.rel.startsWith(`${dir}/`)).length;
|
|
1032
1400
|
|
|
1033
1401
|
for (const o of orphans) {
|
|
1034
1402
|
// Unreachability is a property of the whole document, so
|
|
@@ -1052,8 +1420,7 @@ function reachabilityCommand() {
|
|
|
1052
1420
|
process.exitCode = 1;
|
|
1053
1421
|
} else {
|
|
1054
1422
|
log.info(
|
|
1055
|
-
`All ${total} document(s) in ${dir} are reachable ` +
|
|
1056
|
-
`from ${argv.file}.`,
|
|
1423
|
+
`All ${total} document(s) in ${dir} are reachable ` + `from ${argv.file}.`,
|
|
1057
1424
|
);
|
|
1058
1425
|
}
|
|
1059
1426
|
} catch (err) {
|
|
@@ -1144,8 +1511,7 @@ function depsCommand() {
|
|
|
1144
1511
|
return;
|
|
1145
1512
|
}
|
|
1146
1513
|
const count = await fetchAllCatalogs(config);
|
|
1147
|
-
if (count)
|
|
1148
|
-
log.info(`Fetched ${count} dependency catalogue(s).`);
|
|
1514
|
+
if (count) log.info(`Fetched ${count} dependency catalogue(s).`);
|
|
1149
1515
|
} catch (err) {
|
|
1150
1516
|
reportFailure(err);
|
|
1151
1517
|
process.exitCode = 1;
|
|
@@ -1192,24 +1558,15 @@ async function diffAddresses(config, argv) {
|
|
|
1192
1558
|
...config,
|
|
1193
1559
|
paths: {
|
|
1194
1560
|
...config.paths,
|
|
1195
|
-
foreignCache: path.join(
|
|
1196
|
-
path.dirname(config.paths.foreignCache),
|
|
1197
|
-
"baseline",
|
|
1198
|
-
),
|
|
1561
|
+
foreignCache: path.join(path.dirname(config.paths.foreignCache), "baseline"),
|
|
1199
1562
|
},
|
|
1200
1563
|
};
|
|
1201
|
-
const dir = await fetchCatalogFromPath(
|
|
1202
|
-
cacheConfig,
|
|
1203
|
-
{ id: config.foundryPackage },
|
|
1204
|
-
argv.from,
|
|
1205
|
-
);
|
|
1564
|
+
const dir = await fetchCatalogFromPath(cacheConfig, { id: config.foundryPackage }, argv.from);
|
|
1206
1565
|
// `<id>@<version>`, which is what the diagnostics name the baseline by.
|
|
1207
1566
|
const label = path.basename(dir);
|
|
1208
1567
|
|
|
1209
1568
|
const itemsRoot = path.join(dir, "items");
|
|
1210
|
-
const baselineDirs = fs
|
|
1211
|
-
.readdirSync(itemsRoot)
|
|
1212
|
-
.map((name) => path.join(itemsRoot, name));
|
|
1569
|
+
const baselineDirs = fs.readdirSync(itemsRoot).map((name) => path.join(itemsRoot, name));
|
|
1213
1570
|
const currentDirs = itemPackJsonDirs(config);
|
|
1214
1571
|
if (!currentDirs.length) {
|
|
1215
1572
|
throw new Error(
|
|
@@ -1254,8 +1611,7 @@ async function diffAddresses(config, argv) {
|
|
|
1254
1611
|
function addressesCommand() {
|
|
1255
1612
|
return {
|
|
1256
1613
|
command: "addresses <action>",
|
|
1257
|
-
describe:
|
|
1258
|
-
"Compare the addresses this build publishes against a release's",
|
|
1614
|
+
describe: "Compare the addresses this build publishes against a release's",
|
|
1259
1615
|
builder: (yargs) => {
|
|
1260
1616
|
// Required, for the reason every other action is (#57): an
|
|
1261
1617
|
// optional one exits 0 having compared nothing.
|