@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/engine/base-compiler.mjs
CHANGED
|
@@ -79,6 +79,9 @@ import { emitDiagnostic } from "./diagnostics.mjs";
|
|
|
79
79
|
import { assertNoDeclaredPackage } from "./note-package.mjs";
|
|
80
80
|
import { assertNoDraftField } from "./retired-fields.mjs";
|
|
81
81
|
import { assertTypeNotRetired, packForType } from "./ids.mjs";
|
|
82
|
+
import { carriesSystemBlock } from "./system-block.mjs";
|
|
83
|
+
import { checkAuthoredSystemData, checkEmittedSystemData } from "./schema-check.mjs";
|
|
84
|
+
import { locateFrontmatterKey } from "./retired-fields.mjs";
|
|
82
85
|
|
|
83
86
|
/**
|
|
84
87
|
* The tallies one pass accumulates while walking the tree.
|
|
@@ -93,8 +96,9 @@ import { assertTypeNotRetired, packForType } from "./ids.mjs";
|
|
|
93
96
|
* @property {number} compiled - Notes that became a document.
|
|
94
97
|
* @property {number} skippedNoId - Notes with no `id`, where that is tolerated.
|
|
95
98
|
* @property {number} skippedOther - Notes this pass does not claim.
|
|
96
|
-
* @property {number} declined - Notes refused
|
|
97
|
-
* frontmatter field
|
|
99
|
+
* @property {number} declined - Notes this pack **refused** — one declaring a
|
|
100
|
+
* retired frontmatter field, or one routed to a system pack whose system it
|
|
101
|
+
* says nothing about (#58). Counted as errors, never as skips.
|
|
98
102
|
*/
|
|
99
103
|
|
|
100
104
|
/**
|
|
@@ -167,6 +171,26 @@ export class BasePackCompiler {
|
|
|
167
171
|
*/
|
|
168
172
|
static readsPackOutputOf = Object.freeze([]);
|
|
169
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Whether this pass's document **is** a system's data, and therefore takes
|
|
176
|
+
* only notes that carry that system's block (#58).
|
|
177
|
+
*
|
|
178
|
+
* A pack may declare a `system:` — `harn-ensemble` ships an `actors-hm3`
|
|
179
|
+
* and an `actors-sohl` from one tree — and the note-side half of that is
|
|
180
|
+
* the block named after the system. A note carrying no such block has
|
|
181
|
+
* nothing to say about it, so compiling it there would emit a **hollow
|
|
182
|
+
* document**: a subtype, and none of the fields the subtype exists for.
|
|
183
|
+
*
|
|
184
|
+
* False by default, because most passes write documents that are not
|
|
185
|
+
* system data at all. A JournalEntry of prose is the same document under
|
|
186
|
+
* either system, and a journals pack that declared one must not turn every
|
|
187
|
+
* doc note in the tree into a finding. The Item and Actor passes say so;
|
|
188
|
+
* anything else that genuinely writes a system's data says so too.
|
|
189
|
+
*
|
|
190
|
+
* @type {boolean}
|
|
191
|
+
*/
|
|
192
|
+
static requiresSystemBlock = false;
|
|
193
|
+
|
|
170
194
|
/** @type {string} */
|
|
171
195
|
contentBase;
|
|
172
196
|
/** @type {string} */
|
|
@@ -176,6 +200,19 @@ export class BasePackCompiler {
|
|
|
176
200
|
/** @type {number} */
|
|
177
201
|
errorCount = 0;
|
|
178
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Emitted-`system` findings, one per `documentType|subtype|field` (#155).
|
|
205
|
+
*
|
|
206
|
+
* A key the compiler writes is on **every** document of a subtype, so
|
|
207
|
+
* reporting it where it is found would print the same sentence 3,126 times
|
|
208
|
+
* and bury the one that is not systemic. Collected here instead and flushed
|
|
209
|
+
* once at the end of the pass, keyed so the class of defect is reported
|
|
210
|
+
* once and the first document carrying it names a file a reader can open.
|
|
211
|
+
*
|
|
212
|
+
* @type {Map<string, {message: string, file: string|undefined}>}
|
|
213
|
+
*/
|
|
214
|
+
emittedFindings = new Map();
|
|
215
|
+
|
|
179
216
|
/**
|
|
180
217
|
* The pack this pass writes, and the Foundry document type it holds.
|
|
181
218
|
*
|
|
@@ -244,9 +281,7 @@ export class BasePackCompiler {
|
|
|
244
281
|
routingReporter = false,
|
|
245
282
|
} = {}) {
|
|
246
283
|
if (!contentBase) {
|
|
247
|
-
throw new Error(
|
|
248
|
-
`${this.constructor.name} compiler requires \`contentBase\``,
|
|
249
|
-
);
|
|
284
|
+
throw new Error(`${this.constructor.name} compiler requires \`contentBase\``);
|
|
250
285
|
}
|
|
251
286
|
if (!fs.existsSync(contentBase)) {
|
|
252
287
|
throw new Error(`Content tree not found at ${contentBase}`);
|
|
@@ -302,7 +337,36 @@ export class BasePackCompiler {
|
|
|
302
337
|
*/
|
|
303
338
|
routesHere(fm) {
|
|
304
339
|
if (!this.router || !this.packName || !this.docType) return true;
|
|
305
|
-
return
|
|
340
|
+
return (
|
|
341
|
+
this.router.resolve(fm, this.docType, this.packSystem ?? undefined) === this.packName
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Whether a claimed, routed note may become this pack's document at all.
|
|
347
|
+
*
|
|
348
|
+
* The pack-eligibility gate, and it fails rather than skipping: a note that
|
|
349
|
+
* routed *here* and carries nothing for this pack's system is an authoring
|
|
350
|
+
* mistake with a hollow document at the end of it, not a note that belongs
|
|
351
|
+
* to another pass. Skipping it quietly is how a whole tree compiles to
|
|
352
|
+
* documents nobody can use — the failure mode #1502 and #56 are both
|
|
353
|
+
* instances of.
|
|
354
|
+
*
|
|
355
|
+
* @param {object} fm - The note's frontmatter.
|
|
356
|
+
* @returns {boolean} True when the note may be compiled here.
|
|
357
|
+
* @throws {Error} When this pack's system is absent from the note. The
|
|
358
|
+
* error carries a `position` where the note's own file can be read.
|
|
359
|
+
*/
|
|
360
|
+
eligibleFor(fm) {
|
|
361
|
+
if (!this.constructor.requiresSystemBlock || !this.packSystem) return true;
|
|
362
|
+
if (carriesSystemBlock(fm, this.packSystem)) return true;
|
|
363
|
+
const label = fm?.name?.full ?? fm?.shortcode ?? fm?.id ?? "this note";
|
|
364
|
+
throw new Error(
|
|
365
|
+
`${label} carries no \`${this.packSystem}:\` block, so it has no ` +
|
|
366
|
+
`${this.packSystem} data to compile — but it routes to pack ` +
|
|
367
|
+
`"${this.packName}", which declares \`system: ${this.packSystem}\`. ` +
|
|
368
|
+
`Add the block, or route the note to a pack of another system.`,
|
|
369
|
+
);
|
|
306
370
|
}
|
|
307
371
|
|
|
308
372
|
/**
|
|
@@ -358,10 +422,7 @@ export class BasePackCompiler {
|
|
|
358
422
|
*/
|
|
359
423
|
async prepare() {
|
|
360
424
|
if (this.constructor.convertsWikilinks) {
|
|
361
|
-
this.linkIndex = buildContentLinkIndex(
|
|
362
|
-
this.contentBase,
|
|
363
|
-
this.router,
|
|
364
|
-
);
|
|
425
|
+
this.linkIndex = buildContentLinkIndex(this.contentBase, this.router);
|
|
365
426
|
this.contentDocs = collectContentDocs(this.contentBase);
|
|
366
427
|
}
|
|
367
428
|
this.unresolvedLinks = 0;
|
|
@@ -447,6 +508,113 @@ export class BasePackCompiler {
|
|
|
447
508
|
});
|
|
448
509
|
}
|
|
449
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Report every `<system>.system` key the receiving subtype does not declare
|
|
513
|
+
* (#58).
|
|
514
|
+
*
|
|
515
|
+
* An **error**, not a warning: Foundry drops an unknown `system` key at
|
|
516
|
+
* construction without a word, so the alternative is a document shipped
|
|
517
|
+
* with a field the author wrote and nobody will ever see. Each finding is
|
|
518
|
+
* located at the offending key where the file can be read, so it points at
|
|
519
|
+
* a line rather than at a note.
|
|
520
|
+
*
|
|
521
|
+
* Silent where nothing can answer — no published schema, or a subtype the
|
|
522
|
+
* artifact does not name. `content-build lint` says that out loud once for
|
|
523
|
+
* the whole build rather than once per note.
|
|
524
|
+
*
|
|
525
|
+
* @param {object} fm - The note's frontmatter.
|
|
526
|
+
* @param {string} block - The system block to read.
|
|
527
|
+
* @param {string} documentType - `Item`, `Actor`, …
|
|
528
|
+
* @param {string} subType - The subtype this note compiles into.
|
|
529
|
+
* @returns {number} How many findings were reported.
|
|
530
|
+
*/
|
|
531
|
+
reportUndeclaredSystemData(fm, block, documentType, subType) {
|
|
532
|
+
const absPath = this.currentNote?.absPath;
|
|
533
|
+
const findings = checkAuthoredSystemData(fm, { block, documentType, subType });
|
|
534
|
+
for (const finding of findings) {
|
|
535
|
+
this.errorCount++;
|
|
536
|
+
const leaf = finding.path.split(".").pop();
|
|
537
|
+
this.noteError(finding.message, locateFrontmatterKey(absPath, leaf));
|
|
538
|
+
}
|
|
539
|
+
return findings.length;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Record every `system` key the *compiled document* carries that the
|
|
544
|
+
* receiving subtype does not declare (#155).
|
|
545
|
+
*
|
|
546
|
+
* The sibling of {@link BasePackCompiler#reportUndeclaredSystemData}, and
|
|
547
|
+
* the half that sees what no declaration states. A compiler writes keys of
|
|
548
|
+
* its own alongside the declared fields — `shortcode`, `actionDefs`,
|
|
549
|
+
* `notes`, `docHtml`, `archetype` — and neither the field-declaration check
|
|
550
|
+
* nor the authored-`system` check can see them, so until this nothing
|
|
551
|
+
* compared them at all. Foundry's discard is the same silent one either
|
|
552
|
+
* way.
|
|
553
|
+
*
|
|
554
|
+
* Called with the block **after** the builder, the authored merge and any
|
|
555
|
+
* conditional fields have all written into it, so what is checked is what
|
|
556
|
+
* the pack file receives.
|
|
557
|
+
*
|
|
558
|
+
* Recorded rather than reported: see {@link BasePackCompiler#emittedFindings}
|
|
559
|
+
* for why, and {@link BasePackCompiler#reportEmittedFindings} for where they
|
|
560
|
+
* come out.
|
|
561
|
+
*
|
|
562
|
+
* @param {object} system - The `system` block just assembled.
|
|
563
|
+
* @param {object} opts
|
|
564
|
+
* @param {object} opts.fm - The note's frontmatter.
|
|
565
|
+
* @param {string} opts.block - The system block the note writes.
|
|
566
|
+
* @param {string} opts.documentType - `Item`, `Actor`, …
|
|
567
|
+
* @param {string} opts.subType - The subtype this note compiles into.
|
|
568
|
+
* @param {string} opts.type - The note's content type.
|
|
569
|
+
* @param {readonly {to?: string}[]} [opts.fields] - The type's field
|
|
570
|
+
* declaration, which tells a builder emission from a compiler one.
|
|
571
|
+
* @param {object} [opts.config] - The resolved build configuration.
|
|
572
|
+
* @returns {number} How many findings were new to this pass.
|
|
573
|
+
*/
|
|
574
|
+
reportEmittedSystemData(system, { fm, block, documentType, subType, type, fields, config }) {
|
|
575
|
+
const findings = checkEmittedSystemData(system, {
|
|
576
|
+
fm,
|
|
577
|
+
block,
|
|
578
|
+
documentType,
|
|
579
|
+
subType,
|
|
580
|
+
type,
|
|
581
|
+
fields,
|
|
582
|
+
...(config ? { config } : {}),
|
|
583
|
+
});
|
|
584
|
+
let added = 0;
|
|
585
|
+
for (const finding of findings) {
|
|
586
|
+
const key = `${finding.documentType}|${finding.subtype}|${finding.field}`;
|
|
587
|
+
if (this.emittedFindings.has(key)) continue;
|
|
588
|
+
this.emittedFindings.set(key, {
|
|
589
|
+
message: finding.message,
|
|
590
|
+
file: this.currentNote?.absPath,
|
|
591
|
+
});
|
|
592
|
+
added++;
|
|
593
|
+
}
|
|
594
|
+
return added;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Emit the collected emitted-`system` findings, once each.
|
|
599
|
+
*
|
|
600
|
+
* An **error**, for the reason #60 made its sibling one: the value is gone
|
|
601
|
+
* at load and the build says nothing, and severity that varied by *which
|
|
602
|
+
* part of the build wrote the key* would make the less fixable half the
|
|
603
|
+
* quieter one. What varies is the message, which says whose fix it is —
|
|
604
|
+
* see {@link module:engine/schema-check.emittedUndeclaredMessage}.
|
|
605
|
+
*
|
|
606
|
+
* @returns {number} How many were reported.
|
|
607
|
+
*/
|
|
608
|
+
reportEmittedFindings() {
|
|
609
|
+
const found = this.emittedFindings;
|
|
610
|
+
this.emittedFindings = new Map();
|
|
611
|
+
for (const finding of found.values()) {
|
|
612
|
+
this.errorCount++;
|
|
613
|
+
emitDiagnostic({ file: finding.file, severity: "error", message: finding.message });
|
|
614
|
+
}
|
|
615
|
+
return found.size;
|
|
616
|
+
}
|
|
617
|
+
|
|
450
618
|
/**
|
|
451
619
|
* One note → one document. **Required.**
|
|
452
620
|
*
|
|
@@ -457,9 +625,7 @@ export class BasePackCompiler {
|
|
|
457
625
|
*/
|
|
458
626
|
// eslint-disable-next-line no-unused-vars
|
|
459
627
|
buildEntry(fm, markdown) {
|
|
460
|
-
throw new Error(
|
|
461
|
-
`${this.constructor.name} must implement buildEntry(fm, markdown)`,
|
|
462
|
-
);
|
|
628
|
+
throw new Error(`${this.constructor.name} must implement buildEntry(fm, markdown)`);
|
|
463
629
|
}
|
|
464
630
|
|
|
465
631
|
/**
|
|
@@ -526,9 +692,7 @@ export class BasePackCompiler {
|
|
|
526
692
|
*/
|
|
527
693
|
reportCompiled(stats) {
|
|
528
694
|
const label = this.constructor.label;
|
|
529
|
-
log.info(
|
|
530
|
-
`Compiled ${stats.compiled} ${label}${stats.compiled === 1 ? "" : "s"}`,
|
|
531
|
-
);
|
|
695
|
+
log.info(`Compiled ${stats.compiled} ${label}${stats.compiled === 1 ? "" : "s"}`);
|
|
532
696
|
}
|
|
533
697
|
|
|
534
698
|
/**
|
|
@@ -538,9 +702,7 @@ export class BasePackCompiler {
|
|
|
538
702
|
* @param {PassStats} stats - The pass's tallies.
|
|
539
703
|
*/
|
|
540
704
|
reportDetail(stats) {
|
|
541
|
-
log.debug(
|
|
542
|
-
`Skipped ${stats.skippedOther} file(s) this pack does not claim`,
|
|
543
|
-
);
|
|
705
|
+
log.debug(`Skipped ${stats.skippedOther} file(s) this pack does not claim`);
|
|
544
706
|
}
|
|
545
707
|
|
|
546
708
|
/**
|
|
@@ -563,8 +725,8 @@ export class BasePackCompiler {
|
|
|
563
725
|
// them in the skipped tally is the defect (#56). Each one has
|
|
564
726
|
// already been named individually as a diagnostic.
|
|
565
727
|
log.error(
|
|
566
|
-
`Declined ${stats.declined} note(s)
|
|
567
|
-
`
|
|
728
|
+
`Declined ${stats.declined} note(s) — each named above, with ` +
|
|
729
|
+
`the reason this pack would not compile it`,
|
|
568
730
|
);
|
|
569
731
|
}
|
|
570
732
|
this.reportDetail(stats);
|
|
@@ -588,13 +750,9 @@ export class BasePackCompiler {
|
|
|
588
750
|
const label = this.constructor.label;
|
|
589
751
|
const Label = label.charAt(0).toUpperCase() + label.slice(1);
|
|
590
752
|
|
|
591
|
-
for (const {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
absPath,
|
|
595
|
-
bodyLine,
|
|
596
|
-
bodyColumn,
|
|
597
|
-
} of walkMarkdownTree(this.contentBase)) {
|
|
753
|
+
for (const { frontmatter: fm, body, absPath, bodyLine, bodyColumn } of walkMarkdownTree(
|
|
754
|
+
this.contentBase,
|
|
755
|
+
)) {
|
|
598
756
|
// Which note this pass is on, so anything it calls can report a
|
|
599
757
|
// position without every method having to be handed one (#17).
|
|
600
758
|
this.currentNote = { absPath, bodyLine, bodyColumn };
|
|
@@ -660,14 +818,24 @@ export class BasePackCompiler {
|
|
|
660
818
|
}
|
|
661
819
|
continue;
|
|
662
820
|
}
|
|
821
|
+
// Whether this pack's system is one the note speaks for. Checked
|
|
822
|
+
// after routing — a note bound for another pack is none of this
|
|
823
|
+
// pass's business — and before `skipNote`, so a pass's own
|
|
824
|
+
// rejection rules never run on a note it may not compile.
|
|
825
|
+
try {
|
|
826
|
+
this.eligibleFor(fm);
|
|
827
|
+
} catch (err) {
|
|
828
|
+
stats.declined++;
|
|
829
|
+
this.errorCount++;
|
|
830
|
+
this.noteError(err.message, err.position);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
663
833
|
if (this.skipNote(fm, body)) {
|
|
664
834
|
stats.skippedOther++;
|
|
665
835
|
continue;
|
|
666
836
|
}
|
|
667
837
|
|
|
668
|
-
log.debug(
|
|
669
|
-
`Processing ${this.noteLabel(fm)}: ${resolveName(fm)} (${absPath})`,
|
|
670
|
-
);
|
|
838
|
+
log.debug(`Processing ${this.noteLabel(fm)}: ${resolveName(fm)} (${absPath})`);
|
|
671
839
|
try {
|
|
672
840
|
const doc = this.compileNote(fm, this.convertBody(fm, body));
|
|
673
841
|
stats.compiled++;
|
|
@@ -686,6 +854,10 @@ export class BasePackCompiler {
|
|
|
686
854
|
|
|
687
855
|
this.compiledCount = stats.compiled;
|
|
688
856
|
await this.finish(stats);
|
|
857
|
+
// After `finish`, because a pass that writes documents there (the
|
|
858
|
+
// scenes pass bundles an Adventure) has emitted them by now; before
|
|
859
|
+
// `report`, so the pass's summary counts them.
|
|
860
|
+
this.reportEmittedFindings();
|
|
689
861
|
this.report(stats);
|
|
690
862
|
}
|
|
691
863
|
}
|
package/engine/code-fences.mjs
CHANGED
|
@@ -111,9 +111,7 @@ export function codeRegions(markdown, { spans = true } = {}) {
|
|
|
111
111
|
const fence = FENCE_LINE.exec(line);
|
|
112
112
|
if (fence) {
|
|
113
113
|
const marker = fence[2];
|
|
114
|
-
const closer = new RegExp(
|
|
115
|
-
`^[ \\t]*${marker[0]}{${marker.length},}[ \\t]*$`,
|
|
116
|
-
);
|
|
114
|
+
const closer = new RegExp(`^[ \\t]*${marker[0]}{${marker.length},}[ \\t]*$`);
|
|
117
115
|
let close = i + 1;
|
|
118
116
|
while (close < lines.length && !closer.test(lines[close])) close++;
|
|
119
117
|
// An unclosed fence runs to the end of the document.
|
|
@@ -225,9 +223,7 @@ function inside(regions, offset) {
|
|
|
225
223
|
*/
|
|
226
224
|
function offsetOf(args) {
|
|
227
225
|
const last = args[args.length - 1];
|
|
228
|
-
return typeof last === "string" ?
|
|
229
|
-
args[args.length - 2]
|
|
230
|
-
: args[args.length - 3];
|
|
226
|
+
return typeof last === "string" ? args[args.length - 2] : args[args.length - 3];
|
|
231
227
|
}
|
|
232
228
|
|
|
233
229
|
/**
|
|
@@ -263,9 +259,7 @@ export function replaceOutsideCode(markdown, pattern, replacer, options) {
|
|
|
263
259
|
export function matchAllOutsideCode(markdown, pattern, options) {
|
|
264
260
|
const src = String(markdown ?? "");
|
|
265
261
|
const regions = codeRegions(src, options);
|
|
266
|
-
return [...src.matchAll(pattern)].filter(
|
|
267
|
-
(match) => !inside(regions, match.index),
|
|
268
|
-
);
|
|
262
|
+
return [...src.matchAll(pattern)].filter((match) => !inside(regions, match.index));
|
|
269
263
|
}
|
|
270
264
|
|
|
271
265
|
/**
|
|
@@ -304,8 +298,5 @@ export function protectCode(body, transform) {
|
|
|
304
298
|
last = region.end;
|
|
305
299
|
}
|
|
306
300
|
masked += src.slice(last);
|
|
307
|
-
return transform(masked).replace(
|
|
308
|
-
/\u0000(\d+)\u0000/g,
|
|
309
|
-
(_m, i) => stash[Number(i)],
|
|
310
|
-
);
|
|
301
|
+
return transform(masked).replace(/\u0000(\d+)\u0000/g, (_m, i) => stash[Number(i)]);
|
|
311
302
|
}
|
package/engine/compendiums.mjs
CHANGED
|
@@ -87,17 +87,13 @@ export async function compilePacks({
|
|
|
87
87
|
stageDest = config.paths.stage,
|
|
88
88
|
packName,
|
|
89
89
|
} = {}) {
|
|
90
|
-
const packNames = sourcePacks.filter(
|
|
91
|
-
(name) => !packName || name === packName,
|
|
92
|
-
);
|
|
90
|
+
const packNames = sourcePacks.filter((name) => !packName || name === packName);
|
|
93
91
|
|
|
94
92
|
// A prebuilt pack's JSON is checked in rather than generated, so it is
|
|
95
93
|
// compiled from where it lives. Anything else comes from the build-only
|
|
96
94
|
// intermediate the generation pass writes.
|
|
97
95
|
const prebuiltDirs = new Map(
|
|
98
|
-
config.packs
|
|
99
|
-
.filter((pack) => pack.prebuilt)
|
|
100
|
-
.map((pack) => [pack.name, pack.prebuilt]),
|
|
96
|
+
config.packs.filter((pack) => pack.prebuilt).map((pack) => [pack.name, pack.prebuilt]),
|
|
101
97
|
);
|
|
102
98
|
const needsGeneration = packNames.some((name) => !prebuiltDirs.has(name));
|
|
103
99
|
|
|
@@ -185,8 +181,7 @@ function cleanPackEntry(
|
|
|
185
181
|
}
|
|
186
182
|
delete data.flags?.importSource;
|
|
187
183
|
delete data.flags?.exportSource;
|
|
188
|
-
if (data._stats?.lastModifiedBy)
|
|
189
|
-
data._stats.lastModifiedBy = lastModifiedBy;
|
|
184
|
+
if (data._stats?.lastModifiedBy) data._stats.lastModifiedBy = lastModifiedBy;
|
|
190
185
|
|
|
191
186
|
// Remove empty entries in flags
|
|
192
187
|
if (!data.flags) data.flags = {};
|
|
@@ -194,18 +189,11 @@ function cleanPackEntry(
|
|
|
194
189
|
if (Object.keys(contents).length === 0) delete data.flags[key];
|
|
195
190
|
});
|
|
196
191
|
|
|
197
|
-
if (data.effects)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (data.
|
|
202
|
-
data.items.forEach((i) => cleanPackEntry(i, { clearSourceId: false }));
|
|
203
|
-
if (data.pages)
|
|
204
|
-
data.pages.forEach((i) => cleanPackEntry(i, { ownership: -1 }));
|
|
205
|
-
if (data.system?.description)
|
|
206
|
-
data.system.description = cleanString(data.system.description);
|
|
207
|
-
if (data.system?.biography)
|
|
208
|
-
data.system.biography = cleanString(data.system.biography);
|
|
192
|
+
if (data.effects) data.effects.forEach((i) => cleanPackEntry(i, { clearSourceId: false }));
|
|
193
|
+
if (data.items) data.items.forEach((i) => cleanPackEntry(i, { clearSourceId: false }));
|
|
194
|
+
if (data.pages) data.pages.forEach((i) => cleanPackEntry(i, { ownership: -1 }));
|
|
195
|
+
if (data.system?.description) data.system.description = cleanString(data.system.description);
|
|
196
|
+
if (data.system?.biography) data.system.biography = cleanString(data.system.biography);
|
|
209
197
|
if (data.system?.textReference)
|
|
210
198
|
data.system.textReference = cleanString(data.system.textReference);
|
|
211
199
|
if (data.system?.notes) data.system.notes = cleanString(data.system.notes);
|
|
@@ -249,10 +237,7 @@ export async function cleanPacks({
|
|
|
249
237
|
|
|
250
238
|
const folders = fs
|
|
251
239
|
.readdirSync(packDest, { withFileTypes: true })
|
|
252
|
-
.filter(
|
|
253
|
-
(file) =>
|
|
254
|
-
file.isDirectory() && (!packName || packName === file.name),
|
|
255
|
-
);
|
|
240
|
+
.filter((file) => file.isDirectory() && (!packName || packName === file.name));
|
|
256
241
|
|
|
257
242
|
/**
|
|
258
243
|
* Walk through directories to find JSON files.
|
|
@@ -273,9 +258,7 @@ export async function cleanPacks({
|
|
|
273
258
|
const json = JSON.parse(await readFile(src, { encoding: "utf8" }));
|
|
274
259
|
if (entryName && entryName !== json.name.toLowerCase()) continue;
|
|
275
260
|
if (!json._id || !json._key) {
|
|
276
|
-
log.info(
|
|
277
|
-
`Failed to clean \x1b[31m${src}\x1b[0m, must have _id and _key.`,
|
|
278
|
-
);
|
|
261
|
+
log.info(`Failed to clean \x1b[31m${src}\x1b[0m, must have _id and _key.`);
|
|
279
262
|
continue;
|
|
280
263
|
}
|
|
281
264
|
cleanPackEntry(json);
|
|
@@ -351,21 +334,14 @@ export async function unpackPacks({
|
|
|
351
334
|
await extractPack(src, dest, {
|
|
352
335
|
log: true,
|
|
353
336
|
transformEntry: (entry) => {
|
|
354
|
-
if (entryName && entryName !== entry.name.toLowerCase())
|
|
355
|
-
return false;
|
|
337
|
+
if (entryName && entryName !== entry.name.toLowerCase()) return false;
|
|
356
338
|
cleanPackEntry(entry);
|
|
357
339
|
},
|
|
358
340
|
transformName: (entry) => {
|
|
359
341
|
if (entry._id in folders)
|
|
360
|
-
return path.join(
|
|
361
|
-
"folder_",
|
|
362
|
-
folders[entry._id].path,
|
|
363
|
-
".json",
|
|
364
|
-
);
|
|
342
|
+
return path.join("folder_", folders[entry._id].path, ".json");
|
|
365
343
|
const outputName = slugify(entry.name);
|
|
366
|
-
const parent =
|
|
367
|
-
containers[entry.system?.container] ??
|
|
368
|
-
folders[entry.folder];
|
|
344
|
+
const parent = containers[entry.system?.container] ?? folders[entry.folder];
|
|
369
345
|
return path.join(parent?.path ?? "", `${outputName}.json`);
|
|
370
346
|
},
|
|
371
347
|
});
|
|
@@ -38,14 +38,14 @@ export const KB_PREFIX = "kb/";
|
|
|
38
38
|
* The URL section a note routes to.
|
|
39
39
|
*
|
|
40
40
|
* A `doc` is narrative content whose only identity is its subtype label, so it
|
|
41
|
-
* routes by `
|
|
41
|
+
* routes by `subType`; every other type names its own section.
|
|
42
42
|
*
|
|
43
43
|
* @param {object} fm - Parsed frontmatter.
|
|
44
44
|
* @returns {string|undefined} The section, or `undefined` when the note has
|
|
45
|
-
* none — a `doc` with no
|
|
45
|
+
* none — a `doc` with no subtype has no address and is not published.
|
|
46
46
|
*/
|
|
47
47
|
export function sectionOf(fm) {
|
|
48
|
-
return fm.type === "doc" ? fm.
|
|
48
|
+
return fm.type === "doc" ? fm.subType : fm.type;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -80,14 +80,12 @@ export function contentAddress(fm, name, isReadme) {
|
|
|
80
80
|
*/
|
|
81
81
|
function landingOf(fm, isReadme, landing) {
|
|
82
82
|
if (landing === "readme") {
|
|
83
|
-
return isReadme ?
|
|
84
|
-
{ landing: true, segment: sectionOf(fm) }
|
|
85
|
-
: { landing: false };
|
|
83
|
+
return isReadme ? { landing: true, segment: sectionOf(fm) } : { landing: false };
|
|
86
84
|
}
|
|
87
85
|
// `collection`. The section is authored rather than derived: it is the
|
|
88
86
|
// identity of the section being introduced, and the note's own title
|
|
89
87
|
// ("Creatures") is presentation, which would slug to something else.
|
|
90
|
-
if (fm.type === "doc" && fm.
|
|
88
|
+
if (fm.type === "doc" && fm.subType === "collection") {
|
|
91
89
|
return { landing: true, segment: fm.section || fm.slug };
|
|
92
90
|
}
|
|
93
91
|
return { landing: false };
|
|
@@ -127,9 +125,7 @@ export function packageAddress(fm, name, { isReadme = false, scheme } = {}) {
|
|
|
127
125
|
const land = landingOf(fm, isReadme, landing);
|
|
128
126
|
if (land.landing) {
|
|
129
127
|
if (typeof land.segment !== "string" || !land.segment) {
|
|
130
|
-
throw new Error(
|
|
131
|
-
`landing note declares no section, so it lands nowhere`,
|
|
132
|
-
);
|
|
128
|
+
throw new Error(`landing note declares no section, so it lands nowhere`);
|
|
133
129
|
}
|
|
134
130
|
return `${prefix}${land.segment}/`;
|
|
135
131
|
}
|