@heroiclands/package-build 22.4.4 → 22.4.6

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.
@@ -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
- * Write `_headers` beside the rendered site, and remove any `_redirects`.
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
- * @returns {{files: string[]}} The files written.
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
- return { files: [file] };
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.4",
3
+ "version": "22.4.6",
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",
@@ -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,84 @@
1
+ /**
2
+ * Every place that states, or is named in, a border or a route — the places
3
+ * `--from all` and `--chart all` draw.
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 {string[]} [opts.chart] - Draw the chart from each of these places,
21
+ * or from every related place for {@link FROM_ALL}.
22
+ * @param {number} [opts.horizon] - The charts' horizon in days, a marker of
23
+ * the scale; the end of the scale by default.
24
+ * @param {boolean} [opts.travel] - Draw the whole route graph.
25
+ * @param {string} [opts.engine="dot"] - The tree's engine: `dot`, `twopi` or
26
+ * `neato`. The map and the chart from a place are always `neato` with
27
+ * every node pinned, and the route graph always `neato`.
28
+ * @param {string} [opts.rankdir="TB"] - The tree's rank direction, `dot` only.
29
+ * @param {number} [opts.nodesep] - The tree's `nodesep`, inches.
30
+ * @param {number} [opts.ranksep] - The tree's `ranksep`, inches.
31
+ * @param {number} [opts.scale=1] - Multiplies every node's size and font.
32
+ * @param {boolean} [opts.polities=true] - Whether the tree draws polities.
33
+ * @param {(engine: string) => string|undefined} [opts.locate] - How a GraphViz
34
+ * engine's binary is found; the real lookup by default.
35
+ * @param {boolean} [opts.requireGraphviz=true] - Whether a missing engine is
36
+ * an error. False, the `.dot` files are written, the renderings skipped,
37
+ * and one warning returned.
38
+ * @returns {{written: string[], findings: Array<{file: string, line?: number,
39
+ * column?: number, severity: "error"|"warning", message: string}>,
40
+ * skipped: boolean}} What was written, what was found, and whether the
41
+ * rendering was skipped for want of GraphViz.
42
+ * @throws {Error} When no mode is asked for, a named place is not one, the
43
+ * horizon is not a marker of the scale, or GraphViz is required and absent.
44
+ */
45
+ export function buildMaps({ world, outDir, tree, root, from, chart, horizon, travel, engine, rankdir, nodesep, ranksep, scale, polities, locate, requireGraphviz, }: {
46
+ world: import("./map-places.mjs").MapWorld;
47
+ outDir: string;
48
+ tree?: boolean | undefined;
49
+ root?: string | undefined;
50
+ from?: string[] | undefined;
51
+ chart?: string[] | undefined;
52
+ horizon?: number | undefined;
53
+ travel?: boolean | undefined;
54
+ engine?: string | undefined;
55
+ rankdir?: string | undefined;
56
+ nodesep?: number | undefined;
57
+ ranksep?: number | undefined;
58
+ scale?: number | undefined;
59
+ polities?: boolean | undefined;
60
+ locate?: ((engine: string) => string | undefined) | undefined;
61
+ requireGraphviz?: boolean | undefined;
62
+ }): {
63
+ written: string[];
64
+ findings: Array<{
65
+ file: string;
66
+ line?: number;
67
+ column?: number;
68
+ severity: "error" | "warning";
69
+ message: string;
70
+ }>;
71
+ skipped: boolean;
72
+ };
73
+ /**
74
+ * The directory the maps land in, below a package's root.
75
+ *
76
+ * @type {string}
77
+ */
78
+ export const MAP_DIR: string;
79
+ /**
80
+ * The value of `--from` or `--chart` that means every place with a relation.
81
+ *
82
+ * @type {string}
83
+ */
84
+ export const FROM_ALL: string;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The opacity of a place by its hop count: the neighbours and the second hop
3
+ * are drawn full — the second in the dim palette — and each hop beyond
4
+ * steps down, never below the floor.
5
+ *
6
+ * @param {number} hop - 1 for a neighbour, 2 and up beyond.
7
+ * @returns {number} 0 to 1.
8
+ */
9
+ export function hopOpacity(hop: number): number;
10
+ /**
11
+ * A DOT double-quoted string.
12
+ *
13
+ * @param {unknown} value - The text.
14
+ * @returns {string} Quoted and escaped, newlines folded to spaces.
15
+ */
16
+ export function dotString(value: unknown): string;
17
+ /**
18
+ * The containment tree as DOT.
19
+ *
20
+ * With no `root`, every place, clustered by continent — a region parented
21
+ * directly on the world — and the places reaching no continent floating
22
+ * outside every cluster. With a `root`, the subtree beneath that place, in
23
+ * one cluster, the world drawn beside it only when the root is the world or
24
+ * a continent. Edges run child → parent, as `parents` states them. A polity
25
+ * is drawn dashed beside every region note in its directory, unless
26
+ * `polities` is false.
27
+ *
28
+ * @param {object} opts
29
+ * @param {Map<string, import("./map-places.mjs").MapPlace>} opts.places
30
+ * @param {import("./map-places.mjs").MapPolity[]} [opts.polities]
31
+ * @param {import("./map-places.mjs").Containment} opts.analysis
32
+ * @param {string} [opts.root] - Draw the subtree beneath this place.
33
+ * @param {string} [opts.title] - The drawing's title, defaulting to the
34
+ * world's or the root's name.
35
+ * @param {string} [opts.engine="dot"] - `dot`, `twopi` or `neato`.
36
+ * @param {string} [opts.rankdir="TB"] - `dot` only.
37
+ * @param {number} [opts.nodesep] - Inches; defaults by scale.
38
+ * @param {number} [opts.ranksep] - Inches; defaults by engine and scale.
39
+ * @param {number} [opts.scale=1] - Multiplies every size.
40
+ * @param {boolean} [opts.drawPolities=true] - Whether to draw the polities.
41
+ * @returns {string} The DOT text.
42
+ */
43
+ export function treeDot({ places, polities, analysis, root, title, engine, rankdir, nodesep, ranksep, scale, drawPolities, }: {
44
+ places: Map<string, import("./map-places.mjs").MapPlace>;
45
+ polities?: import("./map-places.mjs").MapPolity[] | undefined;
46
+ analysis: import("./map-places.mjs").Containment;
47
+ root?: string | undefined;
48
+ title?: string | undefined;
49
+ engine?: string | undefined;
50
+ rankdir?: string | undefined;
51
+ nodesep?: number | undefined;
52
+ ranksep?: number | undefined;
53
+ scale?: number | undefined;
54
+ drawPolities?: boolean | undefined;
55
+ }): string;
56
+ /**
57
+ * The map from a place, or the chart from one, as DOT for `neato -n2`.
58
+ *
59
+ * Every node is pinned at the position the layout computed, so the engine
60
+ * only draws. The rings are circles pinned at the origin, drawn faintly,
61
+ * each labelled with its days marker at the top; the rim is one more,
62
+ * labelled `beyond` where the layout names what lies past the horizon there
63
+ * and `unknown` where it holds only the places whose days nobody stated.
64
+ * The centre is drawn in its `subType` style at full size, a neighbour at
65
+ * the layout's scale, a second hop dimmer, and each hop beyond fainter
66
+ * still, down to a floor. A place at the rim is a name, its label carrying
67
+ * its days or `?`. A border edge is dashed; every edge is labelled with its
68
+ * bearing and days, and dimmed with its far end.
69
+ *
70
+ * @param {import("./map-layout.mjs").FromLayout} layout - The layout.
71
+ * @param {Map<string, import("./map-places.mjs").MapPlace>} places - Every place.
72
+ * @param {object} [opts]
73
+ * @param {number} [opts.scale=1] - Multiplies every size.
74
+ * @param {string} [opts.title] - Defaults to `From <name>`, or to
75
+ * `Chart from <name>` for a layout with no rim beyond the horizon.
76
+ * @returns {string} The DOT text.
77
+ */
78
+ export function fromDot(layout: import("./map-layout.mjs").FromLayout, places: Map<string, import("./map-places.mjs").MapPlace>, { scale, title }?: {
79
+ scale?: number | undefined;
80
+ title?: string | undefined;
81
+ }): string;
82
+ /**
83
+ * The whole route graph as DOT, for `neato`.
84
+ *
85
+ * Undirected, one edge per pair, each asking the engine for a length from
86
+ * its days; the engine solves the positions. A border is dashed. Every node
87
+ * that takes part in a relation is drawn in its `subType` style.
88
+ *
89
+ * @param {ReturnType<typeof import("./map-layout.mjs").travelGraph>} graph - The graph.
90
+ * @param {Map<string, import("./map-places.mjs").MapPlace>} places - Every place.
91
+ * @param {object} [opts]
92
+ * @param {number} [opts.scale=1] - Multiplies every size.
93
+ * @param {string} [opts.title] - Defaults to `Routes`.
94
+ * @returns {string} The DOT text.
95
+ */
96
+ export function travelDot(graph: ReturnType<typeof import("./map-layout.mjs").travelGraph>, places: Map<string, import("./map-places.mjs").MapPlace>, { scale, title }?: {
97
+ scale?: number | undefined;
98
+ title?: string | undefined;
99
+ }): string;
100
+ /**
101
+ * One row of the style table.
102
+ *
103
+ * @typedef {object} NodeStyle
104
+ * @property {string} shape
105
+ * @property {string} fill
106
+ * @property {string} border
107
+ * @property {number} fontsize
108
+ * @property {number} width - Inches, a minimum: a long label still grows the node.
109
+ * @property {number} height - Inches.
110
+ * @property {number} penwidth
111
+ * @property {string} extraStyle - `rounded`, `dashed`, or none.
112
+ */
113
+ /**
114
+ * Shape, colour and size by `subType`, plus the synthetic `polity` row for
115
+ * the affiliation notes drawn beside their regions. The world is largest;
116
+ * regions large with a bold label; settlements small with a thin border;
117
+ * site, feature and structure between.
118
+ *
119
+ * @type {Readonly<Record<string, NodeStyle>>}
120
+ */
121
+ export const STYLE: Readonly<Record<string, NodeStyle>>;
122
+ /**
123
+ * One row of the style table.
124
+ */
125
+ export type NodeStyle = {
126
+ shape: string;
127
+ fill: string;
128
+ border: string;
129
+ fontsize: number;
130
+ /**
131
+ * - Inches, a minimum: a long label still grows the node.
132
+ */
133
+ width: number;
134
+ /**
135
+ * - Inches.
136
+ */
137
+ height: number;
138
+ penwidth: number;
139
+ /**
140
+ * - `rounded`, `dashed`, or none.
141
+ */
142
+ extraStyle: string;
143
+ };
@@ -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[];