@heroiclands/package-build 22.4.3 → 22.4.5

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +28 -18
  2. package/CONTENT.md +77 -1
  3. package/README.md +9 -0
  4. package/bin/content-build.mjs +143 -0
  5. package/bin/package-build.mjs +18 -4
  6. package/content-config.mjs +7 -0
  7. package/docs/api.md +84 -0
  8. package/docs/commands.md +182 -2
  9. package/docs/configuration.md +19 -1
  10. package/docs/content-format.md +105 -6
  11. package/docs/diagnostics.md +1 -0
  12. package/docs/project-setup.md +7 -6
  13. package/engine/changelog-group.mjs +27 -6
  14. package/engine/changelog-lint.mjs +27 -7
  15. package/engine/frontmatter-lint.mjs +11 -1
  16. package/engine/homepage.mjs +9 -1
  17. package/engine/index.mjs +18 -0
  18. package/engine/map-build.mjs +240 -0
  19. package/engine/map-dot.mjs +685 -0
  20. package/engine/map-graphviz.mjs +138 -0
  21. package/engine/map-layout.mjs +712 -0
  22. package/engine/map-places.mjs +561 -0
  23. package/engine/metadata-index.mjs +7 -0
  24. package/engine/note-vocabulary.mjs +28 -0
  25. package/engine/place-relations.mjs +637 -0
  26. package/engine/related-pages.mjs +161 -0
  27. package/engine/site-build.mjs +107 -9
  28. package/engine/site-config.mjs +1 -1
  29. package/engine/site-index.mjs +5 -1
  30. package/engine/site-root.mjs +147 -6
  31. package/engine/web-wikilinks.mjs +13 -2
  32. package/package.json +3 -2
  33. package/types/engine/changelog-lint.d.mts +9 -7
  34. package/types/engine/homepage.d.mts +8 -1
  35. package/types/engine/index.d.mts +6 -0
  36. package/types/engine/map-build.d.mts +78 -0
  37. package/types/engine/map-dot.d.mts +130 -0
  38. package/types/engine/map-graphviz.d.mts +61 -0
  39. package/types/engine/map-layout.d.mts +359 -0
  40. package/types/engine/map-places.d.mts +259 -0
  41. package/types/engine/note-vocabulary.d.mts +12 -0
  42. package/types/engine/place-relations.d.mts +95 -0
  43. package/types/engine/related-pages.d.mts +70 -0
  44. package/types/engine/site-build.d.mts +36 -3
  45. package/types/engine/site-config.d.mts +1 -1
  46. package/types/engine/site-index.d.mts +5 -1
  47. package/types/engine/site-root.d.mts +59 -7
  48. package/types/engine/web-wikilinks.d.mts +11 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.4.3",
3
+ "version": "22.4.5",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -137,7 +137,7 @@
137
137
  "acorn": "^8.18.0",
138
138
  "archiver": "^8.0.0",
139
139
  "classic-level": "^3.0.0",
140
- "dotenv": "^17.2.3",
140
+ "dotenv": "^18.0.0",
141
141
  "eslint": "^10.10.0",
142
142
  "eslint-plugin-yml": "^3.8.1",
143
143
  "fflate": "^0.8.3",
@@ -148,6 +148,7 @@
148
148
  "loglevel-plugin-prefix": "^0.8.4",
149
149
  "markdown-it": "^15.0.0",
150
150
  "markdownlint-cli2": "^0.23.2",
151
+ "pagefind": "^1.5.2",
151
152
  "prettier": "^3.9.6",
152
153
  "smol-toml": "^1.7.0",
153
154
  "ssh2-sftp-client": "^12.1.1",
@@ -48,13 +48,15 @@ export function topLevelBullets(text: string, codeLines: Set<number>): Array<{
48
48
  * {@link topLevelBullets}, where a bullet marker *is* the top-level
49
49
  * construct — a `-` bullet at column 0 belongs to whatever block is
50
50
  * already open, since a label's bullets sit unindented directly under it.
51
- * Any other column-0 line (plain prose with no label, a bullet with no
52
- * block open yet) starts the one "lead" block (`label: null`) a changeset
53
- * with no category writes — the case `changelog group` sorts first and
54
- * `check` never flags. Nested detail — a wrapped line, a bullet's own
55
- * continuation — stays indented and belongs to whatever it follows.
56
- * `changelog group` folds same-label blocks together; `check` warns when a
57
- * block's label is not in the declared vocabulary.
51
+ * A plain column-0 line that is neither a label nor a bullet continues
52
+ * whatever block is open, because Prettier's `proseWrap` puts every line of
53
+ * a hard-wrapped paragraph at column 0 — it starts a new "lead" block
54
+ * (`label: null`) only at a paragraph boundary, the start of the section or
55
+ * a line right after a blank one, which is the case `changelog group`
56
+ * sorts first and `check` never flags. A blank line never closes a block by
57
+ * itself; it only ends the run of continuation lines a following column-0
58
+ * line can attach to. `changelog group` folds same-label blocks together;
59
+ * `check` warns when a block's label is not in the declared vocabulary.
58
60
  *
59
61
  * @param {string} text - Section text.
60
62
  * @param {Set<number>} codeLines - Lines inside a code region, from
@@ -148,15 +148,22 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
148
148
  * redirect stub at each one. The field is retired and refused before a
149
149
  * build reaches here, which makes this a guard rather than a working path.
150
150
  *
151
+ * `related` is derived like every other page's — what links to the homepage
152
+ * is a fact about the rest of the tree — so an authored value is dropped and
153
+ * the caller's block, where it has one, is written in its place.
154
+ *
151
155
  * @param {object} fm - The note's frontmatter.
152
156
  * @param {object} options - Options.
153
157
  * @param {string} options.contentPackage - The package this build publishes.
154
158
  * @param {string} options.title - The resolved title.
159
+ * @param {object} [options.related] - The homepage's backlinks and mentions,
160
+ * where the site build found either.
155
161
  * @returns {object} The frontmatter to write.
156
162
  */
157
- export function homepageFrontmatter(fm: object, { contentPackage, title }: {
163
+ export function homepageFrontmatter(fm: object, { contentPackage, title, related }: {
158
164
  contentPackage: string;
159
165
  title: string;
166
+ related?: object | undefined;
160
167
  }): object;
161
168
  /**
162
169
  * Every address a homepage carries: the markdown links in its body.
@@ -15,6 +15,12 @@ export * as derivedFields from "./derived-fields.mjs";
15
15
  export * as homepage from "./homepage.mjs";
16
16
  export * as noteSchemas from "./note-schemas.mjs";
17
17
  export * as noteVocabulary from "./note-vocabulary.mjs";
18
+ export * as placeRelations from "./place-relations.mjs";
19
+ export * as mapPlaces from "./map-places.mjs";
20
+ export * as mapLayout from "./map-layout.mjs";
21
+ export * as mapDot from "./map-dot.mjs";
22
+ export * as mapGraphviz from "./map-graphviz.mjs";
23
+ export * as mapBuild from "./map-build.mjs";
18
24
  export * as systems from "./systems.mjs";
19
25
  export * as contentSlug from "./content-slug.mjs";
20
26
  export * as contentAddress from "./content-address.mjs";
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Every place that states, or is named in, a border or a route — the places
3
+ * `--from all` draws.
4
+ *
5
+ * @param {Map<string, import("./map-places.mjs").MapPlace>} places - Every place.
6
+ * @returns {string[]} Shortcodes, sorted.
7
+ */
8
+ export function relatedPlaces(places: Map<string, import("./map-places.mjs").MapPlace>): string[];
9
+ /**
10
+ * Build the maps.
11
+ *
12
+ * @param {object} opts
13
+ * @param {import("./map-places.mjs").MapWorld} opts.world - The places, as
14
+ * {@link module:engine/map-places.loadMapWorld} returns them.
15
+ * @param {string} opts.outDir - Where to write; created if absent.
16
+ * @param {boolean} [opts.tree] - Draw the containment tree.
17
+ * @param {string} [opts.root] - With `tree`, the subtree beneath this place.
18
+ * @param {string[]} [opts.from] - Draw the map from each of these places, or
19
+ * from every related place for {@link FROM_ALL}.
20
+ * @param {boolean} [opts.travel] - Draw the whole route graph.
21
+ * @param {string} [opts.engine="dot"] - The tree's engine: `dot`, `twopi` or
22
+ * `neato`. The map from a place is always `neato` with every node pinned,
23
+ * and the route graph always `neato`.
24
+ * @param {string} [opts.rankdir="TB"] - The tree's rank direction, `dot` only.
25
+ * @param {number} [opts.nodesep] - The tree's `nodesep`, inches.
26
+ * @param {number} [opts.ranksep] - The tree's `ranksep`, inches.
27
+ * @param {number} [opts.scale=1] - Multiplies every node's size and font.
28
+ * @param {boolean} [opts.polities=true] - Whether the tree draws polities.
29
+ * @param {(engine: string) => string|undefined} [opts.locate] - How a GraphViz
30
+ * engine's binary is found; the real lookup by default.
31
+ * @param {boolean} [opts.requireGraphviz=true] - Whether a missing engine is
32
+ * an error. False, the `.dot` files are written, the renderings skipped,
33
+ * and one warning returned.
34
+ * @returns {{written: string[], findings: Array<{file: string, line?: number,
35
+ * column?: number, severity: "error"|"warning", message: string}>,
36
+ * skipped: boolean}} What was written, what was found, and whether the
37
+ * rendering was skipped for want of GraphViz.
38
+ * @throws {Error} When no mode is asked for, a named place is not one, or
39
+ * GraphViz is required and absent.
40
+ */
41
+ export function buildMaps({ world, outDir, tree, root, from, travel, engine, rankdir, nodesep, ranksep, scale, polities, locate, requireGraphviz, }: {
42
+ world: import("./map-places.mjs").MapWorld;
43
+ outDir: string;
44
+ tree?: boolean | undefined;
45
+ root?: string | undefined;
46
+ from?: string[] | undefined;
47
+ travel?: boolean | undefined;
48
+ engine?: string | undefined;
49
+ rankdir?: string | undefined;
50
+ nodesep?: number | undefined;
51
+ ranksep?: number | undefined;
52
+ scale?: number | undefined;
53
+ polities?: boolean | undefined;
54
+ locate?: ((engine: string) => string | undefined) | undefined;
55
+ requireGraphviz?: boolean | undefined;
56
+ }): {
57
+ written: string[];
58
+ findings: Array<{
59
+ file: string;
60
+ line?: number;
61
+ column?: number;
62
+ severity: "error" | "warning";
63
+ message: string;
64
+ }>;
65
+ skipped: boolean;
66
+ };
67
+ /**
68
+ * The directory the maps land in, below a package's root.
69
+ *
70
+ * @type {string}
71
+ */
72
+ export const MAP_DIR: string;
73
+ /**
74
+ * The value of `--from` that means every place with a relation.
75
+ *
76
+ * @type {string}
77
+ */
78
+ export const FROM_ALL: string;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * A DOT double-quoted string.
3
+ *
4
+ * @param {unknown} value - The text.
5
+ * @returns {string} Quoted and escaped, newlines folded to spaces.
6
+ */
7
+ export function dotString(value: unknown): string;
8
+ /**
9
+ * The containment tree as DOT.
10
+ *
11
+ * With no `root`, every place, clustered by continent — a region parented
12
+ * directly on the world — and the places reaching no continent floating
13
+ * outside every cluster. With a `root`, the subtree beneath that place, in
14
+ * one cluster, the world drawn beside it only when the root is the world or
15
+ * a continent. Edges run child → parent, as `parents` states them. A polity
16
+ * is drawn dashed beside every region note in its directory, unless
17
+ * `polities` is false.
18
+ *
19
+ * @param {object} opts
20
+ * @param {Map<string, import("./map-places.mjs").MapPlace>} opts.places
21
+ * @param {import("./map-places.mjs").MapPolity[]} [opts.polities]
22
+ * @param {import("./map-places.mjs").Containment} opts.analysis
23
+ * @param {string} [opts.root] - Draw the subtree beneath this place.
24
+ * @param {string} [opts.title] - The drawing's title, defaulting to the
25
+ * world's or the root's name.
26
+ * @param {string} [opts.engine="dot"] - `dot`, `twopi` or `neato`.
27
+ * @param {string} [opts.rankdir="TB"] - `dot` only.
28
+ * @param {number} [opts.nodesep] - Inches; defaults by scale.
29
+ * @param {number} [opts.ranksep] - Inches; defaults by engine and scale.
30
+ * @param {number} [opts.scale=1] - Multiplies every size.
31
+ * @param {boolean} [opts.drawPolities=true] - Whether to draw the polities.
32
+ * @returns {string} The DOT text.
33
+ */
34
+ export function treeDot({ places, polities, analysis, root, title, engine, rankdir, nodesep, ranksep, scale, drawPolities, }: {
35
+ places: Map<string, import("./map-places.mjs").MapPlace>;
36
+ polities?: import("./map-places.mjs").MapPolity[] | undefined;
37
+ analysis: import("./map-places.mjs").Containment;
38
+ root?: string | undefined;
39
+ title?: string | undefined;
40
+ engine?: string | undefined;
41
+ rankdir?: string | undefined;
42
+ nodesep?: number | undefined;
43
+ ranksep?: number | undefined;
44
+ scale?: number | undefined;
45
+ drawPolities?: boolean | undefined;
46
+ }): string;
47
+ /**
48
+ * The map from a place as DOT, for `neato -n2`.
49
+ *
50
+ * Every node is pinned at the position the layout computed, so the engine
51
+ * only draws. The rings are circles pinned at the origin, drawn faintly,
52
+ * each labelled with its days marker at the top; the rim is one more. The
53
+ * centre is drawn in its `subType` style at full size, a neighbour at the
54
+ * layout's scale, and a second hop dimmer. A place beyond the horizon or
55
+ * with unknown days is a name at the rim, its label carrying its days or `?`.
56
+ * A border edge is dashed; every edge is labelled with its bearing and days.
57
+ *
58
+ * @param {ReturnType<typeof import("./map-layout.mjs").layoutFrom>} layout - The layout.
59
+ * @param {Map<string, import("./map-places.mjs").MapPlace>} places - Every place.
60
+ * @param {object} [opts]
61
+ * @param {number} [opts.scale=1] - Multiplies every size.
62
+ * @param {string} [opts.title] - Defaults to `From <name>`.
63
+ * @returns {string} The DOT text.
64
+ */
65
+ export function fromDot(layout: ReturnType<typeof import("./map-layout.mjs").layoutFrom>, places: Map<string, import("./map-places.mjs").MapPlace>, { scale, title }?: {
66
+ scale?: number | undefined;
67
+ title?: string | undefined;
68
+ }): string;
69
+ /**
70
+ * The whole route graph as DOT, for `neato`.
71
+ *
72
+ * Undirected, one edge per pair, each asking the engine for a length from
73
+ * its days; the engine solves the positions. A border is dashed. Every node
74
+ * that takes part in a relation is drawn in its `subType` style.
75
+ *
76
+ * @param {ReturnType<typeof import("./map-layout.mjs").travelGraph>} graph - The graph.
77
+ * @param {Map<string, import("./map-places.mjs").MapPlace>} places - Every place.
78
+ * @param {object} [opts]
79
+ * @param {number} [opts.scale=1] - Multiplies every size.
80
+ * @param {string} [opts.title] - Defaults to `Routes`.
81
+ * @returns {string} The DOT text.
82
+ */
83
+ export function travelDot(graph: ReturnType<typeof import("./map-layout.mjs").travelGraph>, places: Map<string, import("./map-places.mjs").MapPlace>, { scale, title }?: {
84
+ scale?: number | undefined;
85
+ title?: string | undefined;
86
+ }): string;
87
+ /**
88
+ * One row of the style table.
89
+ *
90
+ * @typedef {object} NodeStyle
91
+ * @property {string} shape
92
+ * @property {string} fill
93
+ * @property {string} border
94
+ * @property {number} fontsize
95
+ * @property {number} width - Inches, a minimum: a long label still grows the node.
96
+ * @property {number} height - Inches.
97
+ * @property {number} penwidth
98
+ * @property {string} extraStyle - `rounded`, `dashed`, or none.
99
+ */
100
+ /**
101
+ * Shape, colour and size by `subType`, plus the synthetic `polity` row for
102
+ * the affiliation notes drawn beside their regions. The world is largest;
103
+ * regions large with a bold label; settlements small with a thin border;
104
+ * site, feature and structure between.
105
+ *
106
+ * @type {Readonly<Record<string, NodeStyle>>}
107
+ */
108
+ export const STYLE: Readonly<Record<string, NodeStyle>>;
109
+ /**
110
+ * One row of the style table.
111
+ */
112
+ export type NodeStyle = {
113
+ shape: string;
114
+ fill: string;
115
+ border: string;
116
+ fontsize: number;
117
+ /**
118
+ * - Inches, a minimum: a long label still grows the node.
119
+ */
120
+ width: number;
121
+ /**
122
+ * - Inches.
123
+ */
124
+ height: number;
125
+ penwidth: number;
126
+ /**
127
+ * - `rounded`, `dashed`, or none.
128
+ */
129
+ extraStyle: string;
130
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Find a GraphViz engine's binary.
3
+ *
4
+ * `PATH` first, then the directories a package manager installs into, so a
5
+ * shell whose profile does not export Homebrew's directory still finds it.
6
+ * The result is a path to a file that exists; whether it runs is the render's
7
+ * business.
8
+ *
9
+ * @param {string} engine - One of {@link GRAPHVIZ_ENGINES}.
10
+ * @param {object} [opts]
11
+ * @param {Record<string, string|undefined>} [opts.env] - The environment to
12
+ * read `PATH` from, defaulting to the process's.
13
+ * @param {readonly string[]} [opts.fallbacks] - The directories tried after
14
+ * `PATH`, defaulting to the package managers' ones.
15
+ * @returns {string|undefined} The binary, or `undefined` when none is found.
16
+ * @throws {Error} When `engine` is not a GraphViz engine this command uses.
17
+ */
18
+ export function findGraphviz(engine: string, { env, fallbacks }?: {
19
+ env?: Record<string, string | undefined> | undefined;
20
+ fallbacks?: readonly string[] | undefined;
21
+ }): string | undefined;
22
+ /**
23
+ * What to say when an engine is not installed: the name, and what installs
24
+ * it on the two platforms a contributor is likely to be on.
25
+ *
26
+ * @param {string} engine - The engine that was wanted.
27
+ * @returns {string} The message.
28
+ */
29
+ export function graphvizMissingMessage(engine: string): string;
30
+ /**
31
+ * Render a `.dot` file with a GraphViz engine.
32
+ *
33
+ * The engine is run over the file rather than fed the text, so the file the
34
+ * command keeps is exactly what was drawn. Warnings GraphViz prints — an
35
+ * unknown font, an overlapping label — are returned rather than swallowed,
36
+ * and a non-zero exit is an error naming the file.
37
+ *
38
+ * @param {string} dotPath - The `.dot` file.
39
+ * @param {string} outPath - Where to write the rendering.
40
+ * @param {object} opts
41
+ * @param {string} opts.binary - The engine's binary, from {@link findGraphviz}.
42
+ * @param {string} [opts.format="svg"] - The `-T` format.
43
+ * @param {string[]} [opts.args] - Further arguments, before the file.
44
+ * @returns {{warnings: string}} What the engine printed on stderr.
45
+ * @throws {Error} When the engine exits non-zero or cannot be run.
46
+ */
47
+ export function renderDot(dotPath: string, outPath: string, { binary, format, args }: {
48
+ binary: string;
49
+ format?: string | undefined;
50
+ args?: string[] | undefined;
51
+ }): {
52
+ warnings: string;
53
+ };
54
+ /**
55
+ * The layout engines the map command may ask for. `dot` draws a tree by
56
+ * rank, `twopi` the same tree radially, `neato` a graph by its edge lengths —
57
+ * and, with every node pinned, the map from a place.
58
+ *
59
+ * @type {readonly string[]}
60
+ */
61
+ export const GRAPHVIZ_ENGINES: readonly string[];