@heroiclands/package-build 17.2.0 → 18.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 +1711 -0
- package/CONTENT.md +288 -72
- package/README.md +1 -1
- package/bin/content-build.mjs +218 -137
- package/bin/package-build.mjs +43 -0
- package/content-config.mjs +62 -67
- package/docs/content-format.md +768 -58
- package/engine/actor-compiler.mjs +586 -0
- package/engine/address-charset.mjs +11 -8
- package/engine/address-diff.mjs +266 -22
- package/engine/anchored-sections.mjs +83 -0
- package/engine/anchors.mjs +83 -0
- package/engine/base-compiler.mjs +148 -18
- package/engine/bundle-notes.mjs +276 -0
- package/engine/bundles.mjs +307 -0
- package/engine/code-fences.mjs +103 -0
- package/engine/compile-corpus.mjs +89 -0
- package/engine/content-address.mjs +267 -5
- package/engine/content-format-check.mjs +54 -11
- package/engine/content-format.mjs +37 -5
- package/engine/content-index.mjs +225 -127
- package/engine/content-links.mjs +185 -77
- package/engine/content-lint.mjs +232 -14
- package/engine/content-package.mjs +2 -1
- package/engine/content-tables.mjs +99 -3
- package/engine/document-subtypes.mjs +49 -6
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +56 -6
- package/engine/folder-notes.mjs +470 -0
- package/engine/foreign-catalog.mjs +195 -4
- package/engine/{manifest-emit.mjs → foundry-entries.mjs} +55 -83
- package/engine/frontmatter-lint.mjs +245 -19
- package/engine/frontmatter.mjs +88 -8
- package/engine/generate.mjs +304 -13
- package/engine/helpers.mjs +293 -64
- package/engine/ids.mjs +137 -2
- package/engine/index-records.mjs +126 -0
- package/engine/index.mjs +23 -5
- package/engine/item-compiler.mjs +349 -0
- package/engine/item-docs.mjs +7 -1
- package/engine/item-registry.mjs +6 -0
- package/engine/journals.mjs +93 -27
- package/engine/macros.mjs +4 -2
- package/engine/metadata-index.mjs +495 -0
- package/engine/note-claims.mjs +146 -25
- package/engine/note-ids.mjs +112 -0
- package/engine/note-renames.mjs +134 -0
- package/engine/note-schemas.mjs +25 -0
- package/engine/note-vocabulary.mjs +110 -27
- package/engine/pack-config.mjs +4 -0
- package/engine/pack-router.mjs +23 -0
- package/engine/retired-fields.mjs +137 -3
- package/engine/scenes.mjs +66 -34
- package/engine/schema-check.mjs +58 -17
- package/engine/site-build.mjs +71 -12
- package/engine/site-index.mjs +20 -7
- package/engine/sql-tables.mjs +485 -0
- package/engine/subtype-registry.mjs +102 -0
- package/engine/system-block.mjs +96 -12
- package/engine/systems.mjs +201 -0
- package/engine/web-wikilinks.mjs +50 -20
- package/engine/wikilink-syntax.mjs +17 -8
- package/engine/wikilinks.mjs +124 -51
- package/engine/yaml-lint.mjs +282 -0
- package/hm3/actors.mjs +352 -0
- package/hm3/default-item-art.mjs +75 -0
- package/hm3/document-subtypes.mjs +134 -0
- package/hm3/index.mjs +56 -0
- package/hm3/item-builders.mjs +84 -0
- package/hm3/item-fields.mjs +180 -0
- package/hm3/items.mjs +96 -0
- package/hm3/template-priority.mjs +85 -0
- package/manifest.mjs +43 -2
- package/package.json +18 -4
- package/release.mjs +62 -7
- package/sohl/actors.mjs +33 -487
- package/sohl/being-info.mjs +16 -7
- package/sohl/default-item-art.mjs +14 -3
- package/sohl/document-subtypes.mjs +16 -10
- package/sohl/item-builders.mjs +14 -5
- package/sohl/item-fields.mjs +68 -7
- package/sohl/items.mjs +44 -258
- package/sohl/note-schemas.mjs +2 -2
- package/types/content-config.d.mts +21 -42
- package/types/engine/actor-compiler.d.mts +204 -0
- package/types/engine/address-charset.d.mts +11 -8
- package/types/engine/address-diff.d.mts +53 -5
- package/types/engine/anchored-sections.d.mts +21 -0
- package/types/engine/anchors.d.mts +20 -0
- package/types/engine/base-compiler.d.mts +17 -17
- package/types/engine/bundle-notes.d.mts +173 -0
- package/types/engine/bundles.d.mts +60 -0
- package/types/engine/code-fences.d.mts +43 -0
- package/types/engine/compile-corpus.d.mts +32 -0
- package/types/engine/content-address.d.mts +205 -5
- package/types/engine/content-format-check.d.mts +6 -2
- package/types/engine/content-format.d.mts +57 -1
- package/types/engine/content-index.d.mts +54 -49
- package/types/engine/content-links.d.mts +52 -7
- package/types/engine/content-lint.d.mts +10 -1
- package/types/engine/content-package.d.mts +2 -1
- package/types/engine/content-tables.d.mts +21 -39
- package/types/engine/document-subtypes.d.mts +37 -3
- package/types/engine/field-spec.d.mts +76 -5
- package/types/engine/folder-notes.d.mts +159 -0
- package/types/engine/foreign-catalog.d.mts +53 -0
- package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +2 -43
- package/types/engine/frontmatter-lint.d.mts +10 -2
- package/types/engine/frontmatter.d.mts +64 -0
- package/types/engine/generate.d.mts +38 -0
- package/types/engine/helpers.d.mts +94 -30
- package/types/engine/ids.d.mts +96 -0
- package/types/engine/index-records.d.mts +68 -0
- package/types/engine/index.d.mts +9 -3
- package/types/engine/item-compiler.d.mts +131 -0
- package/types/engine/journals.d.mts +47 -9
- package/types/engine/metadata-index.d.mts +226 -0
- package/types/engine/note-claims.d.mts +57 -11
- package/types/engine/note-ids.d.mts +38 -0
- package/types/engine/note-renames.d.mts +102 -0
- package/types/engine/note-vocabulary.d.mts +44 -8
- package/types/engine/retired-fields.d.mts +75 -0
- package/types/engine/scenes.d.mts +3 -2
- package/types/engine/schema-check.d.mts +25 -4
- package/types/engine/site-build.d.mts +4 -4
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +185 -0
- package/types/engine/subtype-registry.d.mts +49 -0
- package/types/engine/system-block.d.mts +40 -1
- package/types/engine/systems.d.mts +106 -0
- package/types/engine/web-wikilinks.d.mts +4 -2
- package/types/engine/wikilink-syntax.d.mts +10 -3
- package/types/engine/wikilinks.d.mts +41 -13
- package/types/engine/yaml-lint.d.mts +107 -0
- package/types/hm3/actors.d.mts +48 -0
- package/types/hm3/default-item-art.d.mts +42 -0
- package/types/hm3/document-subtypes.d.mts +24 -0
- package/types/hm3/index.d.mts +7 -0
- package/types/hm3/item-builders.d.mts +11 -0
- package/types/hm3/item-fields.d.mts +12 -0
- package/types/hm3/items.d.mts +23 -0
- package/types/hm3/template-priority.d.mts +21 -0
- package/types/manifest.d.mts +22 -1
- package/types/release.d.mts +9 -4
- package/types/sohl/actors.d.mts +7 -74
- package/types/sohl/being-info.d.mts +8 -27
- package/types/sohl/default-item-art.d.mts +5 -3
- package/types/sohl/items.d.mts +17 -32
- package/engine/foreign-manifests.mjs +0 -126
- package/engine/kb-manifest.mjs +0 -490
- package/types/engine/foreign-manifests.d.mts +0 -43
- package/types/engine/kb-manifest.d.mts +0 -241
package/bin/content-build.mjs
CHANGED
|
@@ -67,8 +67,16 @@ import { loadPackConfig } from "../engine/pack-config.mjs";
|
|
|
67
67
|
import {
|
|
68
68
|
fetchAllCatalogs,
|
|
69
69
|
fetchCatalogFromPath,
|
|
70
|
+
fetchAllMetadata,
|
|
71
|
+
fetchMetadataFromPath,
|
|
70
72
|
itemCatalogRelationships,
|
|
71
73
|
} from "../engine/foreign-catalog.mjs";
|
|
74
|
+
import {
|
|
75
|
+
metadataRelationships,
|
|
76
|
+
cachedIndexPath,
|
|
77
|
+
unaddressableForeignPackages,
|
|
78
|
+
formatUnaddressableFinding,
|
|
79
|
+
} from "../engine/metadata-index.mjs";
|
|
72
80
|
import { renderItemFieldReference } from "../engine/field-reference.mjs";
|
|
73
81
|
import { lintContentTree } from "../engine/content-lint.mjs";
|
|
74
82
|
import { lintFrontmatter } from "../engine/frontmatter-lint.mjs";
|
|
@@ -91,19 +99,27 @@ import { NOTE_SCHEMAS } from "../sohl/note-schemas.mjs";
|
|
|
91
99
|
// The shipped declarations, so this repository can check its own specification
|
|
92
100
|
// against them without standing up a consumer's configuration (#136).
|
|
93
101
|
import { ITEM_FIELDS } from "../sohl/item-fields.mjs";
|
|
102
|
+
import { HM3_ITEM_FIELDS } from "../hm3/item-fields.mjs";
|
|
94
103
|
// The engine's own types, merged under the registry's so the vocabulary stands
|
|
95
104
|
// in a package that configures no `itemBuilders` at all (#51).
|
|
96
105
|
import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
|
|
106
|
+
import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
|
|
97
107
|
import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
|
|
98
108
|
import { checkFormatting, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
99
|
-
import {
|
|
100
|
-
|
|
109
|
+
import {
|
|
110
|
+
authoredFrontmatter,
|
|
111
|
+
emitContentIndex,
|
|
112
|
+
indexRecordsFor,
|
|
113
|
+
isNoteRecord,
|
|
114
|
+
noteFile,
|
|
115
|
+
} from "../engine/content-index.mjs";
|
|
101
116
|
import {
|
|
102
117
|
buildSite,
|
|
103
118
|
gatesFailed,
|
|
104
119
|
formatUnaddressableFinding as formatUnaddressable,
|
|
105
120
|
} from "../engine/site-build.mjs";
|
|
106
121
|
import { auditLinks, buildLinkIndex, walkReachability } from "../engine/content-links.mjs";
|
|
122
|
+
import { prepareTreeSqlTables } from "../engine/sql-tables.mjs";
|
|
107
123
|
// The one place a link finding is worded, shared with both builds (#184).
|
|
108
124
|
import { linkFindingMessage } from "../engine/wikilink-syntax.mjs";
|
|
109
125
|
import {
|
|
@@ -115,16 +131,12 @@ import { reportFindings } from "./report.mjs";
|
|
|
115
131
|
import {
|
|
116
132
|
readItemAddresses,
|
|
117
133
|
diffItemAddresses,
|
|
134
|
+
declaredPredecessors,
|
|
118
135
|
noteFilesById,
|
|
119
136
|
locateAddressFinding,
|
|
120
137
|
addressFindingMessage,
|
|
121
138
|
} from "../engine/address-diff.mjs";
|
|
122
139
|
import { itemPackJsonDirs } from "../engine/generate.mjs";
|
|
123
|
-
import { walkMarkdownTree } from "../engine/helpers.mjs";
|
|
124
|
-
import {
|
|
125
|
-
formatUnaddressableFinding,
|
|
126
|
-
unaddressableForeignPackages,
|
|
127
|
-
} from "../engine/foreign-manifests.mjs";
|
|
128
140
|
|
|
129
141
|
/**
|
|
130
142
|
* The packs `unpack` extracts.
|
|
@@ -217,7 +229,7 @@ function readRawNote(file) {
|
|
|
217
229
|
*
|
|
218
230
|
* @type {Record<string, Record<string, readonly object[]>>}
|
|
219
231
|
*/
|
|
220
|
-
const SHIPPED_ITEM_FIELDS = { sohl: ITEM_FIELDS };
|
|
232
|
+
const SHIPPED_ITEM_FIELDS = { sohl: ITEM_FIELDS, hm3: HM3_ITEM_FIELDS };
|
|
221
233
|
|
|
222
234
|
const argv = yargs(hideBin(process.argv))
|
|
223
235
|
.command(packageCommand())
|
|
@@ -228,7 +240,6 @@ const argv = yargs(hideBin(process.argv))
|
|
|
228
240
|
.command(linksCommand())
|
|
229
241
|
.command(formatCommand())
|
|
230
242
|
.command(markdownCommand())
|
|
231
|
-
.command(manifestCommand())
|
|
232
243
|
.command(contentIndexCommand())
|
|
233
244
|
.command(siteCommand())
|
|
234
245
|
.command(reachabilityCommand())
|
|
@@ -647,14 +658,33 @@ function contentFormatNotesCommand() {
|
|
|
647
658
|
const root = argv.root ?? config.paths.content;
|
|
648
659
|
const format = specFrom(argv);
|
|
649
660
|
|
|
650
|
-
|
|
651
|
-
|
|
661
|
+
// The corpus from the index, like every other check (#243).
|
|
662
|
+
// A report measuring the tree against the declared vocabulary
|
|
663
|
+
// has to be looking at the same tree the compile will, or its
|
|
664
|
+
// counts describe a corpus nobody builds.
|
|
665
|
+
const corpusProblems = [];
|
|
666
|
+
const records = indexRecordsFor({
|
|
667
|
+
contentBase: root,
|
|
668
|
+
config,
|
|
652
669
|
skipDirectories: config.skipDirectories,
|
|
653
|
-
|
|
654
|
-
|
|
670
|
+
problems: corpusProblems,
|
|
671
|
+
});
|
|
672
|
+
for (const problem of corpusProblems) emitDiagnostic(problem);
|
|
673
|
+
if (corpusProblems.length) process.exitCode = 1;
|
|
674
|
+
|
|
675
|
+
const notes = [];
|
|
676
|
+
for (const record of records) {
|
|
677
|
+
// A documentation journal is a document this tree emits,
|
|
678
|
+
// not a note in it, and has no authored frontmatter to
|
|
679
|
+
// measure.
|
|
680
|
+
if (!isNoteRecord(record) || typeof record.type !== "string") continue;
|
|
681
|
+
const absPath = noteFile(root, record);
|
|
655
682
|
notes.push({
|
|
656
683
|
file: absPath,
|
|
657
|
-
|
|
684
|
+
// What the author wrote, never the keys the index
|
|
685
|
+
// derived — this measures a note against a vocabulary,
|
|
686
|
+
// and `address:` is in no vocabulary.
|
|
687
|
+
fm: authoredFrontmatter(record),
|
|
658
688
|
raw: fs.readFileSync(absPath, "utf8"),
|
|
659
689
|
});
|
|
660
690
|
}
|
|
@@ -706,29 +736,56 @@ function lintCommand() {
|
|
|
706
736
|
type: "boolean",
|
|
707
737
|
default: true,
|
|
708
738
|
});
|
|
709
|
-
yargs.option("manifests", {
|
|
710
|
-
describe:
|
|
711
|
-
"Directory of vendored foreign link manifests, for the reference check. Defaults to the configured `paths.manifests`.",
|
|
712
|
-
type: "string",
|
|
713
|
-
});
|
|
714
739
|
},
|
|
715
|
-
handler: (argv) => {
|
|
740
|
+
handler: async (argv) => {
|
|
716
741
|
try {
|
|
717
742
|
const config = loadPackConfig();
|
|
718
743
|
const root = argv.root ?? config.paths.content;
|
|
719
|
-
|
|
744
|
+
|
|
745
|
+
// The corpus, enumerated once for this command and handed to
|
|
746
|
+
// every pass below, rather than derived again by each (#243).
|
|
747
|
+
// The address lint reads it, the `sql` tables select over it
|
|
748
|
+
// and the link index is built from it, so no two findings this
|
|
749
|
+
// command reports can be drawn from different ideas of which
|
|
750
|
+
// files the content is.
|
|
751
|
+
//
|
|
752
|
+
// A note the index cannot record is reported like any other
|
|
753
|
+
// finding rather than thrown (#243): one malformed note must
|
|
754
|
+
// not take every other finding in the tree with it, and the
|
|
755
|
+
// reader needs a line to open, not a stack.
|
|
756
|
+
const corpusProblems = [];
|
|
757
|
+
const records = indexRecordsFor({
|
|
758
|
+
contentBase: root,
|
|
759
|
+
config,
|
|
760
|
+
skipDirectories: config.skipDirectories,
|
|
761
|
+
problems: corpusProblems,
|
|
762
|
+
});
|
|
763
|
+
for (const problem of corpusProblems) emitDiagnostic(problem);
|
|
764
|
+
// An error whatever the command's own strictness: the note is
|
|
765
|
+
// absent from every answer below, so reporting it and exiting 0
|
|
766
|
+
// would call the tree clean while silently omitting a note.
|
|
767
|
+
if (corpusProblems.length) process.exitCode = 1;
|
|
720
768
|
|
|
721
769
|
// The package is passed for the homepage rule (#52), which
|
|
722
770
|
// names the address a tree with no front page fails to serve.
|
|
723
771
|
const addresses = lintContentTree(root, {
|
|
724
772
|
contentPackage: config.contentPackage,
|
|
773
|
+
skipDirectories: config.skipDirectories,
|
|
774
|
+
config,
|
|
775
|
+
records,
|
|
725
776
|
});
|
|
726
777
|
// One index, built once, for the reference check. It is the
|
|
727
778
|
// same resolver the wikilink audit uses, so a frontmatter
|
|
728
779
|
// reference and a body link answer the same way.
|
|
729
780
|
const index = buildLinkIndex(root, {
|
|
730
|
-
|
|
781
|
+
config,
|
|
782
|
+
records,
|
|
731
783
|
skipDirectories: config.skipDirectories,
|
|
784
|
+
sqlTables: await prepareTreeSqlTables(root, {
|
|
785
|
+
config,
|
|
786
|
+
records,
|
|
787
|
+
skipDirectories: config.skipDirectories,
|
|
788
|
+
}),
|
|
732
789
|
});
|
|
733
790
|
const frontmatter = lintFrontmatter(index, {
|
|
734
791
|
schemas: { ...ENGINE_NOTE_SCHEMAS, ...NOTE_SCHEMAS },
|
|
@@ -737,6 +794,11 @@ function lintCommand() {
|
|
|
737
794
|
// whatever it is handed and this stays the one place that
|
|
738
795
|
// decides which vocabulary a tree is held to.
|
|
739
796
|
vocabulary: NOTE_VOCABULARY,
|
|
797
|
+
// The pack names, for a `data:` field keyed by pack — a
|
|
798
|
+
// folder's `parent` is one (#288). Companions included:
|
|
799
|
+
// the compile asks the map for whichever pack it is
|
|
800
|
+
// writing, and a companion is a pack it writes.
|
|
801
|
+
packs: config.packDirectories,
|
|
740
802
|
references: argv.references,
|
|
741
803
|
});
|
|
742
804
|
|
|
@@ -789,6 +851,12 @@ function lintCommand() {
|
|
|
789
851
|
const { undeclared, unemitted } = compareFields({
|
|
790
852
|
builders: fieldSpecs,
|
|
791
853
|
artifact: schema.artifact,
|
|
854
|
+
// A schema is keyed by document subtype and a field
|
|
855
|
+
// declaration by note type. Those were one string until
|
|
856
|
+
// #78 renamed three of them, and joining them by name
|
|
857
|
+
// after that would drop `armorgear`'s findings without
|
|
858
|
+
// saying so — the seam exists for exactly this.
|
|
859
|
+
subtypeOf: (type) => schemaSubtypeOf(config.stats?.systemId, type),
|
|
792
860
|
});
|
|
793
861
|
for (const f of undeclared) {
|
|
794
862
|
schemaFindings.push({
|
|
@@ -1000,20 +1068,37 @@ function linksCommand() {
|
|
|
1000
1068
|
describe: "Content tree to check. Defaults to the configured contentBase.",
|
|
1001
1069
|
type: "string",
|
|
1002
1070
|
});
|
|
1003
|
-
yargs.option("manifests", {
|
|
1004
|
-
describe:
|
|
1005
|
-
"Directory of vendored foreign link manifests. Defaults " +
|
|
1006
|
-
"to the configured `paths.manifests`.",
|
|
1007
|
-
type: "string",
|
|
1008
|
-
});
|
|
1009
1071
|
},
|
|
1010
|
-
handler: (argv) => {
|
|
1072
|
+
handler: async (argv) => {
|
|
1011
1073
|
try {
|
|
1012
1074
|
const config = loadPackConfig();
|
|
1013
1075
|
const contentBase = argv.root ?? config.paths.content;
|
|
1014
|
-
const manifestDir = argv.manifests ?? config.paths.manifests;
|
|
1015
1076
|
|
|
1016
|
-
const
|
|
1077
|
+
const scope = { skipDirectories: config.skipDirectories };
|
|
1078
|
+
// Enumerated once and shared, as in `lint` (#243), and a note
|
|
1079
|
+
// it cannot record is reported rather than thrown.
|
|
1080
|
+
const corpusProblems = [];
|
|
1081
|
+
const records = indexRecordsFor({
|
|
1082
|
+
contentBase,
|
|
1083
|
+
config,
|
|
1084
|
+
...scope,
|
|
1085
|
+
problems: corpusProblems,
|
|
1086
|
+
});
|
|
1087
|
+
for (const problem of corpusProblems) emitDiagnostic(problem);
|
|
1088
|
+
// An error whatever the command's own strictness: the note is
|
|
1089
|
+
// absent from every answer below, so reporting it and exiting 0
|
|
1090
|
+
// would call the tree clean while silently omitting a note.
|
|
1091
|
+
if (corpusProblems.length) process.exitCode = 1;
|
|
1092
|
+
const index = buildLinkIndex(contentBase, {
|
|
1093
|
+
config,
|
|
1094
|
+
records,
|
|
1095
|
+
...scope,
|
|
1096
|
+
sqlTables: await prepareTreeSqlTables(contentBase, {
|
|
1097
|
+
config,
|
|
1098
|
+
records,
|
|
1099
|
+
...scope,
|
|
1100
|
+
}),
|
|
1101
|
+
});
|
|
1017
1102
|
|
|
1018
1103
|
// An unusable manifest would otherwise surface as a pile of
|
|
1019
1104
|
// dead addresses pointing at the notes that cite it, rather
|
|
@@ -1021,12 +1106,12 @@ function linksCommand() {
|
|
|
1021
1106
|
if (index.foreign.stale.length) {
|
|
1022
1107
|
for (const s of index.foreign.stale) {
|
|
1023
1108
|
emitDiagnostic({
|
|
1024
|
-
file:
|
|
1109
|
+
file: cachedIndexPath(config, s.package),
|
|
1025
1110
|
severity: "error",
|
|
1026
|
-
message: `unusable
|
|
1111
|
+
message: `unusable content index: ${s.reason}`,
|
|
1027
1112
|
});
|
|
1028
1113
|
}
|
|
1029
|
-
log.error("
|
|
1114
|
+
log.error("Re-run `content-build deps fetch`.");
|
|
1030
1115
|
process.exitCode = 1;
|
|
1031
1116
|
return;
|
|
1032
1117
|
}
|
|
@@ -1037,7 +1122,7 @@ function linksCommand() {
|
|
|
1037
1122
|
const drifted = unaddressableForeignPackages(index.foreign.index);
|
|
1038
1123
|
if (drifted.length) {
|
|
1039
1124
|
for (const f of drifted) {
|
|
1040
|
-
console.error(formatUnaddressableFinding(f,
|
|
1125
|
+
console.error(formatUnaddressableFinding(f, config));
|
|
1041
1126
|
}
|
|
1042
1127
|
process.exitCode = 1;
|
|
1043
1128
|
return;
|
|
@@ -1126,77 +1211,6 @@ function linksCommand() {
|
|
|
1126
1211
|
};
|
|
1127
1212
|
}
|
|
1128
1213
|
|
|
1129
|
-
/**
|
|
1130
|
-
* `content-build manifest` — emit this package's cross-package link manifest.
|
|
1131
|
-
*
|
|
1132
|
-
* The last capability the library exposed without a command (#58). Every
|
|
1133
|
-
* consumer that publishes a manifest had to write the walk, the address
|
|
1134
|
-
* derivation, the anchor pass and the entry assembly for itself, and the two
|
|
1135
|
-
* that did drifted apart: one routed its UUIDs through the pack router and one
|
|
1136
|
-
* did not, so a repository shipping several packs of a type published UUIDs
|
|
1137
|
-
* naming the wrong one.
|
|
1138
|
-
*
|
|
1139
|
-
* Takes no paths. The content tree, the output directory, the content and
|
|
1140
|
-
* Foundry package identities and the address scheme all come from
|
|
1141
|
-
* `package-build.config.yaml`; `[root]` and `--out` exist to point the same
|
|
1142
|
-
* derivation at a scratch tree, not because a build needs to name them.
|
|
1143
|
-
*
|
|
1144
|
-
* @returns {object} The yargs command module.
|
|
1145
|
-
*/
|
|
1146
|
-
// eslint-disable-next-line
|
|
1147
|
-
function manifestCommand() {
|
|
1148
|
-
return {
|
|
1149
|
-
command: "manifest [root]",
|
|
1150
|
-
describe: "Emit this package's cross-package link manifest",
|
|
1151
|
-
builder: (yargs) => {
|
|
1152
|
-
yargs.positional("root", {
|
|
1153
|
-
describe: "Content tree to read. Defaults to the configured contentBase.",
|
|
1154
|
-
type: "string",
|
|
1155
|
-
});
|
|
1156
|
-
yargs.option("out", {
|
|
1157
|
-
describe:
|
|
1158
|
-
"Directory to write into. Defaults to the configured " + "`paths.manifestOut`.",
|
|
1159
|
-
type: "string",
|
|
1160
|
-
});
|
|
1161
|
-
},
|
|
1162
|
-
handler: (argv) => {
|
|
1163
|
-
try {
|
|
1164
|
-
const config = loadPackConfig();
|
|
1165
|
-
const { written, notes, skipped } = emitLinkManifest({
|
|
1166
|
-
config,
|
|
1167
|
-
...(argv.root ? { contentBase: argv.root } : {}),
|
|
1168
|
-
...(argv.out ? { outDir: argv.out } : {}),
|
|
1169
|
-
});
|
|
1170
|
-
|
|
1171
|
-
for (const { package: pkg, file, count } of written) {
|
|
1172
|
-
log.info(
|
|
1173
|
-
`${pkg} → ${path.relative(process.cwd(), file)} ` +
|
|
1174
|
-
`(${count} entries, from ${notes} addressable ` +
|
|
1175
|
-
`note(s))`,
|
|
1176
|
-
);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// Reported rather than fatal: a note with no address is
|
|
1180
|
-
// ordinary — a template, a stub, a `doc` with no category —
|
|
1181
|
-
// and failing the build on one would make the manifest
|
|
1182
|
-
// unemittable for a reason that is not about the manifest.
|
|
1183
|
-
// Silence is the thing to avoid, since a note that quietly
|
|
1184
|
-
// lost its address becomes a dead link in every consumer.
|
|
1185
|
-
for (const s of skipped) {
|
|
1186
|
-
emitDiagnostic({
|
|
1187
|
-
file: path.join(argv.root ?? config.paths.content, s.file),
|
|
1188
|
-
severity: "warning",
|
|
1189
|
-
message: `no address, so it is absent from the manifest: ${s.reason}`,
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
|
-
} catch (err) {
|
|
1193
|
-
reportFailure(err);
|
|
1194
|
-
process.exitCode = 1;
|
|
1195
|
-
}
|
|
1196
|
-
},
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
1214
|
/**
|
|
1201
1215
|
* `content-build content-index` — emit this package's note index.
|
|
1202
1216
|
*
|
|
@@ -1276,9 +1290,12 @@ function siteCommand() {
|
|
|
1276
1290
|
type: "string",
|
|
1277
1291
|
});
|
|
1278
1292
|
},
|
|
1279
|
-
handler: (argv) => {
|
|
1293
|
+
handler: async (argv) => {
|
|
1280
1294
|
try {
|
|
1281
1295
|
const result = buildSite({
|
|
1296
|
+
sqlTables: await prepareTreeSqlTables(loadPackConfig().paths.content, {
|
|
1297
|
+
skipDirectories: loadPackConfig().skipDirectories,
|
|
1298
|
+
}),
|
|
1282
1299
|
...(argv.out ? { outRoot: argv.out } : {}),
|
|
1283
1300
|
});
|
|
1284
1301
|
const { gates } = result;
|
|
@@ -1307,13 +1324,13 @@ function siteCommand() {
|
|
|
1307
1324
|
}
|
|
1308
1325
|
for (const s of gates.staleManifests) {
|
|
1309
1326
|
emitDiagnostic({
|
|
1310
|
-
file:
|
|
1327
|
+
file: cachedIndexPath(loadPackConfig(), s.package),
|
|
1311
1328
|
severity: "error",
|
|
1312
|
-
message: `unusable
|
|
1329
|
+
message: `unusable content index: ${s.reason}`,
|
|
1313
1330
|
});
|
|
1314
1331
|
}
|
|
1315
1332
|
for (const f of gates.unaddressable) {
|
|
1316
|
-
console.error(formatUnaddressable(f, loadPackConfig()
|
|
1333
|
+
console.error(formatUnaddressable(f, loadPackConfig()));
|
|
1317
1334
|
}
|
|
1318
1335
|
for (const c of gates.conflicts) {
|
|
1319
1336
|
log.error(`address ${c.key} is also published by ${c.package}`);
|
|
@@ -1327,8 +1344,20 @@ function siteCommand() {
|
|
|
1327
1344
|
// failures of individual notes, and stopping the whole build
|
|
1328
1345
|
// before anything is emitted would make a single bad table
|
|
1329
1346
|
// hide every other problem in the tree.
|
|
1347
|
+
// `file:line:column: error: message`, path first — the same
|
|
1348
|
+
// shape the pack build and the wikilink findings below use. It
|
|
1349
|
+
// was prose with a timestamp where a parser reads the path,
|
|
1350
|
+
// which made one authored table produce a machine-readable
|
|
1351
|
+
// diagnostic from one build and something ungreppable from the
|
|
1352
|
+
// other (#223).
|
|
1330
1353
|
for (const e of result.tableErrors) {
|
|
1331
|
-
|
|
1354
|
+
emitDiagnostic({
|
|
1355
|
+
file: e.source,
|
|
1356
|
+
line: typeof e.line === "number" ? e.line + 1 : undefined,
|
|
1357
|
+
column: e.column,
|
|
1358
|
+
severity: "error",
|
|
1359
|
+
message: e.reason,
|
|
1360
|
+
});
|
|
1332
1361
|
}
|
|
1333
1362
|
// Reported the way the pack build reports the very same
|
|
1334
1363
|
// finding: `file:line:column: error: message`, path first, and
|
|
@@ -1351,20 +1380,6 @@ function siteCommand() {
|
|
|
1351
1380
|
return;
|
|
1352
1381
|
}
|
|
1353
1382
|
|
|
1354
|
-
if (result.manifests && !result.manifests.complete) {
|
|
1355
|
-
// Not a softening any more (#184): an address into one of
|
|
1356
|
-
// these packages fails like any other that resolves
|
|
1357
|
-
// nowhere. The warning names them so an author meeting that
|
|
1358
|
-
// failure knows the fix may be to vendor a manifest rather
|
|
1359
|
-
// than to correct a shortcode.
|
|
1360
|
-
log.warn(
|
|
1361
|
-
`no link manifest vendored for ` +
|
|
1362
|
-
`${result.manifests.missing.join(", ")} — an ` +
|
|
1363
|
-
`address into one of those packages resolves ` +
|
|
1364
|
-
`nowhere and fails the build.`,
|
|
1365
|
-
);
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
1383
|
const s = result.stats;
|
|
1369
1384
|
log.info(
|
|
1370
1385
|
`wrote ${s.homepages ?? 0} homepage(s) + ` +
|
|
@@ -1421,11 +1436,37 @@ function reachabilityCommand() {
|
|
|
1421
1436
|
type: "string",
|
|
1422
1437
|
});
|
|
1423
1438
|
},
|
|
1424
|
-
handler: (argv) => {
|
|
1439
|
+
handler: async (argv) => {
|
|
1425
1440
|
try {
|
|
1426
|
-
|
|
1441
|
+
// Resolved once and passed on, so every pass below runs
|
|
1442
|
+
// against the configuration this command resolved rather than
|
|
1443
|
+
// whichever one the working directory answers with (#243).
|
|
1444
|
+
const config = loadPackConfig();
|
|
1445
|
+
const contentBase = argv.root ?? config.paths.content;
|
|
1427
1446
|
const dir = String(argv.dir).replace(/\/+$/, "");
|
|
1428
|
-
const
|
|
1447
|
+
const scope = { skipDirectories: config.skipDirectories };
|
|
1448
|
+
const corpusProblems = [];
|
|
1449
|
+
const records = indexRecordsFor({
|
|
1450
|
+
contentBase,
|
|
1451
|
+
config,
|
|
1452
|
+
...scope,
|
|
1453
|
+
problems: corpusProblems,
|
|
1454
|
+
});
|
|
1455
|
+
for (const problem of corpusProblems) emitDiagnostic(problem);
|
|
1456
|
+
// An error whatever the command's own strictness: the note is
|
|
1457
|
+
// absent from every answer below, so reporting it and exiting 0
|
|
1458
|
+
// would call the tree clean while silently omitting a note.
|
|
1459
|
+
if (corpusProblems.length) process.exitCode = 1;
|
|
1460
|
+
const index = buildLinkIndex(contentBase, {
|
|
1461
|
+
config,
|
|
1462
|
+
records,
|
|
1463
|
+
...scope,
|
|
1464
|
+
sqlTables: await prepareTreeSqlTables(contentBase, {
|
|
1465
|
+
config,
|
|
1466
|
+
records,
|
|
1467
|
+
...scope,
|
|
1468
|
+
}),
|
|
1469
|
+
});
|
|
1429
1470
|
const indexes = new Set(argv.index.map(String));
|
|
1430
1471
|
|
|
1431
1472
|
const { orphans } = walkReachability(index, {
|
|
@@ -1471,8 +1512,14 @@ function reachabilityCommand() {
|
|
|
1471
1512
|
|
|
1472
1513
|
// eslint-disable-next-line
|
|
1473
1514
|
/**
|
|
1474
|
-
* `deps fetch` — fill the
|
|
1475
|
-
*
|
|
1515
|
+
* `deps fetch` — fill the caches this build resolves other packages through:
|
|
1516
|
+
* the **content index** of every declared dependency (#239), and the **item
|
|
1517
|
+
* catalogue** of those additionally declaring `itemCatalog: true`.
|
|
1518
|
+
*
|
|
1519
|
+
* The two sets differ deliberately. Citing another package's *addresses* and
|
|
1520
|
+
* embedding its *items* are separate edges, and a package may have either
|
|
1521
|
+
* without the other — `harn-ensemble` cites no foreign address and embeds
|
|
1522
|
+
* 324,016 item references.
|
|
1476
1523
|
*
|
|
1477
1524
|
* Its own command rather than a step of `package compile`, so that a compile
|
|
1478
1525
|
* never reaches the network. A build that downloads silently is not
|
|
@@ -1496,7 +1543,11 @@ function reachabilityCommand() {
|
|
|
1496
1543
|
* @returns {Promise<void>}
|
|
1497
1544
|
*/
|
|
1498
1545
|
async function fetchFromLocalArtifact(config, argv) {
|
|
1499
|
-
|
|
1546
|
+
// Two caches, two dependency sets: an index is fetched for every declared
|
|
1547
|
+
// dependency, a catalogue only for those declaring `itemCatalog: true`
|
|
1548
|
+
// (#239). `--from` fills whichever of them this dependency belongs to, so
|
|
1549
|
+
// that testing against an unreleased build behaves like a release would.
|
|
1550
|
+
const rels = metadataRelationships(config);
|
|
1500
1551
|
const named = rels.map((r) => r.id).join(", ") || "none";
|
|
1501
1552
|
const rel =
|
|
1502
1553
|
argv.id ? rels.find((r) => r.id === argv.id)
|
|
@@ -1507,11 +1558,14 @@ async function fetchFromLocalArtifact(config, argv) {
|
|
|
1507
1558
|
// config is the only place that says which those are.
|
|
1508
1559
|
throw new Error(
|
|
1509
1560
|
argv.id ?
|
|
1510
|
-
`no dependency "${argv.id}"
|
|
1511
|
-
: `--from needs --id when
|
|
1561
|
+
`no declared dependency "${argv.id}" (declared: ${named})`
|
|
1562
|
+
: `--from needs --id when a package declares several dependencies (declared: ${named})`,
|
|
1512
1563
|
);
|
|
1513
1564
|
}
|
|
1514
|
-
await
|
|
1565
|
+
await fetchMetadataFromPath(config, rel, argv.from);
|
|
1566
|
+
if (itemCatalogRelationships(config).some((r) => r.id === rel.id)) {
|
|
1567
|
+
await fetchCatalogFromPath(config, rel, argv.from);
|
|
1568
|
+
}
|
|
1515
1569
|
}
|
|
1516
1570
|
|
|
1517
1571
|
function depsCommand() {
|
|
@@ -1537,7 +1591,7 @@ function depsCommand() {
|
|
|
1537
1591
|
yargs.option("id", {
|
|
1538
1592
|
describe:
|
|
1539
1593
|
"Which declared dependency `--from` supplies. Only needed " +
|
|
1540
|
-
"when more than one
|
|
1594
|
+
"when a package declares more than one.",
|
|
1541
1595
|
type: "string",
|
|
1542
1596
|
});
|
|
1543
1597
|
},
|
|
@@ -1548,8 +1602,11 @@ function depsCommand() {
|
|
|
1548
1602
|
await fetchFromLocalArtifact(config, argv);
|
|
1549
1603
|
return;
|
|
1550
1604
|
}
|
|
1605
|
+
const indexes = await fetchAllMetadata(config);
|
|
1606
|
+
if (indexes) log.info(`Fetched ${indexes} dependency content index(es).`);
|
|
1551
1607
|
const count = await fetchAllCatalogs(config);
|
|
1552
1608
|
if (count) log.info(`Fetched ${count} dependency catalogue(s).`);
|
|
1609
|
+
if (!indexes && !count) log.info("This package declares no dependencies.");
|
|
1553
1610
|
} catch (err) {
|
|
1554
1611
|
reportFailure(err);
|
|
1555
1612
|
process.exitCode = 1;
|
|
@@ -1613,10 +1670,34 @@ async function diffAddresses(config, argv) {
|
|
|
1613
1670
|
);
|
|
1614
1671
|
}
|
|
1615
1672
|
|
|
1673
|
+
// Stated by the caller, like every other corpus read in this file (#243):
|
|
1674
|
+
// the two tree reads below must agree with each other and with the compile
|
|
1675
|
+
// about which files are the corpus. They now do so by construction — the
|
|
1676
|
+
// corpus is derived once, here, and handed to both.
|
|
1677
|
+
const scope = { skipDirectories: config.skipDirectories };
|
|
1678
|
+
const corpusProblems = [];
|
|
1679
|
+
const records = indexRecordsFor({
|
|
1680
|
+
contentBase: config.paths.content,
|
|
1681
|
+
config,
|
|
1682
|
+
...scope,
|
|
1683
|
+
problems: corpusProblems,
|
|
1684
|
+
});
|
|
1685
|
+
for (const problem of corpusProblems) emitDiagnostic(problem);
|
|
1686
|
+
// An error whatever `--strict` says: the note is in none of the answers
|
|
1687
|
+
// below, so exiting 0 would call the tree clean while omitting a note.
|
|
1688
|
+
if (corpusProblems.length) process.exitCode = 1;
|
|
1689
|
+
const corpus = { config, records, ...scope };
|
|
1690
|
+
|
|
1616
1691
|
const findings = diffItemAddresses(
|
|
1617
1692
|
readItemAddresses(baselineDirs),
|
|
1618
1693
|
readItemAddresses(currentDirs),
|
|
1619
|
-
{
|
|
1694
|
+
{
|
|
1695
|
+
baseline: label,
|
|
1696
|
+
// Read whether or not anything departed: an id match needs no tree,
|
|
1697
|
+
// but the diff decides rename-versus-withdrawal as it walks the
|
|
1698
|
+
// baseline, so the declarations have to be in hand before it does.
|
|
1699
|
+
predecessors: declaredPredecessors(config.paths.content, corpus),
|
|
1700
|
+
},
|
|
1620
1701
|
);
|
|
1621
1702
|
if (!findings.length) {
|
|
1622
1703
|
log.info(`Every address ${label} published is still published.`);
|
|
@@ -1626,7 +1707,7 @@ async function diffAddresses(config, argv) {
|
|
|
1626
1707
|
// A rename is fixed in the note that made it, so findings are placed
|
|
1627
1708
|
// against the tree rather than against the compiled output they were read
|
|
1628
1709
|
// from.
|
|
1629
|
-
const noteFiles = noteFilesById(config.paths.content);
|
|
1710
|
+
const noteFiles = noteFilesById(config.paths.content, corpus);
|
|
1630
1711
|
const severity = argv.strict ? "error" : "warning";
|
|
1631
1712
|
for (const finding of findings) {
|
|
1632
1713
|
emitDiagnostic({
|
package/bin/package-build.mjs
CHANGED
|
@@ -84,6 +84,7 @@ import { buildSchemaArtifact } from "../engine/schema-extract.mjs";
|
|
|
84
84
|
import { SCHEMA_ARTIFACT_FILE } from "../engine/foreign-catalog.mjs";
|
|
85
85
|
import { validateLangSource } from "../lang.mjs";
|
|
86
86
|
import { checkLabelRegistry } from "../labels.mjs";
|
|
87
|
+
import { lintYaml } from "../engine/yaml-lint.mjs";
|
|
87
88
|
import {
|
|
88
89
|
analyzeCoverage,
|
|
89
90
|
collectScriptReferences,
|
|
@@ -713,6 +714,47 @@ function langHardcoded(config) {
|
|
|
713
714
|
*
|
|
714
715
|
* @returns {object} The yargs command module.
|
|
715
716
|
*/
|
|
717
|
+
/**
|
|
718
|
+
* `package-build yaml` — lint note frontmatter and every YAML file.
|
|
719
|
+
*
|
|
720
|
+
* Frontmatter carries a note's type, shortcode, address and system blocks, and
|
|
721
|
+
* until this command existed nothing checked it *as YAML*: a duplicate key was
|
|
722
|
+
* caught during the parse, logged at `warn`, and turned into a note with no
|
|
723
|
+
* frontmatter, which every later pass then skipped while the build reported
|
|
724
|
+
* success.
|
|
725
|
+
*
|
|
726
|
+
* It ships as a command rather than as a configuration to adopt, so a consumer
|
|
727
|
+
* needs no ESLint and no `eslint.config.js` of its own — and a repository that
|
|
728
|
+
* has one, as `Song-of-Heroic-Lands-FoundryVTT` does for `src/`, keeps it
|
|
729
|
+
* untouched and unconsulted.
|
|
730
|
+
*
|
|
731
|
+
* @returns {object} The yargs command module.
|
|
732
|
+
*/
|
|
733
|
+
function yamlCommand() {
|
|
734
|
+
return {
|
|
735
|
+
command: "yaml [paths..]",
|
|
736
|
+
describe: "Lint YAML — note frontmatter, and every YAML file in the repository",
|
|
737
|
+
builder: (y) =>
|
|
738
|
+
y.positional("paths", {
|
|
739
|
+
describe:
|
|
740
|
+
"Files or globs to lint. Defaults to every YAML file and " +
|
|
741
|
+
"markdown frontmatter git would consider — tracked, plus " +
|
|
742
|
+
"untracked and not ignored.",
|
|
743
|
+
type: "string",
|
|
744
|
+
}),
|
|
745
|
+
handler: handler(async (args) => {
|
|
746
|
+
const { findings, checked } = await lintYaml(process.cwd(), { paths: args.paths });
|
|
747
|
+
const errors = reportFindings(findings, {});
|
|
748
|
+
const warnings = findings.length - errors;
|
|
749
|
+
console.log(
|
|
750
|
+
`package-build: ${checked} file(s) checked · ` +
|
|
751
|
+
`${errors} error(s) · ${warnings} warning(s)`,
|
|
752
|
+
);
|
|
753
|
+
if (errors) process.exitCode = 1;
|
|
754
|
+
}),
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
716
758
|
/**
|
|
717
759
|
* `labels check` — do the machine registry and the documented table agree?
|
|
718
760
|
*
|
|
@@ -1065,6 +1107,7 @@ yargs(hideBin(process.argv))
|
|
|
1065
1107
|
.command(schemaCommand())
|
|
1066
1108
|
.command(langCommand())
|
|
1067
1109
|
.command(labelsCommand())
|
|
1110
|
+
.command(yamlCommand())
|
|
1068
1111
|
.command(bundleCommand())
|
|
1069
1112
|
.command(releaseCommand())
|
|
1070
1113
|
.command(deployCommand())
|