@heroiclands/package-build 22.21.0 → 22.23.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 +223 -0
- package/assets/styles/content-base.css +162 -0
- package/bin/content-commands.mjs +77 -17
- package/bin/package-build.mjs +15 -2
- package/ci/content-format-reference.mjs +68 -1
- package/config.mjs +26 -0
- package/content-config.mjs +153 -6
- package/docs/api.md +120 -89
- package/docs/authoring/assets.md +14 -0
- package/docs/authoring/dates-and-calendars.md +41 -3
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +325 -35
- package/docs/commands.md +12 -10
- package/docs/configuration.md +117 -14
- package/docs/content-format.md +4 -2
- package/docs/guides/book.md +5 -4
- package/docs/guides/reference-pages.md +15 -0
- package/docs/reference/format-details.md +545 -104
- package/docs/reference/note-types.md +30 -13
- package/engine/actor-compiler.mjs +23 -0
- package/engine/anchored-sections.mjs +35 -4
- package/engine/anchors.mjs +26 -30
- package/engine/art-fields.mjs +118 -0
- package/engine/art-slots.mjs +7 -1
- package/engine/asset-index.mjs +160 -10
- package/engine/asset-replacement.mjs +93 -0
- package/engine/asset-types.mjs +240 -0
- package/engine/base-compiler.mjs +139 -22
- package/engine/base-styles.mjs +149 -0
- package/engine/bundles.mjs +13 -2
- package/engine/compendiums.mjs +8 -1
- package/engine/content-blocks.mjs +410 -0
- package/engine/content-embeds.mjs +19 -0
- package/engine/content-figures.mjs +468 -0
- package/engine/content-footnotes.mjs +197 -0
- package/engine/content-format-check.mjs +15 -12
- package/engine/content-format.yaml +1 -1
- package/engine/content-images.mjs +126 -25
- package/engine/content-index.mjs +38 -12
- package/engine/content-links.mjs +14 -6
- package/engine/content-tables.mjs +45 -4
- package/engine/content-tasklists.mjs +189 -0
- package/engine/docs-checks.mjs +4 -4
- package/engine/extension-attributes.mjs +48 -2
- package/engine/field-reference.mjs +31 -10
- package/engine/field-spec.mjs +10 -8
- package/engine/foreign-catalog.mjs +143 -18
- package/engine/frontmatter-lint.mjs +120 -65
- package/engine/frontmatter.mjs +3 -3
- package/engine/heading-attributes.mjs +321 -0
- package/engine/helpers.mjs +109 -41
- package/engine/homepage.mjs +8 -8
- package/engine/index.mjs +3 -0
- package/engine/infobox-registry.mjs +4 -1
- package/engine/infobox-render.mjs +15 -3
- package/engine/infobox.mjs +129 -15
- package/engine/itinerary-scenes.mjs +4 -10
- package/engine/journals.mjs +294 -50
- package/engine/lore-events.mjs +197 -0
- package/engine/macros.mjs +1 -1
- package/engine/markdown-expressions.mjs +434 -59
- package/engine/note-dates.mjs +354 -12
- package/engine/note-frontmatter.mjs +7 -16
- package/engine/note-vocabulary.mjs +183 -42
- package/engine/page-lists.mjs +358 -0
- package/engine/pathnames.mjs +36 -0
- package/engine/pdf-build.mjs +431 -74
- package/engine/pdf-images.mjs +64 -0
- package/engine/pdf-render.mjs +563 -66
- package/engine/rank-ladder.mjs +198 -0
- package/engine/reckoning-markers.mjs +1 -1
- package/engine/retired-fields.mjs +8 -13
- package/engine/scenes.mjs +26 -7
- package/engine/site-build.mjs +166 -23
- package/engine/site-config.mjs +10 -1
- package/engine/site-index.mjs +90 -3
- package/engine/site-preview.mjs +20 -7
- package/engine/social-ties.mjs +52 -14
- package/engine/sql-tables.mjs +66 -9
- package/engine/standings.mjs +89 -6
- package/engine/system-block.mjs +56 -30
- package/engine/web-wikilinks.mjs +48 -1
- package/hm3/actors.mjs +8 -5
- package/manifest.mjs +43 -24
- package/package.json +1 -1
- package/packagebuild-metadata.jsonl +13 -13
- package/sohl/actors.mjs +22 -99
- package/sohl/item-fields.mjs +5 -5
- package/sohl/items.mjs +1 -1
- package/sohl/note-schemas.mjs +11 -21
- package/types/config.d.mts +9 -0
- package/types/content-config.d.mts +48 -1
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/anchored-sections.d.mts +24 -0
- package/types/engine/anchors.d.mts +9 -4
- package/types/engine/art-fields.d.mts +64 -0
- package/types/engine/asset-index.d.mts +43 -0
- package/types/engine/asset-replacement.d.mts +22 -0
- package/types/engine/asset-types.d.mts +18 -0
- package/types/engine/base-compiler.d.mts +19 -17
- package/types/engine/base-styles.d.mts +93 -0
- package/types/engine/content-blocks.d.mts +87 -0
- package/types/engine/content-embeds.d.mts +16 -0
- package/types/engine/content-figures.d.mts +101 -0
- package/types/engine/content-footnotes.d.mts +80 -0
- package/types/engine/content-format-check.d.mts +8 -6
- package/types/engine/content-images.d.mts +78 -12
- package/types/engine/content-index.d.mts +8 -2
- package/types/engine/content-tables.d.mts +12 -4
- package/types/engine/content-tasklists.d.mts +72 -0
- package/types/engine/extension-attributes.d.mts +22 -1
- package/types/engine/field-reference.d.mts +21 -0
- package/types/engine/field-spec.d.mts +20 -16
- package/types/engine/foreign-catalog.d.mts +39 -0
- package/types/engine/frontmatter.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +179 -0
- package/types/engine/helpers.d.mts +29 -10
- package/types/engine/homepage.d.mts +7 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/infobox-registry.d.mts +4 -1
- package/types/engine/infobox-render.d.mts +6 -1
- package/types/engine/infobox.d.mts +6 -0
- package/types/engine/journals.d.mts +41 -12
- package/types/engine/lore-events.d.mts +14 -0
- package/types/engine/markdown-expressions.d.mts +46 -2
- package/types/engine/note-dates.d.mts +64 -1
- package/types/engine/note-frontmatter.d.mts +1 -4
- package/types/engine/note-vocabulary.d.mts +82 -6
- package/types/engine/page-lists.d.mts +126 -0
- package/types/engine/pathnames.d.mts +26 -0
- package/types/engine/pdf-build.d.mts +28 -6
- package/types/engine/pdf-images.d.mts +52 -0
- package/types/engine/pdf-render.d.mts +84 -2
- package/types/engine/rank-ladder.d.mts +32 -0
- package/types/engine/retired-fields.d.mts +4 -6
- package/types/engine/site-build.d.mts +27 -4
- package/types/engine/site-config.d.mts +9 -0
- package/types/engine/site-index.d.mts +67 -1
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -7
- package/types/engine/standings.d.mts +21 -3
- package/types/engine/system-block.d.mts +37 -14
- package/types/engine/web-wikilinks.d.mts +3 -1
- package/types/hm3/actors.d.mts +21 -3
- package/types/manifest.d.mts +19 -23
- package/types/sohl/actors.d.mts +6 -7
- package/engine/content-admonitions.mjs +0 -114
- package/engine/content-captions.mjs +0 -181
- package/engine/content-secrets.mjs +0 -112
- package/types/engine/content-admonitions.d.mts +0 -24
- package/types/engine/content-captions.d.mts +0 -45
- package/types/engine/content-secrets.d.mts +0 -17
|
@@ -9,9 +9,12 @@ import { formatGenerated } from "../engine/format-generated.mjs";
|
|
|
9
9
|
import { loadContentFormat } from "../engine/content-format.mjs";
|
|
10
10
|
import {
|
|
11
11
|
BEING_ARCHETYPES,
|
|
12
|
+
NOTE_TOP_LEVEL_FIELDS,
|
|
12
13
|
NOTE_VOCABULARY,
|
|
13
14
|
SHARED_DATA_FIELDS,
|
|
14
15
|
} from "../engine/note-vocabulary.mjs";
|
|
16
|
+
import { EXPRESSION_HELPERS } from "../engine/markdown-expressions.mjs";
|
|
17
|
+
import { PAGE_LIST_ATTRIBUTES } from "../engine/page-lists.mjs";
|
|
15
18
|
|
|
16
19
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
20
|
const output = path.join(root, "docs/reference/note-types.md");
|
|
@@ -39,6 +42,16 @@ const rows = [
|
|
|
39
42
|
"",
|
|
40
43
|
"The [first-note guide](../authoring/first-note.md) teaches the shape of a note. [Frontmatter](../authoring/frontmatter.md) explains shared and system-specific values. The [detailed reference](format-details.md) gives the full behavior and examples.",
|
|
41
44
|
"",
|
|
45
|
+
"## Top-level keys",
|
|
46
|
+
"",
|
|
47
|
+
"These are the only keys a note writes at its top level, in the order the formatter puts them. A key absent from this table is a lint error at its own line: the region is closed, so the key reaches no document and no page. Facts about the subject belong under `data`, and values for one game system belong inside that system's block.",
|
|
48
|
+
"",
|
|
49
|
+
"| Key | Meaning |",
|
|
50
|
+
"| --- | --- |",
|
|
51
|
+
...NOTE_TOP_LEVEL_FIELDS.map(
|
|
52
|
+
(field) => `| \`${cell(field.name)}\` | ${cell(field.describe)} |`,
|
|
53
|
+
),
|
|
54
|
+
"",
|
|
42
55
|
"## Shared `data` fields",
|
|
43
56
|
"",
|
|
44
57
|
"These fields are accepted by every note type. A field's value can still be irrelevant to a particular output; the build reports that where it can.",
|
|
@@ -106,7 +119,61 @@ if (process.argv.includes("--check")) {
|
|
|
106
119
|
process.exitCode = 1;
|
|
107
120
|
}
|
|
108
121
|
} else {
|
|
109
|
-
|
|
122
|
+
fs.writeFileSync(output, result);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The two tables `docs/authoring/links-and-markup.md` generates: every helper
|
|
126
|
+
// an expression may call, and every attribute a page-list fence takes. Both are
|
|
127
|
+
// written from the one list the engine reads, so neither document can fall
|
|
128
|
+
// behind a new entry. Replaced in one pass and written once, so a `--check` run
|
|
129
|
+
// names the file rather than one of the tables in it.
|
|
130
|
+
const markupPath = path.join(root, "docs/authoring/links-and-markup.md");
|
|
131
|
+
const markup = fs.readFileSync(markupPath, "utf8");
|
|
132
|
+
const GENERATED_TABLES = [
|
|
133
|
+
{
|
|
134
|
+
marker: "expression-helpers",
|
|
135
|
+
rows: [
|
|
136
|
+
"| Helper | Parameters | What it gives you |",
|
|
137
|
+
"| ------ | ---------- | ----------------- |",
|
|
138
|
+
...Object.entries(EXPRESSION_HELPERS).map(
|
|
139
|
+
([name, { params, summary }]) =>
|
|
140
|
+
`| \`${cell(name)}\` | \`${cell(params)}\` | ${cell(summary)} |`,
|
|
141
|
+
),
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
marker: "page-list-attributes",
|
|
146
|
+
rows: [
|
|
147
|
+
"| Attribute | Value | Default | What it does |",
|
|
148
|
+
"| --------- | ----- | ------- | ------------ |",
|
|
149
|
+
...Object.entries(PAGE_LIST_ATTRIBUTES).map(
|
|
150
|
+
([name, { value, default: fallback, summary }]) =>
|
|
151
|
+
`| \`${name}\` | ${cell(value)} | ${cell(fallback)} | ${cell(summary)} |`,
|
|
152
|
+
),
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
let markupBody = markup;
|
|
157
|
+
for (const { marker, rows } of GENERATED_TABLES) {
|
|
158
|
+
const region = new RegExp(`<!-- ${marker}:start -->[\\s\\S]*?<!-- ${marker}:end -->`);
|
|
159
|
+
if (!region.test(markupBody)) {
|
|
160
|
+
throw new Error(`The ${marker} reference requires its generation markers`);
|
|
161
|
+
}
|
|
162
|
+
markupBody = markupBody.replace(
|
|
163
|
+
region,
|
|
164
|
+
`<!-- ${marker}:start -->\n\n${rows.join("\n")}\n\n<!-- ${marker}:end -->`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const updatedMarkup = await formatGenerated(markupBody, markupPath);
|
|
168
|
+
if (process.argv.includes("--check")) {
|
|
169
|
+
if (markup !== updatedMarkup) {
|
|
170
|
+
console.error(
|
|
171
|
+
"docs/authoring/links-and-markup.md: error: run node ci/content-format-reference.mjs to refresh its generated tables",
|
|
172
|
+
);
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
fs.writeFileSync(markupPath, updatedMarkup);
|
|
110
177
|
}
|
|
111
178
|
|
|
112
179
|
const detailsPath = path.join(root, "docs/reference/format-details.md");
|
package/config.mjs
CHANGED
|
@@ -65,6 +65,7 @@ const SECTION_KEYS = [
|
|
|
65
65
|
"stageDir",
|
|
66
66
|
"assets",
|
|
67
67
|
"assetTransform",
|
|
68
|
+
"baseStyles",
|
|
68
69
|
"manifest",
|
|
69
70
|
"manifestFlags",
|
|
70
71
|
"schema",
|
|
@@ -205,6 +206,23 @@ function requireNonEmptyString(value, where) {
|
|
|
205
206
|
return /** @type {string} */ (value);
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
/**
|
|
210
|
+
* A declared switch, with the behaviour that applies when it is not declared.
|
|
211
|
+
*
|
|
212
|
+
* A truthy string is refused rather than coerced: `baseStyles: "false"` reads
|
|
213
|
+
* as off and would be on, which is the one wrong answer available here.
|
|
214
|
+
*
|
|
215
|
+
* @param {unknown} value - What was declared, or `undefined`.
|
|
216
|
+
* @param {boolean} fallback - The undeclared behaviour.
|
|
217
|
+
* @param {string} where - Dotted path, for the error.
|
|
218
|
+
* @returns {boolean} The setting.
|
|
219
|
+
*/
|
|
220
|
+
function requireBoolean(value, fallback, where) {
|
|
221
|
+
if (value === undefined) return fallback;
|
|
222
|
+
if (typeof value !== "boolean") fail(where, "must be true or false");
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
|
|
208
226
|
/**
|
|
209
227
|
* One staging copy: a source path in the repository, and where it lands under
|
|
210
228
|
* the staged package root.
|
|
@@ -537,6 +555,9 @@ function normalizeExceptions(value, field, where) {
|
|
|
537
555
|
* @property {string} stageDir The staged package root, relative to
|
|
538
556
|
* `rootDir`. Every asset `to:` lands under it.
|
|
539
557
|
* @property {readonly Readonly<AssetSpec>[]} assets
|
|
558
|
+
* @property {boolean} baseStyles Whether the package takes the shared base
|
|
559
|
+
* stylesheet, staged into `stageDir` and
|
|
560
|
+
* named first in the manifest's `styles`.
|
|
540
561
|
* @property {Readonly<Record<string, unknown>>} manifest The manifest
|
|
541
562
|
* specification, emitted as declared.
|
|
542
563
|
* @property {string|null} manifestFlags Module to load a `flags` function
|
|
@@ -877,6 +898,11 @@ export function resolvePackageBuildConfig(shared) {
|
|
|
877
898
|
ARTIFACT_OF_KIND[/** @type {"systems"|"modules"} */ (shared.packageKind)]
|
|
878
899
|
: requireNonEmptyString(releaseInput.artifact, "packageBuild.release.artifact"),
|
|
879
900
|
assets: Object.freeze(assets),
|
|
901
|
+
// On by default, because the rules it carries are what compiled content
|
|
902
|
+
// needs to be legible and a package that declined them by omission
|
|
903
|
+
// would ship the content unstyled without anything saying so. A package
|
|
904
|
+
// that wants the surface entirely to itself says so.
|
|
905
|
+
baseStyles: requireBoolean(section.baseStyles, true, "packageBuild.baseStyles"),
|
|
880
906
|
schema: normalizeSchema(section.schema),
|
|
881
907
|
assetTransform:
|
|
882
908
|
section.assetTransform === undefined ?
|
package/content-config.mjs
CHANGED
|
@@ -368,7 +368,8 @@ export function publishesContentPages(config) {
|
|
|
368
368
|
* @property {string} [stage] Compiled LevelDB packs.
|
|
369
369
|
* @property {string} [unpack] Where `unpack` extracts JSON back to.
|
|
370
370
|
* @property {string} [foreignCache] Where a dependency declaring
|
|
371
|
-
* `itemCatalog: true`
|
|
371
|
+
* `itemCatalog: true` or
|
|
372
|
+
* `assetArchive: true` is unpacked.
|
|
372
373
|
* Inbound, and fetched rather than
|
|
373
374
|
* committed.
|
|
374
375
|
* @property {string} [metadataCache] Where a dependency's published content
|
|
@@ -486,6 +487,15 @@ export function publishesContentPages(config) {
|
|
|
486
487
|
* package this one targets — for a system
|
|
487
488
|
* relationship, `verified` is what
|
|
488
489
|
* `_stats.systemVersion` is stamped from.
|
|
490
|
+
* @property {boolean} [itemCatalog] Whether `deps fetch` unpacks this
|
|
491
|
+
* dependency's release archive and extracts
|
|
492
|
+
* its Item packs, so the actors pass can
|
|
493
|
+
* resolve embedded items this repository
|
|
494
|
+
* does not hold. Needs a `manifest`.
|
|
495
|
+
* @property {boolean} [assetArchive] Whether `deps fetch` unpacks this
|
|
496
|
+
* dependency's release archive for its
|
|
497
|
+
* asset bytes alone, building no item
|
|
498
|
+
* catalogue from it. Needs a `manifest`.
|
|
489
499
|
* @property {boolean} [contentIndex] Whether `deps fetch` fetches this
|
|
490
500
|
* dependency's content index. Default
|
|
491
501
|
* `true`. `false` declares the dependency
|
|
@@ -494,6 +504,13 @@ export function publishesContentPages(config) {
|
|
|
494
504
|
* `itemCatalog: true` on the same entry,
|
|
495
505
|
* since a catalogue is fetched from the same
|
|
496
506
|
* index.
|
|
507
|
+
* @property {boolean} [assetReplacement] Whether this dependency's asset
|
|
508
|
+
* tree answers this package's own asset
|
|
509
|
+
* addresses ahead of its local record.
|
|
510
|
+
* Default `false`. Refuses `contentIndex:
|
|
511
|
+
* false` on the same entry, since a
|
|
512
|
+
* replacement with no fetched index to
|
|
513
|
+
* check against resolves nothing.
|
|
497
514
|
*/
|
|
498
515
|
|
|
499
516
|
/**
|
|
@@ -620,6 +637,9 @@ export function publishesContentPages(config) {
|
|
|
620
637
|
* @property {string[]} [skipDirectories] Directory names the content walk ignores
|
|
621
638
|
* wherever they appear (e.g. Obsidian's
|
|
622
639
|
* `Templates`). Default `[]`.
|
|
640
|
+
* @property {boolean} [forbidGeneratedArt] Whether `lint` refuses an asset
|
|
641
|
+
* record of this package carrying
|
|
642
|
+
* `ai: true`. Default `false`.
|
|
623
643
|
* @property {PackageBuildSection} [packageBuild] Reserved for
|
|
624
644
|
* `@heroiclands/package-build`, which
|
|
625
645
|
* validates it. Not read here.
|
|
@@ -700,6 +720,8 @@ export function publishesContentPages(config) {
|
|
|
700
720
|
* types. The one set the compilers and the
|
|
701
721
|
* link-manifest emitter both read.
|
|
702
722
|
* @property {readonly string[]} skipDirectories
|
|
723
|
+
* @property {boolean} forbidGeneratedArt Whether `lint` refuses an asset
|
|
724
|
+
* record of this package carrying `ai: true`.
|
|
703
725
|
* @property {import("./engine/content-icons.mjs").IconRegistry} icons The
|
|
704
726
|
* fonts this package ships and the names it
|
|
705
727
|
* draws from them; empty when it declares
|
|
@@ -728,6 +750,7 @@ const CONFIG_KEYS = [
|
|
|
728
750
|
"itemBuilders",
|
|
729
751
|
"paths",
|
|
730
752
|
"skipDirectories",
|
|
753
|
+
"forbidGeneratedArt",
|
|
731
754
|
"icons",
|
|
732
755
|
"packs",
|
|
733
756
|
"docs",
|
|
@@ -765,14 +788,25 @@ const PDF_FONT_KEYS = ["serif", "sans", "mono", "path"];
|
|
|
765
788
|
const EMPTY_PDF_FONTS = Object.freeze({ serif: "", sans: "", mono: "", path: "" });
|
|
766
789
|
const DOC_PAGE_KEYS = ["title", "out", "preamble", "frontmatter"];
|
|
767
790
|
const RELATIONSHIP_KINDS = ["systems", "requires", "recommends", "conflicts"];
|
|
768
|
-
|
|
791
|
+
/**
|
|
792
|
+
* Every key a declared relationship may carry.
|
|
793
|
+
*
|
|
794
|
+
* Read by the manifest writer as well as by the configuration check: the keys
|
|
795
|
+
* Foundry's own relationship schema does not name are the build's own, and the
|
|
796
|
+
* published manifest carries none of them.
|
|
797
|
+
*
|
|
798
|
+
* @type {readonly string[]}
|
|
799
|
+
*/
|
|
800
|
+
export const RELATIONSHIP_KEYS = [
|
|
769
801
|
"id",
|
|
770
802
|
"contentPackage",
|
|
771
803
|
"type",
|
|
772
804
|
"manifest",
|
|
773
805
|
"compatibility",
|
|
774
806
|
"itemCatalog",
|
|
807
|
+
"assetArchive",
|
|
775
808
|
"contentIndex",
|
|
809
|
+
"assetReplacement",
|
|
776
810
|
];
|
|
777
811
|
const AUTHOR_KEYS = ["name", "email", "url"];
|
|
778
812
|
const ITEM_BUILDER_KEYS = ["system", "img", "fields"];
|
|
@@ -1580,6 +1614,7 @@ export const DERIVED_HUGO_KEYS = Object.freeze({
|
|
|
1580
1614
|
"params.brand": "the organisation's brand links, in `engine/site-config.mjs`",
|
|
1581
1615
|
"params.notfound": "`site.notfound`",
|
|
1582
1616
|
"markup.goldmark.renderer.unsafe": "the toolchain, whose pages carry raw HTML",
|
|
1617
|
+
"markup.goldmark.extensions.taskList": "the toolchain, whose format has no checkbox",
|
|
1583
1618
|
menu: "the navigation `package-build deps fetch` caches from heroiclands.org",
|
|
1584
1619
|
});
|
|
1585
1620
|
|
|
@@ -1903,6 +1938,44 @@ function normalizePdf(value, rootDir) {
|
|
|
1903
1938
|
});
|
|
1904
1939
|
}
|
|
1905
1940
|
|
|
1941
|
+
/**
|
|
1942
|
+
* A dotted version string as the three-element tuple Foundry compares.
|
|
1943
|
+
*
|
|
1944
|
+
* A missing component reads as `0`, so `"14"` compares as `14.0.0` — the
|
|
1945
|
+
* common way an author writes a major-only floor or pin. A non-numeric
|
|
1946
|
+
* component reads as `0` too, rather than throwing: this runs on values
|
|
1947
|
+
* {@link requireNonEmptyString} has already accepted as non-empty strings,
|
|
1948
|
+
* and a version with a qualifier (`"14.359.0-beta"`) is not this function's
|
|
1949
|
+
* place to refuse.
|
|
1950
|
+
*
|
|
1951
|
+
* @param {string} version - A dotted version string.
|
|
1952
|
+
* @returns {readonly [number, number, number]} Major, minor, patch.
|
|
1953
|
+
*/
|
|
1954
|
+
function versionTriple(version) {
|
|
1955
|
+
const parts = version.split(".");
|
|
1956
|
+
return [0, 1, 2].map((index) => {
|
|
1957
|
+
const n = Number.parseInt(parts[index] ?? "0", 10);
|
|
1958
|
+
return Number.isNaN(n) ? 0 : n;
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* Whether `verified` names a build below `minimum`, compared as version
|
|
1964
|
+
* triples.
|
|
1965
|
+
*
|
|
1966
|
+
* @param {string} minimum - The declared floor.
|
|
1967
|
+
* @param {string} verified - The declared verified build.
|
|
1968
|
+
* @returns {boolean} `true` when `verified` sorts below `minimum`.
|
|
1969
|
+
*/
|
|
1970
|
+
function verifiedBelowMinimum(minimum, verified) {
|
|
1971
|
+
const a = versionTriple(verified);
|
|
1972
|
+
const b = versionTriple(minimum);
|
|
1973
|
+
for (let i = 0; i < 3; i++) {
|
|
1974
|
+
if (a[i] !== b[i]) return a[i] < b[i];
|
|
1975
|
+
}
|
|
1976
|
+
return false;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1906
1979
|
/**
|
|
1907
1980
|
* Validate a Foundry version range.
|
|
1908
1981
|
*
|
|
@@ -1912,6 +1985,12 @@ function normalizePdf(value, rootDir) {
|
|
|
1912
1985
|
* load-bearing there is `verified`, and a relationship may reasonably name a
|
|
1913
1986
|
* package without pinning a floor at all.
|
|
1914
1987
|
*
|
|
1988
|
+
* **The two keys are a range, not two independent facts.** Foundry reads
|
|
1989
|
+
* `verified` as the build inside `minimum`'s floor that was actually tested,
|
|
1990
|
+
* so a `verified` below `minimum` names a build the package already refuses
|
|
1991
|
+
* to install on. Caught only when both are present — a relationship naming
|
|
1992
|
+
* `verified` alone has nothing to compare it against.
|
|
1993
|
+
*
|
|
1915
1994
|
* @param {unknown} value - The declared range, or `undefined`.
|
|
1916
1995
|
* @param {string} where - Dotted path, for the error.
|
|
1917
1996
|
* @param {boolean} [requireMinimum] - Whether `minimum` must be present.
|
|
@@ -1929,6 +2008,18 @@ function normalizeCompatibility(value, where, requireMinimum = true) {
|
|
|
1929
2008
|
if (input.verified !== undefined) {
|
|
1930
2009
|
out.verified = requireNonEmptyString(input.verified, `${where}.verified`);
|
|
1931
2010
|
}
|
|
2011
|
+
if (
|
|
2012
|
+
out.minimum !== undefined &&
|
|
2013
|
+
out.verified !== undefined &&
|
|
2014
|
+
verifiedBelowMinimum(out.minimum, out.verified)
|
|
2015
|
+
) {
|
|
2016
|
+
fail(
|
|
2017
|
+
`${where}.verified`,
|
|
2018
|
+
`declares \`${out.verified}\`, below \`${where}.minimum\`'s \`${out.minimum}\` — ` +
|
|
2019
|
+
"Foundry reads the two as one range, so a build below the floor is " +
|
|
2020
|
+
"never the one verified against it",
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
1932
2023
|
return Object.freeze(out);
|
|
1933
2024
|
}
|
|
1934
2025
|
|
|
@@ -2022,6 +2113,21 @@ function normalizeSystems(value) {
|
|
|
2022
2113
|
// declaration that cannot answer "which version was this built
|
|
2023
2114
|
// against" is the gap this block exists to close.
|
|
2024
2115
|
const verified = requireNonEmptyString(compat.verified, `${at}.compatibility.verified`);
|
|
2116
|
+
const minimum =
|
|
2117
|
+
compat.minimum === undefined || compat.minimum === null ?
|
|
2118
|
+
null
|
|
2119
|
+
: requireNonEmptyString(compat.minimum, `${at}.compatibility.minimum`);
|
|
2120
|
+
// Caught here too: the shape is the one {@link normalizeCompatibility}
|
|
2121
|
+
// validates, and the same contradiction is possible wherever it
|
|
2122
|
+
// appears.
|
|
2123
|
+
if (minimum !== null && verifiedBelowMinimum(minimum, verified)) {
|
|
2124
|
+
fail(
|
|
2125
|
+
`${at}.compatibility.verified`,
|
|
2126
|
+
`declares \`${verified}\`, below \`${at}.compatibility.minimum\`'s ` +
|
|
2127
|
+
`\`${minimum}\` — Foundry reads the two as one range, so a build ` +
|
|
2128
|
+
"below the floor is never the one verified against it",
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2025
2131
|
|
|
2026
2132
|
out[id] = Object.freeze({
|
|
2027
2133
|
manifest:
|
|
@@ -2029,10 +2135,7 @@ function normalizeSystems(value) {
|
|
|
2029
2135
|
null
|
|
2030
2136
|
: requireNonEmptyString(spec.manifest, `${at}.manifest`),
|
|
2031
2137
|
compatibility: Object.freeze({
|
|
2032
|
-
minimum
|
|
2033
|
-
compat.minimum === undefined || compat.minimum === null ?
|
|
2034
|
-
null
|
|
2035
|
-
: requireNonEmptyString(compat.minimum, `${at}.compatibility.minimum`),
|
|
2138
|
+
minimum,
|
|
2036
2139
|
verified,
|
|
2037
2140
|
}),
|
|
2038
2141
|
});
|
|
@@ -2129,6 +2232,23 @@ function normalizeRelationships(value) {
|
|
|
2129
2232
|
}
|
|
2130
2233
|
spec.itemCatalog = rel.itemCatalog;
|
|
2131
2234
|
}
|
|
2235
|
+
// Opt-in, sibling to `itemCatalog`: unpack this dependency's
|
|
2236
|
+
// release archive for its asset bytes alone, building no item
|
|
2237
|
+
// catalogue from it. Buys the archive for a package with no
|
|
2238
|
+
// items of its own to extract — an asset-replacement source
|
|
2239
|
+
// ships no Item packs and has no use for `itemCatalog: true`.
|
|
2240
|
+
if (rel.assetArchive !== undefined) {
|
|
2241
|
+
if (typeof rel.assetArchive !== "boolean") {
|
|
2242
|
+
fail(`${at}.assetArchive`, "must be true or false");
|
|
2243
|
+
}
|
|
2244
|
+
if (rel.assetArchive && spec.manifest === undefined) {
|
|
2245
|
+
fail(
|
|
2246
|
+
`${at}.assetArchive`,
|
|
2247
|
+
"needs a `manifest` naming the package to fetch",
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
spec.assetArchive = rel.assetArchive;
|
|
2251
|
+
}
|
|
2132
2252
|
// Opt-out: declares the dependency for the Foundry manifest
|
|
2133
2253
|
// only, so `deps fetch` fetches no content index for it and a
|
|
2134
2254
|
// wikilink into it is refused rather than silently dead. A
|
|
@@ -2147,6 +2267,26 @@ function normalizeRelationships(value) {
|
|
|
2147
2267
|
}
|
|
2148
2268
|
spec.contentIndex = rel.contentIndex;
|
|
2149
2269
|
}
|
|
2270
|
+
// Opt-in: this dependency's asset tree answers this
|
|
2271
|
+
// package's own asset addresses ahead of its local record —
|
|
2272
|
+
// the "rewrite table" `engine/asset-replacement.mjs` reads.
|
|
2273
|
+
// A replacement with no fetched index to check against
|
|
2274
|
+
// resolves nothing, so it cannot pair with
|
|
2275
|
+
// `contentIndex: false`.
|
|
2276
|
+
if (rel.assetReplacement !== undefined) {
|
|
2277
|
+
if (typeof rel.assetReplacement !== "boolean") {
|
|
2278
|
+
fail(`${at}.assetReplacement`, "must be true or false");
|
|
2279
|
+
}
|
|
2280
|
+
if (rel.assetReplacement && rel.contentIndex === false) {
|
|
2281
|
+
fail(
|
|
2282
|
+
`${at}.assetReplacement`,
|
|
2283
|
+
"cannot be true together with `contentIndex: false` — a " +
|
|
2284
|
+
"replacement with no fetched index to check against resolves " +
|
|
2285
|
+
"nothing",
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
spec.assetReplacement = rel.assetReplacement;
|
|
2289
|
+
}
|
|
2150
2290
|
return Object.freeze(spec);
|
|
2151
2291
|
}),
|
|
2152
2292
|
);
|
|
@@ -2625,6 +2765,12 @@ export function defineConfig(config) {
|
|
|
2625
2765
|
requireNonEmptyString(name, `skipDirectories[${index}]`),
|
|
2626
2766
|
);
|
|
2627
2767
|
|
|
2768
|
+
const forbidGeneratedArt = optionalBoolean(
|
|
2769
|
+
input.forbidGeneratedArt,
|
|
2770
|
+
"forbidGeneratedArt",
|
|
2771
|
+
false,
|
|
2772
|
+
);
|
|
2773
|
+
|
|
2628
2774
|
// Refused above for a documentation package, so there is nothing to read
|
|
2629
2775
|
// and nothing to derive an asset root or a package-wide system from.
|
|
2630
2776
|
const foundryPackage =
|
|
@@ -2731,6 +2877,7 @@ export function defineConfig(config) {
|
|
|
2731
2877
|
itemTypes,
|
|
2732
2878
|
docEntryTypes,
|
|
2733
2879
|
skipDirectories: Object.freeze(skipDirectories),
|
|
2880
|
+
forbidGeneratedArt,
|
|
2734
2881
|
icons: normalizeIcons(input.icons, rootDir),
|
|
2735
2882
|
packs: Object.freeze(packs),
|
|
2736
2883
|
packDirectories: Object.freeze(packDirectories),
|