@heroiclands/package-build 22.15.1 → 22.17.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 +110 -0
- package/README.md +39 -678
- package/bin/{content-build.mjs → content-commands.mjs} +197 -118
- package/bin/package-build.mjs +315 -42
- package/bin/report.mjs +1 -1
- package/ci/ci-docker.mjs +6 -3
- package/ci/ci-steps.mjs +6 -4
- package/ci/content-format-reference.mjs +106 -0
- package/config.mjs +22 -2
- package/content-config.mjs +74 -134
- package/docs/api.md +296 -236
- package/docs/authoring/assets.md +31 -0
- package/docs/authoring/dates-and-calendars.md +85 -0
- package/docs/authoring/first-note.md +48 -0
- package/docs/authoring/frontmatter.md +40 -0
- package/docs/authoring/links-and-markup.md +137 -0
- package/docs/commands.md +633 -162
- package/docs/configuration.md +176 -140
- package/docs/content-format.md +50 -4229
- package/docs/design.md +32 -0
- package/docs/diagnostics.md +29 -22
- package/docs/getting-started.md +21 -877
- package/docs/guides/book.md +153 -0
- package/docs/guides/e2e.md +143 -0
- package/docs/guides/icons.md +126 -0
- package/docs/guides/localization.md +120 -0
- package/docs/guides/module-project.md +915 -0
- package/docs/guides/packs.md +167 -0
- package/docs/guides/reference-pages.md +148 -0
- package/docs/guides/release-and-deploy.md +114 -0
- package/docs/guides/site.md +152 -0
- package/docs/guides/system-project.md +45 -0
- package/docs/index.md +16 -0
- package/docs/project-setup.md +54 -35
- package/docs/reference/format-details.md +4362 -0
- package/docs/reference/note-types.md +532 -0
- package/engine/address-render.mjs +26 -0
- package/engine/address.mjs +3 -20
- package/engine/anchors.mjs +15 -6
- package/engine/asset-bindings.mjs +6 -2
- package/engine/base-compiler.mjs +47 -11
- package/engine/being-measurements.mjs +70 -0
- package/engine/calendar-artifacts.mjs +1 -1
- package/engine/calendar-choice.mjs +32 -0
- package/engine/calendar-format.mjs +441 -0
- package/engine/calendar-human.mjs +133 -0
- package/engine/calendar-notes.mjs +307 -88
- package/engine/calendars.mjs +8 -1
- package/engine/changelog-lint.mjs +2 -2
- package/engine/code-fences.mjs +23 -97
- package/engine/compile-corpus.mjs +6 -7
- package/engine/content-admonitions.mjs +114 -0
- package/engine/content-captions.mjs +181 -0
- package/engine/content-embeds.mjs +2 -2
- package/engine/content-footnotes.mjs +34 -0
- package/engine/content-format-check.mjs +2 -2
- package/engine/content-format.mjs +127 -73
- package/engine/content-format.yaml +746 -0
- package/engine/content-html.mjs +1 -1
- package/engine/content-icons.mjs +45 -92
- package/engine/content-images.mjs +22 -74
- package/engine/content-index.mjs +12 -3
- package/engine/content-links.mjs +21 -26
- package/engine/content-lint.mjs +1 -5
- package/engine/content-secrets.mjs +3 -1
- package/engine/content-tables.mjs +47 -1257
- package/engine/culture-choice.mjs +32 -0
- package/engine/date-conversion.mjs +47 -0
- package/engine/declaration-check.mjs +87 -0
- package/engine/docs-checks.mjs +140 -0
- package/engine/documentation-links.mjs +31 -0
- package/engine/exported-scene.mjs +1 -1
- package/engine/extension-attributes.mjs +74 -0
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +4 -0
- package/engine/foreign-catalog.mjs +5 -5
- package/engine/format-generated.mjs +15 -0
- package/engine/foundry-entries.mjs +3 -1
- package/engine/frontmatter-lint.mjs +5 -1
- package/engine/frontmatter.mjs +10 -0
- package/engine/generate.mjs +2 -2
- package/engine/helpers.mjs +71 -117
- package/engine/homepage.mjs +2 -3
- package/engine/index-records.mjs +1 -0
- package/engine/index.mjs +18 -3
- package/engine/infobox-registry.mjs +3 -1
- package/engine/infobox.mjs +38 -20
- package/engine/item-compiler.mjs +10 -8
- package/engine/item-docs.mjs +7 -6
- package/engine/journals.mjs +51 -10
- package/engine/macros.mjs +1 -1
- package/engine/map-build.mjs +1 -1
- package/engine/map-dot.mjs +1 -1
- package/engine/markdown-expressions.mjs +118 -0
- package/engine/metadata-index.mjs +7 -3
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-dates.mjs +314 -64
- package/engine/note-package.mjs +5 -25
- package/engine/note-state.mjs +6 -2
- package/engine/note-vocabulary.mjs +74 -19
- package/engine/number-words.mjs +75 -0
- package/engine/office-holders.mjs +188 -0
- package/engine/pack-config.mjs +2 -15
- package/engine/pack-router.mjs +24 -5
- package/engine/packages.mjs +3 -2
- package/engine/pathnames.mjs +21 -24
- package/engine/pdf-build.mjs +217 -55
- package/engine/pdf-fonts.mjs +2 -2
- package/engine/pdf-images.mjs +82 -0
- package/engine/pdf-render.mjs +174 -22
- package/engine/populations.mjs +3 -2
- package/engine/project-init.mjs +499 -0
- package/engine/prose-config.mjs +1 -1
- package/engine/prose-lint.mjs +7 -6
- package/engine/readability-lint.mjs +86 -0
- package/engine/reckoning-markers.mjs +176 -166
- package/engine/scenes.mjs +5 -2
- package/engine/schema-check.mjs +3 -3
- package/engine/site-build.mjs +87 -37
- package/engine/site-config.mjs +8 -8
- package/engine/site-index.mjs +4 -0
- package/engine/site-maps.mjs +1 -1
- package/engine/site-preview.mjs +19 -8
- package/engine/social-ties.mjs +5 -1
- package/engine/sql-tables.mjs +92 -22
- package/engine/web-wikilinks.mjs +15 -31
- package/engine/wikilink-syntax.mjs +4 -1
- package/engine/wikilinks.mjs +12 -14
- package/hm3/item-fields.mjs +1 -1
- package/hm3/items.mjs +12 -45
- package/index.mjs +2 -14
- package/manifest.mjs +31 -4
- package/markdownlint-config.mjs +1 -1
- package/package.json +32 -11
- package/prettier-config.mjs +1 -1
- package/sohl/being-info.mjs +3 -145
- package/sohl/index.mjs +1 -1
- package/sohl/infobox.mjs +2 -1
- package/sohl/item-builders.mjs +3 -27
- package/sohl/item-fields.mjs +61 -22
- package/sohl/kb-passes.mjs +2 -2
- package/sohl/note-schemas.mjs +0 -8
- package/stage.mjs +19 -0
- package/types/config.d.mts +12 -3
- package/types/content-config.d.mts +15 -81
- package/types/engine/address-render.d.mts +13 -0
- package/types/engine/address.d.mts +2 -11
- package/types/engine/anchors.d.mts +3 -4
- package/types/engine/asset-bindings.d.mts +9 -2
- package/types/engine/base-compiler.d.mts +1 -2
- package/types/engine/being-measurements.d.mts +16 -0
- package/types/engine/calendar-choice.d.mts +14 -0
- package/types/engine/calendar-format.d.mts +33 -0
- package/types/engine/calendar-human.d.mts +4 -0
- package/types/engine/calendar-notes.d.mts +5 -3
- package/types/engine/calendars.d.mts +5 -3
- package/types/engine/code-fences.d.mts +6 -72
- package/types/engine/compile-corpus.d.mts +1 -3
- package/types/engine/content-admonitions.d.mts +24 -0
- package/types/engine/content-captions.d.mts +45 -0
- package/types/engine/content-footnotes.d.mts +10 -0
- package/types/engine/content-format-check.d.mts +17 -4
- package/types/engine/content-format.d.mts +24 -11
- package/types/engine/content-icons.d.mts +14 -23
- package/types/engine/content-images.d.mts +2 -2
- package/types/engine/content-index.d.mts +9 -2
- package/types/engine/content-links.d.mts +3 -5
- package/types/engine/content-tables.d.mts +11 -140
- package/types/engine/culture-choice.d.mts +14 -0
- package/types/engine/date-conversion.d.mts +4 -0
- package/types/engine/declaration-check.d.mts +24 -0
- package/types/engine/docs-checks.d.mts +28 -0
- package/types/engine/documentation-links.d.mts +7 -0
- package/types/engine/exported-scene.d.mts +6 -2
- package/types/engine/extension-attributes.d.mts +14 -0
- package/types/engine/field-spec.d.mts +9 -0
- package/types/engine/format-generated.d.mts +7 -0
- package/types/engine/frontmatter.d.mts +14 -4
- package/types/engine/generate.d.mts +1 -1
- package/types/engine/helpers.d.mts +37 -92
- package/types/engine/homepage.d.mts +2 -3
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +3 -1
- package/types/engine/infobox.d.mts +17 -6
- package/types/engine/item-compiler.d.mts +4 -4
- package/types/engine/item-docs.d.mts +4 -3
- package/types/engine/journals.d.mts +10 -7
- package/types/engine/map-graphviz.d.mts +1 -1
- package/types/engine/markdown-expressions.d.mts +17 -0
- package/types/engine/note-dates.d.mts +47 -1
- package/types/engine/note-package.d.mts +3 -19
- package/types/engine/note-state.d.mts +2 -1
- package/types/engine/note-vocabulary.d.mts +9 -0
- package/types/engine/number-words.d.mts +4 -0
- package/types/engine/office-holders.d.mts +14 -0
- package/types/engine/pack-config.d.mts +1 -1
- package/types/engine/packages.d.mts +3 -2
- package/types/engine/pdf-images.d.mts +38 -0
- package/types/engine/populations.d.mts +2 -2
- package/types/engine/project-init.d.mts +17 -0
- package/types/engine/prose-config.d.mts +1 -1
- package/types/engine/prose-lint.d.mts +3 -2
- package/types/engine/readability-lint.d.mts +22 -0
- package/types/engine/reckoning-markers.d.mts +13 -26
- package/types/engine/schema-check.d.mts +3 -3
- package/types/engine/site-build.d.mts +38 -28
- package/types/engine/site-config.d.mts +2 -2
- package/types/engine/site-index.d.mts +4 -0
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/social-ties.d.mts +8 -2
- package/types/engine/sql-tables.d.mts +13 -2
- package/types/engine/web-wikilinks.d.mts +2 -1
- package/types/engine/wikilinks.d.mts +7 -1
- package/types/hm3/items.d.mts +5 -6
- package/types/labels.d.mts +40 -0
- package/types/manifest.d.mts +3 -3
- package/types/sohl/actors.d.mts +2 -2
- package/types/sohl/being-info.d.mts +3 -43
- package/types/sohl/index.d.mts +1 -1
- package/types/sohl/infobox.d.mts +5 -2
- package/types/sohl/item-fields.d.mts +4 -13
- package/types/stage.d.mts +9 -0
- package/CONTENT.md +0 -3239
package/README.md
CHANGED
|
@@ -1,687 +1,48 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
-
The shared toolchain for building and
|
|
4
|
-
package** — its compendium content and the parts Foundry loads besides.
|
|
3
|
+
The shared toolchain for building HeroicLands Foundry systems and modules. It compiles Markdown content into a searchable index, Foundry compendiums, website pages, and a book; it also assembles manifests, assets, bundles, and release archives.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
## Start here
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
| ------------- | ------------------------------------------------------------ | -------------------------------------------------------------- |
|
|
10
|
-
| **content** | `assets/content/**` | compendium packs, site content, content index |
|
|
11
|
-
| **packaging** | `lang/`, `styles/`, `src/`, `assets/`, the manifest template | `system.json` / `module.json`, styles, bundle, release archive |
|
|
7
|
+
Use Node 24 or newer and npm. Initialize a project before it has a `package.json`, or add the package to an existing repository:
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
declares itself with.
|
|
16
|
-
|
|
17
|
-
Editor navigation for content projects is provided by
|
|
18
|
-
[`@heroiclands/content-language-server`](https://github.com/HeroicLands/content-language-server).
|
|
19
|
-
|
|
20
|
-
## Install
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
npm install -D @heroiclands/package-build
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## What it covers
|
|
27
|
-
|
|
28
|
-
The whole of assemble → validate → ship, one subpath each:
|
|
29
|
-
|
|
30
|
-
- **`manifest`** — the Foundry package manifest, `system.json` or `module.json`:
|
|
31
|
-
read the repository's template, stamp the version and the four release
|
|
32
|
-
addresses, write it into the stage. The artifact is inferred from the
|
|
33
|
-
template's name, and every address is derived from `package.json`'s
|
|
34
|
-
`repository` — nothing is transcribed.
|
|
35
|
-
- **`stage`** — assembling the build stage and clearing it away again. A listed
|
|
36
|
-
asset path that does not exist **fails the build** rather than shipping a
|
|
37
|
-
package that quietly lacks its localization or its templates, and the whole
|
|
38
|
-
list is checked before anything is copied, so a bad list leaves no
|
|
39
|
-
half-populated stage.
|
|
40
|
-
- **`lang`** — what a shippable Foundry localization file must satisfy: it
|
|
41
|
-
parses, its top level is an object, no key is both a leaf and a dotted prefix
|
|
42
|
-
of another, placeholders are single-braced, and key segments carry no data.
|
|
43
|
-
- **`coverage`** — whether the keys a package references and the keys it
|
|
44
|
-
declares are the same set. A referenced key that is missing renders to a
|
|
45
|
-
player as its own raw key string, so it fails the run; a declared key nothing
|
|
46
|
-
references is reported and does not.
|
|
47
|
-
- **`templates`** — the opposite question, which coverage cannot ask: whether
|
|
48
|
-
the markup's user-visible text goes through localization at all, rather than
|
|
49
|
-
sitting in the file in English — and whether each template still compiles once
|
|
50
|
-
it does.
|
|
51
|
-
- **`bundle`** — whether the manifest agrees with the file it points at.
|
|
52
|
-
Declared under `"esmodules"` the bundle must parse as a module; declared under
|
|
53
|
-
`"scripts"` it must declare **nothing** at top level, because every top-level
|
|
54
|
-
declaration in a classic script is a global lexical binding and one colliding
|
|
55
|
-
with a non-configurable `window` property throws at parse time.
|
|
56
|
-
- **`release`** — the two assets a GitHub Release carries, `<artifact>.zip` and
|
|
57
|
-
the manifest beside it. Waits for the archive to be _written_, not merely
|
|
58
|
-
finalized.
|
|
59
|
-
- **`deploy`** — installing a staged package into a Foundry data directory, over
|
|
60
|
-
a local copy or SFTP. Always a staged, atomic swap: a running Foundry holds
|
|
61
|
-
its LevelDB packs open, and replacing them in place leaves a directory LevelDB
|
|
62
|
-
"repairs" to zero.
|
|
63
|
-
- **`container`** — running what was just deployed. The Foundry data root a
|
|
64
|
-
deploy writes into is the directory a container mounts, so serving the result
|
|
65
|
-
is the next step from one variable rather than a second configuration.
|
|
66
|
-
- **`e2e`** — a disposable world, seeded with a Gamemaster whose password is
|
|
67
|
-
known, served until it is genuinely **active**, and a suite driven against it.
|
|
68
|
-
What the suite _is_ stays the repository's.
|
|
69
|
-
- **`text`** — locating a literal inside a file, so a finding names the line and
|
|
70
|
-
column it is about.
|
|
71
|
-
|
|
72
|
-
## Configure
|
|
73
|
-
|
|
74
|
-
A repository declares its build in **one** file — `package-build.config.yaml`.
|
|
75
|
-
The content half reads its top level; the packaging half takes its settings from
|
|
76
|
-
the `packageBuild:` section:
|
|
77
|
-
|
|
78
|
-
```yaml
|
|
79
|
-
# Read from the top level, not restated below.
|
|
80
|
-
packageKind: modules
|
|
81
|
-
foundryPackage: sohl-thalorna
|
|
82
|
-
|
|
83
|
-
packageBuild:
|
|
84
|
-
# Where the package is assembled. Every `to:` below is relative to it, so a
|
|
85
|
-
# table reads `lang`, not `build/stage/lang`.
|
|
86
|
-
stageDir: build/stage
|
|
87
|
-
|
|
88
|
-
assets:
|
|
89
|
-
- { from: lang, to: lang }
|
|
90
|
-
- { from: assets/icons, to: assets/icons }
|
|
91
|
-
- { from: LICENSE.md, to: LICENSE.md }
|
|
92
|
-
|
|
93
|
-
# Optional. A module exporting `transform(sourcePath) -> string | null`,
|
|
94
|
-
# applied to every staged file — `null` copies it verbatim. This is the one
|
|
95
|
-
# genuine piece of code in staging, and it stays the repository's: SoHL
|
|
96
|
-
# rewrites each SVG's hard-coded fill so icons follow the Foundry theme.
|
|
97
|
-
assetTransform: ./utils/svg-theme.mjs
|
|
98
|
-
|
|
99
|
-
clean:
|
|
100
|
-
# Beyond the conventional build artifacts, which the library already knows.
|
|
101
|
-
extra: [coverage]
|
|
102
|
-
|
|
103
|
-
lang:
|
|
104
|
-
sources: lang/*.json
|
|
105
|
-
# Printed after a `lang check` failure — where this repository documents its
|
|
106
|
-
# key rules.
|
|
107
|
-
help: See kb/dev-docs/reference/localization-keys.md.
|
|
108
|
-
|
|
109
|
-
# `lang coverage` and `lang hardcoded`. Every one of these defaults to the
|
|
110
|
-
# conventional layout, so a repository that follows it states none of them.
|
|
111
|
-
primary: lang/en.json # the file coverage is measured against
|
|
112
|
-
scripts: src/**/*.{ts,mjs} # scanned for key references
|
|
113
|
-
templates: templates/**/*.hbs # scanned for references and for English
|
|
114
|
-
|
|
115
|
-
# Only when the package references a root its file does not yet declare;
|
|
116
|
-
# otherwise the roots are read from the file's own keys.
|
|
117
|
-
keyRoots: [SOHL, TYPES, TYPE]
|
|
118
|
-
|
|
119
|
-
# Optional. A module exporting `references(context) -> ReferenceSet`,
|
|
120
|
-
# contributing the keys only this repository's conventions can find. SoHL's
|
|
121
|
-
# `defineType(prefix, def)` mints one key per member of an enum by a rule of
|
|
122
|
-
# its own; no shared guard can know it, and no repository can compare the
|
|
123
|
-
# result against the file. See "Contributing generated keys" below.
|
|
124
|
-
references: ./utils/lang-references.mjs
|
|
125
|
-
|
|
126
|
-
# Keys reached in a way no scan can see, exempt from the unreferenced
|
|
127
|
-
# advisory. The reason is required: it is a claim a reviewer has to be able
|
|
128
|
-
# to check, and the honest fix for an unreferenced key is to delete it.
|
|
129
|
-
retained:
|
|
130
|
-
- prefix: SOHL.Gear.Action.
|
|
131
|
-
reason: Titles built as `${titlePrefix}.${shortcode}` — the prefix is a parameter.
|
|
132
|
-
|
|
133
|
-
# Template literals that are deliberately not localization keys. The escape
|
|
134
|
-
# hatch, not the rule.
|
|
135
|
-
allow:
|
|
136
|
-
- literal: item.system.code === 'pyrn'
|
|
137
|
-
reason: An expression example shown as a placeholder — code, not prose.
|
|
138
|
-
|
|
139
|
-
deploy:
|
|
140
|
-
# Prefix of the shared SFTP override variables. Default `SOHL`.
|
|
141
|
-
envPrefix: SOHL
|
|
142
|
-
|
|
143
|
-
bundle:
|
|
144
|
-
# The file Foundry loads, as the manifest spells it, relative to the stage.
|
|
145
|
-
# Defaults to `<packageId>.mjs`; state it only when the bundler emits
|
|
146
|
-
# something else. Deliberately not read back out of the manifest — a value
|
|
147
|
-
# taken from there would agree with itself by construction, and `bundle
|
|
148
|
-
# check` asks whether the manifest declares this file correctly.
|
|
149
|
-
entry: sohl.mjs
|
|
150
|
-
|
|
151
|
-
# Optional. A module exporting `flags(config)` returning namespaced Foundry
|
|
152
|
-
# flags the repository has to *compute* — an address that only exists once
|
|
153
|
-
# the content tree has been walked, say. Merged over any declared below.
|
|
154
|
-
manifestFlags: ./utils/manifest-flags.mjs
|
|
155
|
-
|
|
156
|
-
# The Foundry package manifest. Emitted as declared, so a key Foundry adds in
|
|
157
|
-
# a later version needs no release of this package.
|
|
158
|
-
manifest:
|
|
159
|
-
title: Song of Heroic Lands
|
|
160
|
-
description: <p>…</p>
|
|
161
|
-
license: LICENSE.md
|
|
162
|
-
readme: README.md
|
|
163
|
-
authors:
|
|
164
|
-
- { name: Toasty, discord: "toasty#8538" }
|
|
165
|
-
esmodules: [sohl.js]
|
|
166
|
-
styles: [css/sohl.css]
|
|
167
|
-
languages:
|
|
168
|
-
- { lang: en, name: English, path: lang/en.json }
|
|
169
|
-
documentTypes:
|
|
170
|
-
Item:
|
|
171
|
-
skill: { htmlFields: [notes, docHtml] }
|
|
172
|
-
packFolders:
|
|
173
|
-
- name: Song of Heroic Lands
|
|
174
|
-
sorting: m
|
|
175
|
-
color: "#094fcb"
|
|
176
|
-
packs: [items, journals, actors, macros, scenes, adventures]
|
|
177
|
-
media:
|
|
178
|
-
- { type: logo, url: systems/sohl/assets/ui/logo.webp }
|
|
179
|
-
socket: true
|
|
180
|
-
grid: { distance: 5, units: ft }
|
|
181
|
-
primaryTokenAttribute: health
|
|
182
|
-
|
|
183
|
-
# Running the deployed package in a Foundry container. The four conventional
|
|
184
|
-
# stages — dev, qa, prod, test — need no entry at all: their data root is
|
|
185
|
-
# `FOUNDRYVTT_<STAGE>_DATA`, the same variable `deploy` writes into, and their
|
|
186
|
-
# ports are conventional. Declare a stage only when it is genuinely yours.
|
|
187
|
-
container:
|
|
188
|
-
# Optional. The container name, minus the stage, which is always appended:
|
|
189
|
-
# this declares `heroiclands-foundry-test`, not `sohl-foundry-test`.
|
|
190
|
-
# Foundry binds a signed licence to the container hostname, so packages that
|
|
191
|
-
# declare the same name are one instance covered by one signature. Defaults
|
|
192
|
-
# to the package id, which keeps every package in its own container.
|
|
193
|
-
name: heroiclands-foundry
|
|
194
|
-
|
|
195
|
-
stages:
|
|
196
|
-
# SoHL keeps the previous, pre-TypeScript system on an older Foundry.
|
|
197
|
-
# An empty `world` declares "never auto-launch" — it is managed by hand.
|
|
198
|
-
leg: { port: 30000, world: "", version: "12.331" }
|
|
199
|
-
|
|
200
|
-
e2e:
|
|
201
|
-
# The one thing the harness does not own. Standing Foundry up and seeding a
|
|
202
|
-
# world is nobody's local problem; what runs against it is entirely yours.
|
|
203
|
-
suite:
|
|
204
|
-
run: [npx, cypress, run]
|
|
205
|
-
open: [npx, cypress, open]
|
|
206
|
-
|
|
207
|
-
# Where that suite writes its results. Optional, and what it buys is the
|
|
208
|
-
# difference between "the suite ran and passed" and "the suite did not
|
|
209
|
-
# run" — a headless run that writes nothing here is reported as a failure.
|
|
210
|
-
results: [cypress/results]
|
|
211
|
-
|
|
212
|
-
# What the fast loop can rebuild, in the order it must be built: the
|
|
213
|
-
# bundler empties the stage, so it goes first. `recreate` marks a target
|
|
214
|
-
# whose output Foundry reads once, at world launch.
|
|
215
|
-
build:
|
|
216
|
-
code: build:code
|
|
217
|
-
assets: build:assets
|
|
218
|
-
db: build:db
|
|
219
|
-
system: { script: build:system, recreate: true }
|
|
220
|
-
|
|
221
|
-
# Everything here is optional — it derives from the package id otherwise.
|
|
222
|
-
world: { id: sohl-e2e, title: SoHL E2E }
|
|
223
|
-
gm: { name: Gamemaster, password: sohl-e2e }
|
|
224
|
-
|
|
225
|
-
# Extra world collections, compiled from directories of JSON documents.
|
|
226
|
-
documents:
|
|
227
|
-
actors: cypress/fixtures/actors
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### The manifest is generated, not stamped
|
|
231
|
-
|
|
232
|
-
`package-build manifest` writes `system.json` / `module.json` into the stage.
|
|
233
|
-
**There is no template file.** A hand-authored JSON manifest is one the
|
|
234
|
-
build stamped a few fields into — the one build input still written by hand, per
|
|
235
|
-
repository, with no schema and nothing checking it. It also declared facts the
|
|
236
|
-
configuration already declared: the pack list twice, in two formats, with
|
|
237
|
-
nothing checking the pairs agreed.
|
|
238
|
-
|
|
239
|
-
Three kinds of key end up in the result:
|
|
240
|
-
|
|
241
|
-
| Kind | Where it comes from |
|
|
242
|
-
| ------------ | ------------------------------------------------------------------------------------------------- |
|
|
243
|
-
| **Declared** | `packageBuild.manifest`, emitted unchanged |
|
|
244
|
-
| **Derived** | `id`, `version`, `url`, `bugs`, `manifest`, `download`, `compatibility`, `relationships`, `packs` |
|
|
245
|
-
| **Computed** | namespaced `flags` from `manifestFlags`, merged over any declared |
|
|
246
|
-
|
|
247
|
-
**Declaring a derived key is an error, not an override.** An authored `version`
|
|
248
|
-
would look authoritative, sit there unread, and disagree with the shipped
|
|
249
|
-
package forever; the build says so, naming the key and where the value actually
|
|
250
|
-
comes from.
|
|
251
|
-
|
|
252
|
-
`packs` is derived from the **one** pack list at the top level of
|
|
253
|
-
`package-build.config.yaml` — each entry's `label`, `type`, `name` and
|
|
254
|
-
`private`, plus a `system` from `stats.systemId` and a `path` of
|
|
255
|
-
`packs/<name>`. Companions are flattened in, because Foundry sees no difference:
|
|
256
|
-
a companion is only a pack written by another pass rather than one of its own.
|
|
257
|
-
Give each pack the `label` you want Foundry to show.
|
|
258
|
-
|
|
259
|
-
#### `packFolders` is checked against the packs you ship
|
|
260
|
-
|
|
261
|
-
`packFolders` is the one **declared** key that names something the build
|
|
262
|
-
**derives**. Every other declared key states a fact about the package (`title`,
|
|
263
|
-
`socket`, `grid`) or addresses a staged file (`esmodules`, `styles`,
|
|
264
|
-
`languages`) — a staged file being a different relation, answered against the
|
|
265
|
-
stage rather than against configuration. So it is the one place a declaration
|
|
266
|
-
can quietly go stale against a value the build already computed, and it did:
|
|
267
|
-
`HarnMaster-3-FoundryVTT` shipped a folder naming four packs, three of which had
|
|
268
|
-
not existed since its compendium was consolidated, while `items` — 1,577 of
|
|
269
|
-
1,597 documents — sat in no folder at all, with the build reporting nothing.
|
|
270
|
-
|
|
271
|
-
`package-build manifest` now compares the two, descending through nested folders
|
|
272
|
-
(Foundry allows three levels), and reports in the usual
|
|
273
|
-
`file:line:column: severity: message` form:
|
|
274
|
-
|
|
275
|
-
| Finding | Severity | Why |
|
|
276
|
-
| ----------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
277
|
-
| a folder names a pack the package does not ship | **error** | Foundry silently skips a name it cannot resolve, so the declaration does nothing; no arrangement intends it |
|
|
278
|
-
| a pack no folder names | **warning** | legal, and a root-level pack can be deliberate — but a package that declared a folder rarely meant to leave one out |
|
|
279
|
-
| no `packFolders` declared at all | nothing | everything at the root is an arrangement, not an omission |
|
|
280
|
-
|
|
281
|
-
An error **stops the write**: a manifest already known to describe packs that do
|
|
282
|
-
not exist should not reach the stage, where the next command would deploy it.
|
|
283
|
-
|
|
284
|
-
```text
|
|
285
|
-
package-build.config.yaml:164:23: error: packFolders: folder "HârnMaster 3 System" names pack "character", which this package does not ship (packs: items, system-help)
|
|
286
|
-
package-build.config.yaml:162:13: warning: packFolders: pack "items" is named by no folder, so it ships outside every folder this package declares
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
`compatibility` and `relationships` are read from the **top level** of the
|
|
290
|
-
shared configuration, not from this section — content-build consumes them
|
|
291
|
-
(`supportedCoreVersion`, and a module'''s `stats.systemVersion`) and the
|
|
292
|
-
dependency runs one way.
|
|
293
|
-
|
|
294
|
-
**A relationship's build-only keys are not published.** `relationships` is the
|
|
295
|
-
one block with a second reader, and content-build takes directives there:
|
|
296
|
-
`itemCatalog: true` on a dependency selects that package's Item packs as a
|
|
297
|
-
resolution source for the actors pass. That says how the package is _built_, not
|
|
298
|
-
what it depends on, so it is dropped on the way into the manifest — Foundry's
|
|
299
|
-
relationship schema does not define it, and someone reading a published manifest
|
|
300
|
-
cannot tell a build directive from a declaration. Every other key is copied,
|
|
301
|
-
including one this package has never heard of.
|
|
302
|
-
|
|
303
|
-
**Why one file and not two.** Two of the values this package needs —
|
|
304
|
-
`packageKind` and `foundryPackage` — are already declared for `content-build`. A
|
|
305
|
-
second config file would restate them, which is two places for one fact; that is
|
|
306
|
-
exactly what every consumer's `push-stage.mjs` did, hard-coding
|
|
307
|
-
`packageKind: "systems"` and `packageId: "sohl"` beside a configuration that
|
|
308
|
-
already said both.
|
|
309
|
-
|
|
310
|
-
`content-build` checks only that `packageBuild:` is a mapping and hands it back
|
|
311
|
-
frozen. Everything inside it is validated here, so neither package learns the
|
|
312
|
-
other's schema — they split by input, and the dependency runs one way.
|
|
313
|
-
|
|
314
|
-
**What is derived, not stated:**
|
|
315
|
-
|
|
316
|
-
| Field | Derived from |
|
|
317
|
-
| -------------------------- | --------------------------------------------------------------------------- |
|
|
318
|
-
| the repository root | the configuration file's own location |
|
|
319
|
-
| `packageKind`, `packageId` | the shared configuration's top level |
|
|
320
|
-
| the release artifact | `packageKind` — a system ships `system.json`, a module `module.json` |
|
|
321
|
-
| the bundle entry | `packageId` — `<id>.mjs`, unless `packageBuild.bundle.entry` says otherwise |
|
|
322
|
-
|
|
323
|
-
**`packageKind: documentation` has no packaging half.** That kind publishes a
|
|
324
|
-
website and a book and installs into no Foundry data directory, so there is no
|
|
325
|
-
artifact to name and no id to derive one from: `manifest` refuses rather than
|
|
326
|
-
writing a manifest for a package Foundry never installs, and the whole of such a
|
|
327
|
-
package's build is `content-build`. See
|
|
328
|
-
[A package that compiles nothing](CONTENT.md#a-package-that-compiles-nothing).
|
|
329
|
-
|
|
330
|
-
## Command line
|
|
331
|
-
|
|
332
|
-
```
|
|
333
|
-
npx package-build clean [--distclean]
|
|
334
|
-
npx package-build assets
|
|
335
|
-
npx package-build manifest
|
|
336
|
-
npx package-build lang check
|
|
337
|
-
npx package-build lang coverage [--unused]
|
|
338
|
-
npx package-build lang hardcoded
|
|
339
|
-
npx package-build bundle check
|
|
340
|
-
npx package-build release [--no-pdf]
|
|
341
|
-
npx package-build site-root [--out <dir>]
|
|
342
|
-
npx package-build deploy <stage>
|
|
343
|
-
npx package-build container <stage> <start|stop|restart|recreate|rm|status|logs|pull>
|
|
344
|
-
npx package-build e2e <seed|run|open|fast|sweep>
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
`package-build release` writes `<artifact>.zip` and `<artifact>.json` into
|
|
348
|
-
`build/dist`, the content index the manifest advertises, and — when the package
|
|
349
|
-
publishes a content tree — the book that tree compiles to, as
|
|
350
|
-
`<artifact>-<version>.pdf`.
|
|
351
|
-
|
|
352
|
-
`package-build site-root` finishes a site build. Hugo renders into
|
|
353
|
-
`build/site/<contentPackage>/`; this writes the deployment's `_headers` beside
|
|
354
|
-
that, and indexes the rendered pages for search with Pagefind into
|
|
355
|
-
`build/site/<contentPackage>/pagefind/`, deployed with the pages and served at
|
|
356
|
-
`/<contentPackage>/pagefind/`. The dependency is this package's, declared once,
|
|
357
|
-
so every site — and every local build — gets an index without the repository
|
|
358
|
-
adding one; `site.search: false` in `package-build.config.yaml` turns it off.
|
|
359
|
-
|
|
360
|
-
The book is **built by default and never fatal**. A package publishing only a
|
|
361
|
-
homepage, one with no `pdf:` block, and one with no content tree each build
|
|
362
|
-
none and say so; `--no-pdf` is for a release that has a tree and does not want
|
|
363
|
-
the book. What the book is and how it is configured is `CONTENT.md`, under
|
|
364
|
-
_Publishing a book_ — it is a content surface, and the content half owns it.
|
|
365
|
-
|
|
366
|
-
Wrapped as npm scripts — SoHL spells them:
|
|
367
|
-
|
|
368
|
-
```json
|
|
369
|
-
{
|
|
370
|
-
"clean": "package-build clean",
|
|
371
|
-
"distclean": "package-build clean --distclean",
|
|
372
|
-
"build:assets": "package-build assets",
|
|
373
|
-
"lint:lang": "package-build lang check",
|
|
374
|
-
"lint:lang-coverage": "package-build lang coverage",
|
|
375
|
-
"lint:lang-hardcoded": "package-build lang hardcoded",
|
|
376
|
-
"lint:bundle-globals": "package-build bundle check",
|
|
377
|
-
"build:pack-release": "package-build release",
|
|
378
|
-
"push:qa": "package-build deploy qa",
|
|
379
|
-
"container:dev": "package-build container dev",
|
|
380
|
-
"e2e:full": "package-build e2e run",
|
|
381
|
-
"e2e:fast": "package-build e2e fast",
|
|
382
|
-
"e2e:sweep": "package-build e2e sweep"
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
**The shape of the surface.** A capability with a single operation is a bare
|
|
387
|
-
command (`clean`, `assets`, `manifest`, `release`, `deploy <stage>`); one with
|
|
388
|
-
more than a single operation takes a positional action, so a second can be added
|
|
389
|
-
without renaming the first (`lang check`, `bundle check`). Flat `lang:check`
|
|
390
|
-
names would make every operation a new top-level command and hide which ones
|
|
391
|
-
belong together.
|
|
392
|
-
|
|
393
|
-
**What the two localization guards ask.** They are deliberate opposites, and
|
|
394
|
-
neither can answer the other's question. `lang coverage` walks _key → file_, so
|
|
395
|
-
it is completely blind to a template that names no key whatsoever; `lang
|
|
396
|
-
hardcoded` walks _text → key_, and catches exactly that. Before the work that
|
|
397
|
-
prompted the second, Song of Heroic Lands had **516 hardcoded English literals
|
|
398
|
-
across 61 templates** — translating every key in `en.json` would have left every
|
|
399
|
-
one of them in English.
|
|
400
|
-
|
|
401
|
-
**Contributing generated keys.** `packageBuild.lang.references` names a module
|
|
402
|
-
exporting `references(context)`, called once with
|
|
403
|
-
`{ config, rootDir, roots, files }` — `files` being the scanned sources as
|
|
404
|
-
`{ path, text }`, already read, so the contributor sees exactly the text the
|
|
405
|
-
built-in scan saw. It returns a reference set:
|
|
406
|
-
|
|
407
|
-
```js
|
|
408
|
-
export function references({ files }) {
|
|
409
|
-
return {
|
|
410
|
-
keys: [
|
|
411
|
-
// `exact` when the key is minted whole, so keys sitting beneath it do
|
|
412
|
-
// not vouch for it; `origin` is the verb phrase the message reads with.
|
|
413
|
-
{
|
|
414
|
-
key: "SOHL.Skill.CODE.lore",
|
|
415
|
-
file: "src/utils/constants.ts",
|
|
416
|
-
line: 42,
|
|
417
|
-
exact: true,
|
|
418
|
-
origin: "defineType generates",
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
namespaces: ["SOHL.Skill.CODE"], // families whose leaves are never named
|
|
422
|
-
patterns: ["SOHL.Skill.*.label"], // shapes, `*` standing for one segment
|
|
423
|
-
findings: [], // whatever the contributor could not resolve
|
|
424
|
-
};
|
|
425
|
-
}
|
|
9
|
+
```bash
|
|
10
|
+
npm exec --package=@heroiclands/package-build -- package-build init my-package --kind modules --name my-package --title "My Package" --description "A content package." --author "Your Name" --license original
|
|
426
11
|
```
|
|
427
12
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
DataModel's `LOCALIZATION_PREFIXES`, the `FIELDS.<field>.label` / `.hint` keys Foundry
|
|
431
|
-
mints off one. A contributor is for what only the repository knows.
|
|
432
|
-
|
|
433
|
-
**What `bundle check` checks.** Three ways a package builds successfully and
|
|
434
|
-
still does not load, none of which a bundler can see, because each is a
|
|
435
|
-
disagreement between two files rather than a fault in either:
|
|
436
|
-
|
|
437
|
-
| The manifest says | What Foundry does |
|
|
438
|
-
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
439
|
-
| the entry under both `esmodules` and `scripts` | loads the bundle twice |
|
|
440
|
-
| the entry under neither | never loads it at all |
|
|
441
|
-
| the entry under `esmodules`, but the file only parses as a classic script | fails at load, naming whichever `import` came first and nothing about the manifest |
|
|
442
|
-
|
|
443
|
-
It reads both files from the stage, because the stage is what ships — checking
|
|
444
|
-
sources would answer for a package nobody installs. Findings are emitted as
|
|
445
|
-
`file:line:column: severity: message`, and the command exits non-zero when any
|
|
446
|
-
of them is an error.
|
|
447
|
-
|
|
448
|
-
**Why the CLI exists.** This package was library-only, so every consuming
|
|
449
|
-
repository wrote a wrapper script per job — six of them in the SoHL repository,
|
|
450
|
-
441 lines that between them contained no logic. `clean.mjs` was 47 lines that
|
|
451
|
-
computed a `repoRoot` from `import.meta.url`, read one flag, and made one call.
|
|
452
|
-
Every copy had drifted from its sibling in the other repositories, because
|
|
453
|
-
copies do: `clean.mjs` was 47 lines in one and 48 in another, `copy-assets.mjs`
|
|
454
|
-
71 and 76.
|
|
455
|
-
|
|
456
|
-
It is the same shape the configuration had before it became data — not logic,
|
|
457
|
-
but the boilerplate a code file needs in order to state a literal. The literals
|
|
458
|
-
moved into configuration; the boilerplate lives in the CLI, once.
|
|
459
|
-
|
|
460
|
-
`--version` and `--help` answer in a directory with no configuration at all.
|
|
461
|
-
Running an actual command resolves it, and fails loudly when it is missing.
|
|
462
|
-
|
|
463
|
-
## Standing Foundry up, and running a suite against it
|
|
464
|
-
|
|
465
|
-
A package that claims a `compatibility` range is making a promise, and a
|
|
466
|
-
promise is only defended if something exercises it. Until now exactly one
|
|
467
|
-
repository could: the container lifecycle, the world seeding and the browser
|
|
468
|
-
harness all lived in `SoHL/utils/`, so two of the three HeroicLands module
|
|
469
|
-
repositories declared a range nothing could test.
|
|
470
|
-
|
|
471
|
-
**The seam already existed.** `package-build deploy <stage>` installs a staged
|
|
472
|
-
package into `FOUNDRYVTT_<STAGE>_DATA`; a container mounts that same directory
|
|
473
|
-
at `/data` and serves it. Running Foundry against what was just deployed is the
|
|
474
|
-
next step from one variable, not a second configuration — so nothing about the
|
|
475
|
-
destination, the stage, or the package identity is restated.
|
|
476
|
-
|
|
477
|
-
### Which Foundry build a run pins
|
|
478
|
-
|
|
479
|
-
`compatibility.minimum` is the oldest Foundry a package claims to run on, and
|
|
480
|
-
that claim is what the suite exists to defend. Testing above the floor tests a
|
|
481
|
-
configuration no user is promised while leaving the promised one unverified: a
|
|
482
|
-
regression that breaks the floor but works on a newer build passes in silence.
|
|
483
|
-
|
|
484
|
-
So **the end-to-end stage is pinned to `compatibility.minimum` itself**, read
|
|
485
|
-
from the top level of the shared configuration. The claim and the evidence are
|
|
486
|
-
the same number and cannot drift apart, and raising the pin is what it should
|
|
487
|
-
be — a decision to raise the supported floor, made by editing the claim.
|
|
488
|
-
|
|
489
|
-
`FOUNDRYVTT_<STAGE>_VERSION` still wins, so a contributor can sit on another
|
|
490
|
-
build without touching committed configuration. A floor that names no build
|
|
491
|
-
(`"14"`) cannot pin one; the run floats on the major tag, visibly, rather than
|
|
492
|
-
pretending to a precision it lacks.
|
|
493
|
-
|
|
494
|
-
### The sweep
|
|
495
|
-
|
|
496
|
-
Routine runs go against the floor, which leaves the other direction untested: a
|
|
497
|
-
new Foundry release can break a package and nothing notices until a user does.
|
|
498
|
-
|
|
499
|
-
```
|
|
500
|
-
npx package-build e2e sweep 14.367
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
That is the full suite — reseeded world and all — against a build the
|
|
504
|
-
repository does not pin. It must be the full path rather than the fast one: a
|
|
505
|
-
seeded world is stamped with the build that created it, and Foundry refuses to
|
|
506
|
-
auto-launch a world stamped by another. It takes the build as an argument and
|
|
507
|
-
has **no default**, because the product of a sweep is a citable result ("the
|
|
508
|
-
full suite passed on 14.367") and "the newest release" is not a constant any
|
|
509
|
-
repository can hold without rotting.
|
|
510
|
-
|
|
511
|
-
A green sweep is what licenses moving `compatibility.verified` to that build. A
|
|
512
|
-
red one is the early warning the sweep exists to produce.
|
|
513
|
-
|
|
514
|
-
### A run that executed nothing is not a pass
|
|
515
|
-
|
|
516
|
-
Because a sweep is evidence, the exit status of the suite is not enough on its
|
|
517
|
-
own to call a run green. Every way of stopping a runner before it starts — a
|
|
518
|
-
corrupt install, a missing browser, a killed process, an `npm ci` racing the
|
|
519
|
-
run and taking `node_modules` with it — produces a run that executed nothing,
|
|
520
|
-
and a harness that reports that as 0 makes the evidence unfalsifiable in the
|
|
521
|
-
one direction that matters.
|
|
522
|
-
|
|
523
|
-
So the suite is bracketed rather than trusted:
|
|
524
|
-
|
|
525
|
-
| Check | What it catches |
|
|
526
|
-
| ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
527
|
-
| Before, the command resolves | The runner is not installed. The error names it, before a container is stood up and a world seeded. |
|
|
528
|
-
| A runner is read through | `npx cypress run` is checked for **`cypress`**, not `npx` — `npx` is never missing, so it answers nothing. |
|
|
529
|
-
| After, the command still does | The runner went away mid-run. That is a run that cannot have finished, whatever it exited with. |
|
|
530
|
-
| After, results are newer | The suite started and produced nothing. Requires `packageBuild.e2e.results`. |
|
|
531
|
-
|
|
532
|
-
`results` names one path or a list of them, relative to the repository root.
|
|
533
|
-
Existence is not the test — a directory the _previous_ run left behind exists,
|
|
534
|
-
and reading that as evidence would make the check agree with exactly what it
|
|
535
|
-
was built to catch. What counts is a file modified since the spawn.
|
|
536
|
-
|
|
537
|
-
Two things it deliberately does not do. It only ever makes a verdict **worse**:
|
|
538
|
-
a suite that failed keeps its own status, so there is no second path by which
|
|
539
|
-
the harness can report a result that did not happen. And it does not apply to
|
|
540
|
-
`open`, where a person decides what to execute and when to close the runner —
|
|
541
|
-
"it wrote no results" describes that session rather than faulting it.
|
|
542
|
-
|
|
543
|
-
### Waiting for a world, not for a port
|
|
544
|
-
|
|
545
|
-
Foundry answers on its port long before a world is serving. A suite started at
|
|
546
|
-
that moment fails every spec for no visible reason, which is the single most
|
|
547
|
-
expensive way an integration harness wastes an afternoon. So the wait is for
|
|
548
|
-
the join screen — the form a world renders only once it is **active** — and a
|
|
549
|
-
licence failure, which never recovers, is read out of the container log and
|
|
550
|
-
reported at once rather than after a three-minute timeout that explains
|
|
551
|
-
nothing.
|
|
552
|
-
|
|
553
|
-
### The fast loop
|
|
554
|
-
|
|
555
|
-
```
|
|
556
|
-
npx package-build e2e fast -- --spec cypress/e2e/skill.cy.js
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
Rebuild what changed, redeploy, cycle the world, wait for it, re-run. It is one
|
|
560
|
-
command because every step of it has a quiet failure mode:
|
|
561
|
-
|
|
562
|
-
| Step | What goes wrong by hand |
|
|
563
|
-
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
564
|
-
| Build | The container serves the **built** package, not the source tree. |
|
|
565
|
-
| Order | A bundler empties the stage, so building it after the asset and pack passes silently discards them — and the deploy is a mirror. |
|
|
566
|
-
| Manifest | Read once at world launch, so it needs a container **recreate**, not a restart. A target declaring `recreate: true` forces one. |
|
|
567
|
-
| Restart | A running Foundry holds its packs open, so a content change is invisible until the world reopens them. |
|
|
568
|
-
| Stale lock | A container that died holding the data-root lock makes every later boot fail with a message that names no owner. |
|
|
569
|
-
| Readiness | `docker start` returns long before Foundry serves. |
|
|
570
|
-
| The suite | Editor and agent shells export `ELECTRON_RUN_AS_NODE`, which makes an Electron runner start as plain Node and die on its own flags. |
|
|
571
|
-
|
|
572
|
-
`--build` takes a comma-separated list of declared targets, `all`, or `none`;
|
|
573
|
-
an unrecognised one fails fast rather than half-deploying. `--recreate` forces
|
|
574
|
-
a recreate, `--no-run` stops once the environment is current, and everything
|
|
575
|
-
else — including anything after a bare `--` — is handed to the suite verbatim.
|
|
576
|
-
|
|
577
|
-
### What the seeded world holds
|
|
578
|
-
|
|
579
|
-
A world Foundry will launch without a migration prompt: `world.json`, and one
|
|
580
|
-
Gamemaster whose password is known, so a spec can log in deterministically. It
|
|
581
|
-
also carries one **active** scene, because an empty world auto-starts Foundry's
|
|
582
|
-
welcome tour (whose callout overlays sheets) and a world with no active scene
|
|
583
|
-
has no ready canvas.
|
|
584
|
-
|
|
585
|
-
For a **module** package it additionally writes `core.moduleConfiguration`,
|
|
586
|
-
switching the module on. A system is the world's own and `world.json` names it;
|
|
587
|
-
a module is not, and without that setting a module repository would stand its
|
|
588
|
-
suite up against a world that never loaded the thing under test.
|
|
589
|
-
|
|
590
|
-
Anything else — which actors, which journals — is the repository's, declared as
|
|
591
|
-
`packageBuild.e2e.documents` and compiled in beside the built-ins.
|
|
592
|
-
|
|
593
|
-
The world directory is wiped and rewritten on every seed. That is what makes a
|
|
594
|
-
run repeatable, and it is why `FOUNDRYVTT_<E2E_STAGE>_DATA` must be a separate,
|
|
595
|
-
empty directory: pointing it at a working stage would let the seed delete
|
|
596
|
-
worlds there, and would make the image reuse that stage's `Config/license.json`
|
|
597
|
-
in place of the key dedicated to the suite.
|
|
598
|
-
|
|
599
|
-
## Design
|
|
600
|
-
|
|
601
|
-
**The rules are pure, and I/O is confined to functions named for it.** A rule
|
|
602
|
-
takes source text or data and returns findings or values; discovery and
|
|
603
|
-
reporting stay with the caller. Where a step genuinely has to touch disk or a
|
|
604
|
-
network it is an export named for what it does — `writeFoundryManifest`,
|
|
605
|
-
`stageAssets`, `packRelease`, `deployStage`.
|
|
606
|
-
|
|
607
|
-
That is what lets one rule set serve a `lint` script, a build step and a unit
|
|
608
|
-
test without any of them agreeing on how files are found or how findings are
|
|
609
|
-
printed — and it is what makes the rules testable at all, which the scripts they
|
|
610
|
-
were extracted from were not: each ran its work at import time and exported
|
|
611
|
-
nothing.
|
|
612
|
-
|
|
613
|
-
Findings carry the fields the shared diagnostic format takes (`line`, `column`,
|
|
614
|
-
`severity`, `message`) but never `file`, which only the caller knows. The format
|
|
615
|
-
itself is owned by `@heroiclands/content-build`'s `engine/diagnostics`, and is
|
|
616
|
-
not restated here.
|
|
617
|
-
|
|
618
|
-
```js
|
|
619
|
-
import { validateLangSource } from "@heroiclands/package-build/lang";
|
|
620
|
-
|
|
621
|
-
for (const file of globSync("lang/*.json")) {
|
|
622
|
-
for (const finding of validateLangSource(readFileSync(file, "utf8"))) {
|
|
623
|
-
reportDiagnostic({ file, ...finding });
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
## Tests
|
|
629
|
-
|
|
630
|
-
```
|
|
631
|
-
npm test
|
|
13
|
+
```bash
|
|
14
|
+
npm install -D @heroiclands/package-build
|
|
632
15
|
```
|
|
633
16
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
**Merging that publishes.** `changeset publish` puts the version on npm through
|
|
667
|
-
Trusted Publishing (OIDC — there is no `NPM_TOKEN`), tags the commit `v<version>`
|
|
668
|
-
and cuts the GitHub Release with the changelog section as its body. It publishes
|
|
669
|
-
only versions that are not already on the registry, so re-running it is a no-op;
|
|
670
|
-
`workflow_dispatch` on **Publish to npm** is the recovery path if a run fails
|
|
671
|
-
after versioning.
|
|
672
|
-
|
|
673
|
-
A changeset is also where a **raised dependency floor** gets recorded. 0.2.0 began
|
|
674
|
-
requiring `@heroiclands/content-build >= 0.15.0` and announced it nowhere; a
|
|
675
|
-
changeset is the place that now happens.
|
|
676
|
-
|
|
677
|
-
Below 1.0.0, `^0.x` never crosses a minor — a consumer on `^0.2.0` will not see
|
|
678
|
-
`0.3.0` until it bumps the pin deliberately, and Dependabot raises that as its own
|
|
679
|
-
pull request.
|
|
680
|
-
|
|
681
|
-
> After a successful publish, `npm view @heroiclands/package-build version` can
|
|
682
|
-
> report the _previous_ version for a minute or so. `dist-tags` is correct
|
|
683
|
-
> immediately, and is what the workflow prints.
|
|
684
|
-
|
|
685
|
-
## Licence
|
|
686
|
-
|
|
687
|
-
GPL-3.0-or-later.
|
|
17
|
+
The generated project includes a homepage, website and book targets, npm scripts, and a local `.gitignore`. [Getting started](docs/getting-started.md) routes you to a [system](docs/guides/system-project.md), [module](docs/guides/module-project.md), or [content authoring](docs/authoring/first-note.md) path. The [content-language-server](https://github.com/HeroicLands/content-language-server) provides editor navigation and completion.
|
|
18
|
+
|
|
19
|
+
## Documentation
|
|
20
|
+
|
|
21
|
+
The documentation lives in the Markdown files in this repository. Start with [the documentation home](docs/index.md).
|
|
22
|
+
|
|
23
|
+
| Topic | Guide |
|
|
24
|
+
| ------------------------------------------------- | ------------------------------------------------------------ |
|
|
25
|
+
| Documentation landing | [Home](docs/index.md) |
|
|
26
|
+
| Project layout and scripts | [Project setup](docs/project-setup.md) |
|
|
27
|
+
| Note model and output | [Content format](docs/content-format.md) |
|
|
28
|
+
| Frontmatter and system blocks | [Frontmatter](docs/authoring/frontmatter.md) |
|
|
29
|
+
| Addresses, links, anchors, SQL, and secret blocks | [Links and markup](docs/authoring/links-and-markup.md) |
|
|
30
|
+
| Images, icons, audio, and provenance | [Assets](docs/authoring/assets.md) |
|
|
31
|
+
| Dates, calendars, and eras | [Dates and calendars](docs/authoring/dates-and-calendars.md) |
|
|
32
|
+
| Complete note fields and mappings | [Note types](docs/reference/note-types.md) |
|
|
33
|
+
| Detailed format rules and examples | [Format details](docs/reference/format-details.md) |
|
|
34
|
+
| Compendium routing and document builds | [Packs](docs/guides/packs.md) |
|
|
35
|
+
| Website build and publication | [Site](docs/guides/site.md) |
|
|
36
|
+
| PDF book build | [Book](docs/guides/book.md) |
|
|
37
|
+
| Icon generation and use | [Icons](docs/guides/icons.md) |
|
|
38
|
+
| Reference pages and content indexes | [Reference pages](docs/guides/reference-pages.md) |
|
|
39
|
+
| Localization and templates | [Localization](docs/guides/localization.md) |
|
|
40
|
+
| Foundry integration testing | [End-to-end testing](docs/guides/e2e.md) |
|
|
41
|
+
| Release and deployment | [Release and deploy](docs/guides/release-and-deploy.md) |
|
|
42
|
+
| Every CLI command | [Commands](docs/commands.md) |
|
|
43
|
+
| Every build setting | [Configuration](docs/configuration.md) |
|
|
44
|
+
| Programmatic exports | [API](docs/api.md) |
|
|
45
|
+
| Error locations and severities | [Diagnostics](docs/diagnostics.md) |
|
|
46
|
+
| Architectural rationale | [Design](docs/design.md) |
|
|
47
|
+
|
|
48
|
+
The package is GPL-3.0-or-later for code and CC-BY-SA-4.0 for original content. See [LICENSE.md](LICENSE.md).
|