@heroiclands/package-build 22.4.4 → 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.
- package/CHANGELOG.md +18 -0
- package/CONTENT.md +30 -1
- package/README.md +9 -0
- package/bin/content-build.mjs +143 -0
- package/bin/package-build.mjs +18 -4
- package/content-config.mjs +7 -0
- package/docs/api.md +84 -0
- package/docs/commands.md +182 -2
- package/docs/configuration.md +19 -1
- package/docs/content-format.md +105 -6
- package/docs/diagnostics.md +1 -0
- package/docs/project-setup.md +7 -6
- package/engine/frontmatter-lint.mjs +11 -1
- package/engine/index.mjs +18 -0
- package/engine/map-build.mjs +240 -0
- package/engine/map-dot.mjs +685 -0
- package/engine/map-graphviz.mjs +138 -0
- package/engine/map-layout.mjs +712 -0
- package/engine/map-places.mjs +561 -0
- package/engine/metadata-index.mjs +7 -0
- package/engine/note-vocabulary.mjs +28 -0
- package/engine/place-relations.mjs +637 -0
- package/engine/site-config.mjs +1 -1
- package/engine/site-root.mjs +147 -6
- package/package.json +3 -2
- package/types/engine/index.d.mts +6 -0
- package/types/engine/map-build.d.mts +78 -0
- package/types/engine/map-dot.d.mts +130 -0
- package/types/engine/map-graphviz.d.mts +61 -0
- package/types/engine/map-layout.d.mts +359 -0
- package/types/engine/map-places.d.mts +259 -0
- package/types/engine/note-vocabulary.d.mts +12 -0
- package/types/engine/place-relations.d.mts +95 -0
- package/types/engine/site-config.d.mts +1 -1
- package/types/engine/site-root.d.mts +59 -7
package/engine/site-root.mjs
CHANGED
|
@@ -20,6 +20,18 @@
|
|
|
20
20
|
* `_redirects` is written. One left beside the site by an earlier build is
|
|
21
21
|
* removed rather than left to send every reader somewhere nothing publishes.
|
|
22
22
|
*
|
|
23
|
+
* **And one directory inside the prefix, `pagefind/`.** Every package site
|
|
24
|
+
* ends the same way — Hugo renders into the output directory, this
|
|
25
|
+
* post-processes it there — so the search index is built in this step and
|
|
26
|
+
* every site gets one without each repository adding a dependency and a
|
|
27
|
+
* script. Pagefind indexes the rendered pages and writes `<package>/pagefind/`
|
|
28
|
+
* beside them, served at `/<package>/pagefind/` with the rest of the site;
|
|
29
|
+
* the theme loads it relative to the site's base URL like any other asset.
|
|
30
|
+
* `site.search: false` skips the step, and removes an index an earlier build
|
|
31
|
+
* left. What is indexed is whatever the theme rendered: the theme marks its
|
|
32
|
+
* own chrome `data-pagefind-ignore`, and this package's part is only to run
|
|
33
|
+
* the indexer over the result.
|
|
34
|
+
*
|
|
23
35
|
* **One implementation, because it is one policy.** What is indexable is a
|
|
24
36
|
* decision about the hosting rather than about any one package. Held in each
|
|
25
37
|
* consumer it is the same file with one constant changed, which is a file that
|
|
@@ -31,6 +43,21 @@
|
|
|
31
43
|
|
|
32
44
|
import fs from "node:fs";
|
|
33
45
|
import path from "node:path";
|
|
46
|
+
import * as pagefind from "pagefind";
|
|
47
|
+
|
|
48
|
+
import { formatDiagnostic } from "./diagnostics.mjs";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The directory the search index is written to, inside the rendered site.
|
|
52
|
+
*
|
|
53
|
+
* Pagefind's own default, and the one its browser bundle derives the site's
|
|
54
|
+
* base URL from: `pagefind.js` at `/<package>/pagefind/pagefind.js` means
|
|
55
|
+
* every result's `url` is joined onto `/<package>/`, so nothing about the
|
|
56
|
+
* prefix is configured on either side.
|
|
57
|
+
*
|
|
58
|
+
* @type {string}
|
|
59
|
+
*/
|
|
60
|
+
export const SEARCH_DIR = "pagefind";
|
|
34
61
|
|
|
35
62
|
/**
|
|
36
63
|
* The namespace the routing layer derives a package's origin in.
|
|
@@ -89,23 +116,112 @@ export function headers() {
|
|
|
89
116
|
}
|
|
90
117
|
|
|
91
118
|
/**
|
|
92
|
-
*
|
|
119
|
+
* What runs the search indexer over a rendered site.
|
|
120
|
+
*
|
|
121
|
+
* {@link indexSite} is the one the command uses; a test hands
|
|
122
|
+
* {@link writeSiteRoot} a stand-in, so the step's own contract — what it is
|
|
123
|
+
* given, when it runs, how its failure is reported — is checked without a
|
|
124
|
+
* binary.
|
|
125
|
+
*
|
|
126
|
+
* @callback SiteIndexer
|
|
127
|
+
* @param {{site: string, output: string}} where - The rendered site, and the
|
|
128
|
+
* directory inside it the index is written to.
|
|
129
|
+
* @returns {Promise<{pages: number}>} How many pages were indexed.
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Build the search index of a rendered site with Pagefind.
|
|
134
|
+
*
|
|
135
|
+
* Pagefind's Node API drives the binary the `pagefind` package installs for
|
|
136
|
+
* the platform as an optional dependency, so there is nothing to put on a
|
|
137
|
+
* `PATH`. Three things it does not do on its own are done here, because each
|
|
138
|
+
* is a quiet failure otherwise:
|
|
139
|
+
*
|
|
140
|
+
* - A directory that is not there indexes as zero pages and no error, so the
|
|
141
|
+
* site is checked first.
|
|
142
|
+
* - Zero pages from a directory that _is_ there means the pages were not
|
|
143
|
+
* read — an empty index is deployable and finds nothing — so it is refused.
|
|
144
|
+
* - The binary missing is reported by the package as an install failure of
|
|
145
|
+
* its own; it is rethrown naming the platform package to install.
|
|
146
|
+
*
|
|
147
|
+
* The output directory is emptied first, so an index of an earlier build's
|
|
148
|
+
* pages never survives beside the current one.
|
|
149
|
+
*
|
|
150
|
+
* @type {SiteIndexer}
|
|
151
|
+
*/
|
|
152
|
+
export async function indexSite({ site, output }) {
|
|
153
|
+
if (!fs.statSync(site, { throwIfNoEntry: false })?.isDirectory()) {
|
|
154
|
+
throw new Error(`${site} is not a directory, so there is nothing to index`);
|
|
155
|
+
}
|
|
156
|
+
fs.rmSync(output, { recursive: true, force: true });
|
|
157
|
+
|
|
158
|
+
let index;
|
|
159
|
+
try {
|
|
160
|
+
({ index } = await pagefind.createIndex({ verbose: false }));
|
|
161
|
+
} catch (err) {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
// The package's own wording for "no binary resolved for this
|
|
164
|
+
// platform", the one failure of the step that is not the site's.
|
|
165
|
+
if (/Failed to install/.test(message)) {
|
|
166
|
+
const platform = process.platform === "win32" ? "windows" : process.platform;
|
|
167
|
+
throw new Error(
|
|
168
|
+
`the pagefind binary for ${platform}-${process.arch} is not installed — ` +
|
|
169
|
+
`\`npm ci\` installs it as the optional dependency ` +
|
|
170
|
+
`@pagefind/${platform}-${process.arch} of pagefind; on a platform it ` +
|
|
171
|
+
`has no build for, set PAGEFIND_BINARY_PATH to a binary from ` +
|
|
172
|
+
`https://github.com/Pagefind/pagefind/releases`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const added = await index.addDirectory({ path: site });
|
|
179
|
+
if (added.errors.length) throw new Error(added.errors.join("; "));
|
|
180
|
+
const pages = added.page_count;
|
|
181
|
+
if (!pages) {
|
|
182
|
+
throw new Error(`no page under ${site} was indexed — the site holds no HTML`);
|
|
183
|
+
}
|
|
184
|
+
const written = await index.writeFiles({ outputPath: output });
|
|
185
|
+
if (written.errors.length) throw new Error(written.errors.join("; "));
|
|
186
|
+
return { pages };
|
|
187
|
+
} finally {
|
|
188
|
+
await pagefind.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Write `_headers` beside the rendered site, remove any `_redirects`, and
|
|
194
|
+
* index the site for search.
|
|
93
195
|
*
|
|
94
196
|
* The removal is part of owning the root: a `_redirects` this build did not
|
|
95
197
|
* write is one an earlier build left, and Cloudflare Pages applies whatever
|
|
96
198
|
* sits there. Left in place it would redirect the prefix root — the homepage —
|
|
97
|
-
* to an address nothing publishes.
|
|
199
|
+
* to an address nothing publishes. The same holds for a `pagefind/` left by an
|
|
200
|
+
* earlier build when `search` is off: deployed, it is a working search over
|
|
201
|
+
* pages that may no longer exist, so it goes too.
|
|
202
|
+
*
|
|
203
|
+
* `_headers` is written before the index is built, so a failed index leaves
|
|
204
|
+
* the root files as they would be — the failure is the indexer's, reported as
|
|
205
|
+
* one finding naming the rendered site, and nothing else about the deployment
|
|
206
|
+
* is in doubt.
|
|
98
207
|
*
|
|
99
208
|
* @param {object} options - Options.
|
|
100
209
|
* @param {string} options.pkg - The content package name, which is also the
|
|
101
210
|
* directory Hugo rendered into.
|
|
102
211
|
* @param {string} options.out - The directory that is deployed.
|
|
103
|
-
* @
|
|
212
|
+
* @param {boolean} [options.search] - Whether to build the search index;
|
|
213
|
+
* `site.search` in the configuration. Default `true`.
|
|
214
|
+
* @param {SiteIndexer} [options.indexer] - What builds it. Default
|
|
215
|
+
* {@link indexSite}.
|
|
216
|
+
* @returns {Promise<{files: string[], search: {dir: string, pages: number}|null}>}
|
|
217
|
+
* The files written, and the index — where it is and how many pages it
|
|
218
|
+
* holds — or `null` when none was built.
|
|
104
219
|
* @throws {Error} When no rendered site is there, which means the site build
|
|
105
220
|
* has not run and writing root files would publish a deployment with nothing
|
|
106
|
-
* under the prefix
|
|
221
|
+
* under the prefix; or, located at the rendered site, when the indexer
|
|
222
|
+
* fails.
|
|
107
223
|
*/
|
|
108
|
-
export function writeSiteRoot({ pkg, out }) {
|
|
224
|
+
export async function writeSiteRoot({ pkg, out, search = true, indexer = indexSite }) {
|
|
109
225
|
const root = path.resolve(out);
|
|
110
226
|
const rendered = path.join(root, pkg);
|
|
111
227
|
if (!fs.existsSync(path.join(rendered, "index.html"))) {
|
|
@@ -117,5 +233,30 @@ export function writeSiteRoot({ pkg, out }) {
|
|
|
117
233
|
const file = path.join(root, "_headers");
|
|
118
234
|
fs.writeFileSync(file, headers());
|
|
119
235
|
fs.rmSync(path.join(root, "_redirects"), { force: true });
|
|
120
|
-
|
|
236
|
+
|
|
237
|
+
const dir = path.join(rendered, SEARCH_DIR);
|
|
238
|
+
if (!search) {
|
|
239
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
240
|
+
return { files: [file], search: null };
|
|
241
|
+
}
|
|
242
|
+
let pages;
|
|
243
|
+
try {
|
|
244
|
+
({ pages } = await indexer({ site: rendered, output: dir }));
|
|
245
|
+
} catch (err) {
|
|
246
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
247
|
+
// A directory, so the locator is the file field alone: there is no
|
|
248
|
+
// line to name, and `1:1` would send a reader to the homepage's
|
|
249
|
+
// markup for a failure that is the indexer's.
|
|
250
|
+
throw Object.assign(
|
|
251
|
+
new Error(
|
|
252
|
+
formatDiagnostic({
|
|
253
|
+
file: rendered,
|
|
254
|
+
severity: "error",
|
|
255
|
+
message: `search index: ${message}`,
|
|
256
|
+
}),
|
|
257
|
+
),
|
|
258
|
+
{ located: true, file: rendered },
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return { files: [file], search: { dir, pages } };
|
|
121
262
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.4.
|
|
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": "^
|
|
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",
|
package/types/engine/index.d.mts
CHANGED
|
@@ -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[];
|