@heroiclands/package-build 22.17.0 → 22.17.2
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 +23 -0
- package/bin/content-commands.mjs +114 -37
- package/config.mjs +54 -3
- package/docs/api.md +4 -14
- package/docs/commands.md +89 -52
- package/docs/configuration.md +49 -22
- package/docs/reference/format-details.md +27 -45
- package/docs/reference/note-types.md +1 -1
- package/engine/actor-compiler.mjs +5 -5
- package/engine/address-diff.mjs +32 -279
- package/engine/art-fields.mjs +8 -5
- package/engine/content-format.yaml +4 -4
- package/engine/content-index.mjs +28 -3
- package/engine/content-lint.mjs +3 -182
- package/engine/frontmatter-lint.mjs +112 -59
- package/engine/helpers.mjs +15 -3
- package/engine/index.mjs +0 -3
- package/engine/map-graphviz.mjs +4 -1
- package/engine/note-frontmatter.mjs +1 -0
- package/engine/note-vocabulary.mjs +1 -13
- package/engine/readability-lint.mjs +196 -18
- package/engine/subtype-registry.mjs +2 -6
- package/engine/systems.mjs +11 -13
- package/package.json +5 -1
- package/types/config.d.mts +14 -2
- package/types/engine/actor-compiler.d.mts +5 -5
- package/types/engine/address-diff.d.mts +18 -107
- package/types/engine/art-fields.d.mts +4 -5
- package/types/engine/frontmatter-lint.d.mts +14 -0
- package/types/engine/helpers.d.mts +12 -1
- package/types/engine/index.d.mts +0 -1
- package/types/engine/note-vocabulary.d.mts +0 -9
- package/types/engine/readability-lint.d.mts +11 -5
- package/types/engine/subtype-registry.d.mts +2 -6
- package/types/engine/systems.d.mts +6 -6
- package/engine/note-renames.mjs +0 -134
- package/types/engine/note-renames.d.mts +0 -102
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.17.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
Being notes can identify encounterable NPCs, playable characters, and creatures with a checked subtype.
|
|
8
|
+
|
|
9
|
+
**Content notes**
|
|
10
|
+
|
|
11
|
+
- Notes can carry an additional system-specific block in their frontmatter.
|
|
12
|
+
|
|
13
|
+
**Content checks**
|
|
14
|
+
|
|
15
|
+
- Lint and link checks report unresolved references in SoHL system fields.
|
|
16
|
+
|
|
17
|
+
## 22.17.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
**Prose review**
|
|
22
|
+
|
|
23
|
+
- Prose lint focuses on difficult sentences by default and skips tables and code; simpler-word suggestions remain available on request.
|
|
24
|
+
- Prose score reports each note's readability and lets a content package define an advisory range for its writing.
|
|
25
|
+
|
|
3
26
|
## 22.17.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
package/bin/content-commands.mjs
CHANGED
|
@@ -70,7 +70,12 @@ import { lintContentCharset } from "../engine/content-charset.mjs";
|
|
|
70
70
|
import { lintContentHtml } from "../engine/content-html.mjs";
|
|
71
71
|
import { lintContentIcons } from "../engine/content-icons.mjs";
|
|
72
72
|
import { lintContentImages } from "../engine/content-images.mjs";
|
|
73
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
declaredSystems,
|
|
75
|
+
lintFrontmatter,
|
|
76
|
+
systemAddressFindings,
|
|
77
|
+
systemBlocksFor,
|
|
78
|
+
} from "../engine/frontmatter-lint.mjs";
|
|
74
79
|
import { loadContentFormat } from "../engine/content-format.mjs";
|
|
75
80
|
import {
|
|
76
81
|
checkDeclaredFields,
|
|
@@ -97,7 +102,7 @@ import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
|
|
|
97
102
|
import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
|
|
98
103
|
import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
|
|
99
104
|
import { checkFormatting, checkPrettierConventions, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
100
|
-
import { lintProse } from "../engine/readability-lint.mjs";
|
|
105
|
+
import { lintProse, scoreProseTree } from "../engine/readability-lint.mjs";
|
|
101
106
|
import { loadPackageBuildConfig } from "../config.mjs";
|
|
102
107
|
import {
|
|
103
108
|
authoredFrontmatter,
|
|
@@ -124,7 +129,6 @@ import { reportFindings } from "./report.mjs";
|
|
|
124
129
|
import {
|
|
125
130
|
readItemAddresses,
|
|
126
131
|
diffItemAddresses,
|
|
127
|
-
declaredPredecessors,
|
|
128
132
|
noteFilesById,
|
|
129
133
|
locateAddressFinding,
|
|
130
134
|
addressFindingMessage,
|
|
@@ -240,14 +244,14 @@ export function registerContentCommands(cli) {
|
|
|
240
244
|
.command(addressesCommand());
|
|
241
245
|
}
|
|
242
246
|
|
|
243
|
-
/**
|
|
247
|
+
/** Optional sentence findings and note-level readability reports. */
|
|
244
248
|
function proseCommand() {
|
|
245
249
|
return {
|
|
246
250
|
command: "prose <action> [path]",
|
|
247
251
|
describe: "Analyze note prose on demand",
|
|
248
252
|
builder: (yargs) =>
|
|
249
253
|
yargs
|
|
250
|
-
.positional("action", { choices: ["lint"], describe: "Analyze prose" })
|
|
254
|
+
.positional("action", { choices: ["lint", "score"], describe: "Analyze prose" })
|
|
251
255
|
.positional("path", {
|
|
252
256
|
type: "string",
|
|
253
257
|
describe: "One Markdown file or a content tree",
|
|
@@ -257,17 +261,97 @@ function proseCommand() {
|
|
|
257
261
|
type: "number",
|
|
258
262
|
describe: "Readability algorithms required (1–7)",
|
|
259
263
|
})
|
|
260
|
-
.option("min-words", {
|
|
264
|
+
.option("min-words", {
|
|
265
|
+
type: "number",
|
|
266
|
+
describe: "Minimum words per sentence or note",
|
|
267
|
+
})
|
|
268
|
+
.option("rules", {
|
|
269
|
+
choices: ["readability", "simplify", "all"],
|
|
270
|
+
describe: "Lint rule set",
|
|
271
|
+
})
|
|
272
|
+
.option("fail-on-warning", {
|
|
273
|
+
type: "boolean",
|
|
274
|
+
default: false,
|
|
275
|
+
describe: "Exit unsuccessfully when lint reports suggestions",
|
|
276
|
+
})
|
|
277
|
+
.option("fail-outside", {
|
|
278
|
+
type: "boolean",
|
|
279
|
+
default: false,
|
|
280
|
+
describe: "Exit unsuccessfully when a scored note is outside a configured band",
|
|
281
|
+
}),
|
|
261
282
|
handler: async (argv) => {
|
|
262
283
|
try {
|
|
263
284
|
const config = loadPackConfig();
|
|
264
|
-
const defaults = loadPackageBuildConfig()
|
|
285
|
+
const defaults = loadPackageBuildConfig();
|
|
286
|
+
const root = argv.path ?? config.paths.content;
|
|
287
|
+
const displayFile = (file) => path.relative(process.cwd(), path.resolve(file));
|
|
288
|
+
if (argv.action === "score") {
|
|
289
|
+
if (
|
|
290
|
+
argv.age !== undefined ||
|
|
291
|
+
argv.threshold !== undefined ||
|
|
292
|
+
argv.rules !== undefined ||
|
|
293
|
+
argv.failOnWarning
|
|
294
|
+
) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
"--age, --threshold, --rules, and --fail-on-warning apply only to prose lint",
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const minWords = argv.minWords ?? defaults.proseScore.minWords;
|
|
300
|
+
if (!Number.isInteger(minWords) || minWords < 1) {
|
|
301
|
+
throw new Error("minWords must be a positive integer");
|
|
302
|
+
}
|
|
303
|
+
const result = await scoreProseTree(
|
|
304
|
+
root,
|
|
305
|
+
{ ...defaults.proseScore, minWords },
|
|
306
|
+
config.skipDirectories,
|
|
307
|
+
);
|
|
308
|
+
const fixed = (number, places = 1) => number.toFixed(places);
|
|
309
|
+
for (const note of result.notes) {
|
|
310
|
+
if (note.insufficient) {
|
|
311
|
+
console.log(
|
|
312
|
+
`${displayFile(note.file)}: insufficient prose (${note.words}/${minWords} words; coverage=${fixed(note.coveragePercent)}%)`,
|
|
313
|
+
);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const metrics = note.metrics;
|
|
317
|
+
const detail =
|
|
318
|
+
`flesch=${fixed(metrics.flesch)} syl/word=${fixed(metrics.syllablesPerWord, 2)} ` +
|
|
319
|
+
`mean=${fixed(metrics.meanSentenceWords)}w longest=${note.longestSentenceWords}w ` +
|
|
320
|
+
`unfamiliar=${fixed(metrics.unfamiliarWordPercent)}% ` +
|
|
321
|
+
`nominalizations/1k=${fixed(metrics.nominalizationsPer1000Words)} ` +
|
|
322
|
+
`words=${note.words} coverage=${fixed(note.coveragePercent)}%`;
|
|
323
|
+
if (note.violations.length) {
|
|
324
|
+
emitDiagnostic({
|
|
325
|
+
file: displayFile(note.file),
|
|
326
|
+
severity: "warning",
|
|
327
|
+
message: `prose-score/band: ${detail}; ${note.violations.join(", ")}`,
|
|
328
|
+
});
|
|
329
|
+
} else {
|
|
330
|
+
console.log(`${displayFile(note.file)}: ${detail}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const aggregate = result.summary;
|
|
334
|
+
console.log(
|
|
335
|
+
`Total: ${aggregate.scored} scored, ${aggregate.insufficient} insufficient, ` +
|
|
336
|
+
`${aggregate.outside} outside band; ${aggregate.words} words; ` +
|
|
337
|
+
`pooled flesch=${aggregate.flesch === null ? "n/a" : fixed(aggregate.flesch)} ` +
|
|
338
|
+
`syl/word=${aggregate.words ? fixed(aggregate.syllables / aggregate.words, 2) : "n/a"} ` +
|
|
339
|
+
`mean=${aggregate.sentences ? fixed(aggregate.words / aggregate.sentences) : "n/a"}w ` +
|
|
340
|
+
`unfamiliar=${aggregate.words ? fixed((100 * aggregate.unfamiliar) / aggregate.words) : "n/a"}% ` +
|
|
341
|
+
`nominalizations/1k=${aggregate.words ? fixed((1000 * aggregate.nominalizations) / aggregate.words) : "n/a"}`,
|
|
342
|
+
);
|
|
343
|
+
if (argv.failOutside && aggregate.outside) process.exitCode = 1;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (argv.failOutside) throw new Error("--fail-outside applies only to prose score");
|
|
265
347
|
const options = {
|
|
266
|
-
age: argv.age ?? defaults.age,
|
|
267
|
-
threshold: argv.threshold ?? defaults.threshold,
|
|
268
|
-
minWords: argv.minWords ?? defaults.minWords,
|
|
348
|
+
age: argv.age ?? defaults.proseLint.age,
|
|
349
|
+
threshold: argv.threshold ?? defaults.proseLint.threshold,
|
|
350
|
+
minWords: argv.minWords ?? defaults.proseLint.minWords,
|
|
351
|
+
rules: argv.rules ?? defaults.proseLint.rules,
|
|
269
352
|
};
|
|
270
353
|
for (const [key, value] of Object.entries(options)) {
|
|
354
|
+
if (key === "rules") continue;
|
|
271
355
|
if (
|
|
272
356
|
!Number.isInteger(value) ||
|
|
273
357
|
value < 1 ||
|
|
@@ -278,13 +362,12 @@ function proseCommand() {
|
|
|
278
362
|
);
|
|
279
363
|
}
|
|
280
364
|
}
|
|
281
|
-
const findings = await lintProse(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
);
|
|
286
|
-
for (const finding of findings) emitDiagnostic(finding);
|
|
365
|
+
const findings = await lintProse(root, options, config.skipDirectories);
|
|
366
|
+
for (const finding of findings) {
|
|
367
|
+
emitDiagnostic({ ...finding, file: displayFile(finding.file) });
|
|
368
|
+
}
|
|
287
369
|
log.info(`${findings.length} prose suggestion(s).`);
|
|
370
|
+
if (argv.failOnWarning && findings.length) process.exitCode = 1;
|
|
288
371
|
} catch (err) {
|
|
289
372
|
reportFailure(err);
|
|
290
373
|
process.exitCode = 1;
|
|
@@ -1370,6 +1453,13 @@ function linksCommand() {
|
|
|
1370
1453
|
homepageLinks,
|
|
1371
1454
|
usedManifest,
|
|
1372
1455
|
} = auditLinks(index);
|
|
1456
|
+
const systemReferences = index.notes.flatMap((note) =>
|
|
1457
|
+
systemAddressFindings(note, {
|
|
1458
|
+
index,
|
|
1459
|
+
schemas: { ...ENGINE_NOTE_SCHEMAS, ...NOTE_SCHEMAS },
|
|
1460
|
+
systems: systemBlocksFor(config, { schemaSystem: "sohl" }),
|
|
1461
|
+
}),
|
|
1462
|
+
);
|
|
1373
1463
|
|
|
1374
1464
|
for (const d of deadAnchors) {
|
|
1375
1465
|
emitDiagnostic({
|
|
@@ -1407,6 +1497,7 @@ function linksCommand() {
|
|
|
1407
1497
|
`${f.path} — frontmatter is data and is never resolved`,
|
|
1408
1498
|
});
|
|
1409
1499
|
}
|
|
1500
|
+
for (const finding of systemReferences) emitDiagnostic(finding);
|
|
1410
1501
|
|
|
1411
1502
|
// The package homepage. Its addresses are markdown links and
|
|
1412
1503
|
// `landing:` url/href fields rather than wikilinks — it is
|
|
@@ -1427,6 +1518,7 @@ function linksCommand() {
|
|
|
1427
1518
|
deadEmbeds.length +
|
|
1428
1519
|
unlabelledLinks.length +
|
|
1429
1520
|
frontmatterLinks.length +
|
|
1521
|
+
systemReferences.length +
|
|
1430
1522
|
homepageLinks.length;
|
|
1431
1523
|
if (failures) {
|
|
1432
1524
|
log.error(`${failures} link problem(s) across ${index.notes.length} note(s).`);
|
|
@@ -2179,21 +2271,11 @@ function depsCommand() {
|
|
|
2179
2271
|
* `addresses diff` — report every published `(type, shortcode)` this build no
|
|
2180
2272
|
* longer publishes, against a released artifact.
|
|
2181
2273
|
*
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
* still in front of the author.
|
|
2186
|
-
*
|
|
2187
|
-
* **Its own command rather than a step of `package compile`.** It reads a
|
|
2188
|
-
* *second* artifact that the compile knows nothing about and that has to be
|
|
2189
|
-
* obtained separately, and it is a question about a release rather than about a
|
|
2190
|
-
* build — a repository between releases has nothing to compare against.
|
|
2274
|
+
* Compare this package's compiled Item addresses with the release artifact
|
|
2275
|
+
* named by `--from`. The command reports departed addresses and locates
|
|
2276
|
+
* findings against the current content tree or baseline artifact.
|
|
2191
2277
|
*
|
|
2192
|
-
*
|
|
2193
|
-
* takes the artifact — the `.zip` a release publishes, or a directory built
|
|
2194
|
-
* from one — for the same reason `deps fetch --from` does: a command that
|
|
2195
|
-
* reaches the network on its own is not reproducible and fails strangely
|
|
2196
|
-
* offline. In a release workflow the artifact is one line ahead of it:
|
|
2278
|
+
* `--from` accepts a release `.zip` or a directory built from one:
|
|
2197
2279
|
*
|
|
2198
2280
|
* ```sh
|
|
2199
2281
|
* gh release download v0.8.2 -p system.zip -D build/baseline
|
|
@@ -2253,10 +2335,6 @@ async function diffAddresses(config, argv) {
|
|
|
2253
2335
|
readItemAddresses(currentDirs),
|
|
2254
2336
|
{
|
|
2255
2337
|
baseline: label,
|
|
2256
|
-
// Read whether or not anything departed: an id match needs no tree,
|
|
2257
|
-
// but the diff decides rename-versus-withdrawal as it walks the
|
|
2258
|
-
// baseline, so the declarations have to be in hand before it does.
|
|
2259
|
-
predecessors: declaredPredecessors(config.paths.content, corpus),
|
|
2260
2338
|
},
|
|
2261
2339
|
);
|
|
2262
2340
|
if (!findings.length) {
|
|
@@ -2264,9 +2342,8 @@ async function diffAddresses(config, argv) {
|
|
|
2264
2342
|
return;
|
|
2265
2343
|
}
|
|
2266
2344
|
|
|
2267
|
-
//
|
|
2268
|
-
//
|
|
2269
|
-
// from.
|
|
2345
|
+
// The tree locates a matched document; otherwise the baseline locates the
|
|
2346
|
+
// departed address.
|
|
2270
2347
|
const noteFiles = noteFilesById(config.paths.content, corpus);
|
|
2271
2348
|
const severity = argv.strict ? "error" : "warning";
|
|
2272
2349
|
for (const finding of findings) {
|
package/config.mjs
CHANGED
|
@@ -76,6 +76,7 @@ const SECTION_KEYS = [
|
|
|
76
76
|
"container",
|
|
77
77
|
"e2e",
|
|
78
78
|
"proseLint",
|
|
79
|
+
"proseScore",
|
|
79
80
|
];
|
|
80
81
|
|
|
81
82
|
/**
|
|
@@ -528,7 +529,8 @@ function normalizeExceptions(value, field, where) {
|
|
|
528
529
|
*
|
|
529
530
|
* @typedef {object} PackageBuildConfig
|
|
530
531
|
* @property {string} rootDir The repository root, from package-build.
|
|
531
|
-
* @property {{age: number, threshold: number, minWords: number}} proseLint Optional prose
|
|
532
|
+
* @property {{age: number, threshold: number, minWords: number, rules: string}} proseLint Optional prose lint settings.
|
|
533
|
+
* @property {{minWords: number, bands: Record<string, {min?: number, max?: number}>}} proseScore Optional note score settings.
|
|
532
534
|
* @property {string} packageKind `systems` or `modules`.
|
|
533
535
|
* @property {string} packageId The Foundry package id.
|
|
534
536
|
* @property {string} artifact Derived: `system` or `module`.
|
|
@@ -693,9 +695,20 @@ export function resolvePackageBuildConfig(shared) {
|
|
|
693
695
|
|
|
694
696
|
const proseLint = section.proseLint ?? {};
|
|
695
697
|
if (!isMapping(proseLint)) fail("packageBuild.proseLint", "must be a mapping");
|
|
696
|
-
rejectUnknownKeys(
|
|
697
|
-
|
|
698
|
+
rejectUnknownKeys(
|
|
699
|
+
proseLint,
|
|
700
|
+
["age", "threshold", "minWords", "rules"],
|
|
701
|
+
"packageBuild.proseLint.",
|
|
702
|
+
);
|
|
703
|
+
const proseOptions = { age: 21, threshold: 5, minWords: 8, rules: "readability" };
|
|
698
704
|
for (const [key, value] of Object.entries(proseLint)) {
|
|
705
|
+
if (key === "rules") {
|
|
706
|
+
if (!["readability", "simplify", "all"].includes(value)) {
|
|
707
|
+
fail("packageBuild.proseLint.rules", "must be readability, simplify, or all");
|
|
708
|
+
}
|
|
709
|
+
proseOptions.rules = value;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
699
712
|
const upper = key === "threshold" ? 7 : Infinity;
|
|
700
713
|
if (!Number.isInteger(value) || value < 1 || value > upper) {
|
|
701
714
|
fail(
|
|
@@ -708,6 +721,43 @@ export function resolvePackageBuildConfig(shared) {
|
|
|
708
721
|
proseOptions[key] = value;
|
|
709
722
|
}
|
|
710
723
|
|
|
724
|
+
const proseScore = section.proseScore ?? {};
|
|
725
|
+
if (!isMapping(proseScore)) fail("packageBuild.proseScore", "must be a mapping");
|
|
726
|
+
rejectUnknownKeys(proseScore, ["minWords", "bands"], "packageBuild.proseScore.");
|
|
727
|
+
const scoreMinWords = proseScore.minWords ?? 80;
|
|
728
|
+
if (!Number.isInteger(scoreMinWords) || scoreMinWords < 1) {
|
|
729
|
+
fail("packageBuild.proseScore.minWords", "must be a positive integer");
|
|
730
|
+
}
|
|
731
|
+
const bands = proseScore.bands ?? {};
|
|
732
|
+
if (!isMapping(bands)) fail("packageBuild.proseScore.bands", "must be a mapping");
|
|
733
|
+
const scoreMetrics = [
|
|
734
|
+
"flesch",
|
|
735
|
+
"syllablesPerWord",
|
|
736
|
+
"meanSentenceWords",
|
|
737
|
+
"longestSentenceWords",
|
|
738
|
+
"unfamiliarWordPercent",
|
|
739
|
+
"nominalizationsPer1000Words",
|
|
740
|
+
];
|
|
741
|
+
rejectUnknownKeys(bands, scoreMetrics, "packageBuild.proseScore.bands.");
|
|
742
|
+
const scoreBands = {};
|
|
743
|
+
for (const [metric, band] of Object.entries(bands)) {
|
|
744
|
+
const where = `packageBuild.proseScore.bands.${metric}`;
|
|
745
|
+
if (!isMapping(band)) fail(where, "must be a mapping");
|
|
746
|
+
rejectUnknownKeys(band, ["min", "max"], `${where}.`);
|
|
747
|
+
if (band.min === undefined && band.max === undefined) {
|
|
748
|
+
fail(where, "must specify min, max, or both");
|
|
749
|
+
}
|
|
750
|
+
for (const [limit, value] of Object.entries(band)) {
|
|
751
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
752
|
+
fail(`${where}.${limit}`, "must be a finite number");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (band.min !== undefined && band.max !== undefined && band.min > band.max) {
|
|
756
|
+
fail(where, "min must not exceed max");
|
|
757
|
+
}
|
|
758
|
+
scoreBands[metric] = Object.freeze({ ...band });
|
|
759
|
+
}
|
|
760
|
+
|
|
711
761
|
if (section.assets !== undefined && !Array.isArray(section.assets)) {
|
|
712
762
|
fail("packageBuild.assets", "must be a list");
|
|
713
763
|
}
|
|
@@ -809,6 +859,7 @@ export function resolvePackageBuildConfig(shared) {
|
|
|
809
859
|
return Object.freeze({
|
|
810
860
|
rootDir: shared.rootDir,
|
|
811
861
|
proseLint: Object.freeze(proseOptions),
|
|
862
|
+
proseScore: Object.freeze({ minWords: scoreMinWords, bands: Object.freeze(scoreBands) }),
|
|
812
863
|
// Where the package is assembled before it is zipped or deployed. Every
|
|
813
864
|
// asset destination is relative to it, so a repository's table says
|
|
814
865
|
// `lang`, not `build/stage/lang` — the latter is what each consumer's
|
package/docs/api.md
CHANGED
|
@@ -347,7 +347,7 @@ The note types the engine itself declares — vocabulary that is a fact about th
|
|
|
347
347
|
|
|
348
348
|
### `engine.noteVocabulary`
|
|
349
349
|
|
|
350
|
-
|
|
350
|
+
An addressed content note accepts exactly these top-level keys, in this order: `shortcode`, `name`, `type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`, `dnd5e`. A key outside this list is an error. The address vocabulary recognizes three systems: `sohl`, `hm3`, and `dnd5e`. No dnd5e-specific document types are declared. Within that shape, the closed vocabularies are the `data:` container and each type's `subType`.
|
|
351
351
|
|
|
352
352
|
| Export | Signature | Returns | Use it when |
|
|
353
353
|
| ------------------------- | -------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
@@ -358,7 +358,7 @@ The closed half of a note's frontmatter: the `data:` container and each type's `
|
|
|
358
358
|
| `DECLARED_TAGS` | `const DECLARED_TAGS` | — | looking up the tags a note type may declare, grouped |
|
|
359
359
|
| `declaredTags` | `declaredTags(type, groups)` | `readonly string[]` | reading the declared tags a note of a type may carry, flattened |
|
|
360
360
|
| `applicableTagGroups` | `applicableTagGroups(type, groups)` | `object[]` | reading the declared tag groups that apply to a note type |
|
|
361
|
-
| `exclusiveTagGroups` | `exclusiveTagGroups(type, groups)` | `Array<{slot, tags}>` | reading
|
|
361
|
+
| `exclusiveTagGroups` | `exclusiveTagGroups(type, groups)` | `Array<{slot, tags}>` | reading single-valued tag slots declared for a note type |
|
|
362
362
|
| `hasTag` | `hasTag(fm, tag)` | `boolean` | checking whether a note carries a given tag, whatever scalar-or-list form it was authored in |
|
|
363
363
|
| `isDraftNote` | `isDraftNote(fm)` | `boolean` | checking whether a note is tagged as an unfinished draft |
|
|
364
364
|
| `isGmNote` | `isGmNote(fm)` | `boolean` | checking whether a note is restricted to GM-facing output |
|
|
@@ -607,13 +607,13 @@ The map from each place, drawn for the site: `package-build site` draws the map
|
|
|
607
607
|
|
|
608
608
|
### `engine.systems`
|
|
609
609
|
|
|
610
|
-
The closed registry of system ids, and the `none` that stands for no system at all. An unknown system value is an error; adding a system is a data change to this registry rather than a hardcoded set scattered through the pipeline.
|
|
610
|
+
The closed registry of system ids, and the `none` that stands for no system at all. An unknown system value is an error; adding a system is a data change to this registry rather than a hardcoded set scattered through the pipeline. The registry recognizes `sohl`, `hm3`, and `dnd5e`; no dnd5e-specific document types are declared.
|
|
611
611
|
|
|
612
612
|
| Export | Signature | Returns | Use it when |
|
|
613
613
|
| ---------------------- | -------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------- |
|
|
614
614
|
| `NO_SYSTEM` | `const NO_SYSTEM` | — | naming a systemless asset or Foundry document |
|
|
615
615
|
| `NOTE_SYSTEM` | `const NOTE_SYSTEM` | — | naming readable note content across web, book and Foundry |
|
|
616
|
-
| `SYSTEM_IDS` | `const SYSTEM_IDS` | — | enumerating
|
|
616
|
+
| `SYSTEM_IDS` | `const SYSTEM_IDS` | — | enumerating game system ids accepted in configuration and addresses |
|
|
617
617
|
| `SYSTEM_SEGMENTS` | `const SYSTEM_SEGMENTS` | — | enumerating the game systems, `note` and `none` |
|
|
618
618
|
| `isSystemId` | `isSystemId(value)` | `boolean` | checking whether a value names a game system this toolchain knows (`none` is rejected) |
|
|
619
619
|
| `isSystemSegment` | `isSystemSegment(value)` | `boolean` | checking whether a value is something the `<system>` address segment may hold |
|
|
@@ -809,16 +809,6 @@ The id a note's document is filed under: its pinned `id:`, when a note authors o
|
|
|
809
809
|
| `noteDocId` | `function noteDocId(fm,` | {string\|undefined} The document's `_id`, or `undefined` when the note has no address to derive one from. | The document id a note compiles under: its pin, or its address. |
|
|
810
810
|
| `resolveNoteId` | `function resolveNoteId(fm, opts)` | {object\|null\|undefined} `fm`, for chaining. | Fill a note's `id` in place, so everything downstream reads one value. |
|
|
811
811
|
|
|
812
|
-
### `engine.noteRenames`
|
|
813
|
-
|
|
814
|
-
The shortcodes a note declares it used to be published under. A package's `(type, shortcode)` addresses are a published interface, and `addresses diff` reports what a build stopped publishing — telling a **rename** from a **withdrawal** by matching document ids across two releases. The property that rested on is gone: an id is derived from the canonical address, which carries the shortcode, so renaming a shortcode moves the id too: both sides of the join move together, the match finds nothing, and a rename is reported as a withdrawal with no successor named.
|
|
815
|
-
|
|
816
|
-
| Export | Signature | Returns | Use it when |
|
|
817
|
-
| --------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
|
|
818
|
-
| `renamedFromEntries` | `function renamedFromEntries(fm)` | {readonly unknown[]} The authored entries, in authored order; empty when the note declares none. | The `renamedFrom:` entries a note authors, exactly as authored. |
|
|
819
|
-
| `renamedFrom` | `function renamedFrom(fm)` | {string[]} The declared predecessor shortcodes. | The well-formed shortcodes among a note's `renamedFrom:` entries. |
|
|
820
|
-
| `declaresRenamedFrom` | `function declaresRenamedFrom(fm)` | {boolean} `true` when the key is present and not null. | Whether a note declares the key at all, however malformed its value. |
|
|
821
|
-
|
|
822
812
|
### `engine.metadataIndex`
|
|
823
813
|
|
|
824
814
|
The published content index — the artifact packages exchange addresses through. **A package publishes its own index; a consumer fetches the ones it depends on.** That is the whole mechanism, and it replaces a vendored link manifest that each repository committed a copy of every other repository's file into. Vendoring failed three ways, and only the last is about staleness:
|