@pieceful/ravel-host-node 0.1.1 → 0.2.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/LICENSE +15 -3
- package/README.md +74 -1
- package/package.json +35 -10
- package/src/index.d.ts +38 -4
- package/src/index.js +436 -47
package/LICENSE
CHANGED
|
@@ -2,8 +2,20 @@ MIT License
|
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2026 James Taylor
|
|
4
4
|
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
6
11
|
|
|
7
|
-
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
8
14
|
|
|
9
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @pieceful/ravel-host-node
|
|
2
2
|
|
|
3
|
-
The Node.js host for Ravel projects. It loads Markdown, JSON Ravel Maps, and
|
|
3
|
+
The Node.js host for [Ravel](https://github.com/jostylr/ravel) projects. It loads Markdown, JSON Ravel Maps, and
|
|
4
4
|
TOML project configuration; confines project filesystem access; and safely
|
|
5
5
|
writes planned deliverables, manifests, provenance maps, cleanups, and backups.
|
|
6
6
|
|
|
@@ -13,6 +13,79 @@ completed program, or use the higher-level `@pieceful/ravel` CLI. Inputs and
|
|
|
13
13
|
outputs are contained beneath an explicit project root; path escapes and
|
|
14
14
|
symlink traversal are rejected.
|
|
15
15
|
|
|
16
|
+
Editor hosts can evaluate unsaved project state by supplying absolute,
|
|
17
|
+
normalized in-memory overlays. Imported inputs and configuration consult the
|
|
18
|
+
overlay before disk:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
const loaded = await loadBuildInput("ravel.toml", {
|
|
22
|
+
overlays: new Map([
|
|
23
|
+
[absoluteMarkdownPath, { text: dirtyEditorText, version: 12 }],
|
|
24
|
+
]),
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Loading overlays never writes source, deliverables, manifests, provenance
|
|
29
|
+
sidecars, backups, or stale-output changes. Artifact writes remain separate,
|
|
30
|
+
explicit host operations.
|
|
31
|
+
|
|
32
|
+
`loadBuildInput` reports `loadedInputUris` for invalidation and separately
|
|
33
|
+
reports `authoredSourceUris` plus `authoredSourceTexts`. The text record contains
|
|
34
|
+
the exact overlay or disk text consumed for each authored non-JSON input, keyed
|
|
35
|
+
relative to the project root. Editor hosts can use it to prove that a newly
|
|
36
|
+
opened clean navigation destination still matches the evaluated snapshot; it
|
|
37
|
+
does not supply an editor version or write permission. A source path merely
|
|
38
|
+
asserted by JSON Ravel Map data is not added to the authored set, and a load
|
|
39
|
+
containing JSON map provenance reports `sourceEditsAllowed: false` for the
|
|
40
|
+
current conservative automatic-edit policy.
|
|
41
|
+
|
|
42
|
+
Version-1 TOML projects may declare text `[[live.resources]]` and installed
|
|
43
|
+
package exports in `[[live.modules]]`. The host validates and loads those
|
|
44
|
+
declarations; provider-specific package preparation remains outside this
|
|
45
|
+
general filesystem host.
|
|
46
|
+
|
|
47
|
+
`.qmd` inputs are parsed through the modern Markdown adapter before any Quarto
|
|
48
|
+
execution stage. Markdown entries in `ravel.toml` may select
|
|
49
|
+
`profile = "modern"` or retain the compatibility profile with
|
|
50
|
+
`profile = "fences"`.
|
|
51
|
+
|
|
52
|
+
Historical projects select the independent LitPro adapter with
|
|
53
|
+
`adapter = "markdown-litpro"` and may choose `dialect = "litpro-2017"`,
|
|
54
|
+
`"pieceful-2020"`, or `"litpro-plus"`. Legacy `load:` directives retain the
|
|
55
|
+
adapter and dialect while loading their declared input; parsing never performs
|
|
56
|
+
other legacy effects.
|
|
57
|
+
|
|
58
|
+
Noweb sources use `.nw` or `.noweb`, or may be selected explicitly with
|
|
59
|
+
`adapter = "noweb"`. TOML accepts `dialect = "noweb"` or `"noweb-plus"`,
|
|
60
|
+
`references = "noweb"`, `"underscore-quote"`, or `"both"`, and an optional
|
|
61
|
+
`language`. `run = true` and `provider = "..."` retain live execution intent
|
|
62
|
+
as chunk metadata; loading never executes a noweb chunk or writes tangled
|
|
63
|
+
output.
|
|
64
|
+
|
|
65
|
+
AsciiDoc sources use `.adoc`, `.asciidoc`, or `adapter = "asciidoc"`.
|
|
66
|
+
Section and block pieces retain exact source bodies, pipelines, native
|
|
67
|
+
cross-reference navigation, and `ravel::` graph directives. Loading does not
|
|
68
|
+
invoke Asciidoctor or expand includes.
|
|
69
|
+
|
|
70
|
+
HTML sources use `.html`, `.htm`, or `adapter = "html"`. Semantic sections and
|
|
71
|
+
figures retain visible names, decoded code, native navigation, pipelines, and
|
|
72
|
+
directive links. Loading uses authored source with scripting disabled; it
|
|
73
|
+
never constructs a browser DOM or observes runtime mutations.
|
|
74
|
+
|
|
75
|
+
Org sources use `.org` or `adapter = "org"`. TOML can select
|
|
76
|
+
`references = "org-noweb"`, `"underscore-quote"`, or `"both"`, enable
|
|
77
|
+
Ravel-only use-site pipes with `noweb_pipes = true`, and assign Babel
|
|
78
|
+
authority with `execution_owner = "org"` or `"ravel"`. The host retains
|
|
79
|
+
Babel header arguments, result artifacts, and tangle requests as data; loading
|
|
80
|
+
does not invoke Emacs or Babel.
|
|
81
|
+
|
|
82
|
+
MyST sources use `.myst.md` or `adapter = "myst"`. Labeled `{ravel:piece}`,
|
|
83
|
+
`{code}`, `{code-block}`, and `{code-cell}` directives retain visible
|
|
84
|
+
captions, anchors, exact bodies, cross-reference navigation, front matter, and
|
|
85
|
+
notebook tags. TOML may assign cell authority with
|
|
86
|
+
`execution_owner = "myst"` or `"ravel"`; loading invokes neither MyST nor
|
|
87
|
+
Jupyter.
|
|
88
|
+
|
|
16
89
|
Requires Node.js 22 or newer. This package is intentionally Node-specific;
|
|
17
90
|
browser and Bun hosts should supply their own I/O boundary around
|
|
18
91
|
`@pieceful/ravel-core`.
|
package/package.json
CHANGED
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pieceful/ravel-host-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Node host capabilities for Ravel.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "James Taylor",
|
|
8
|
-
"repository": {
|
|
9
|
-
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/jostylr/ravel.git",
|
|
11
|
+
"directory": "packages/host-node"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/jostylr/ravel/issues"
|
|
15
|
+
},
|
|
10
16
|
"homepage": "https://ravel.jostylr.com/",
|
|
11
|
-
"engines": {
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ravel",
|
|
22
|
+
"literate-programming",
|
|
23
|
+
"node",
|
|
24
|
+
"build"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
14
29
|
"exports": {
|
|
15
30
|
".": "./src/index.js"
|
|
16
31
|
},
|
|
17
32
|
"types": "./src/index.d.ts",
|
|
18
|
-
"files": [
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
19
38
|
"dependencies": {
|
|
20
|
-
"@pieceful/ravel-
|
|
21
|
-
"@pieceful/ravel-
|
|
22
|
-
"@pieceful/ravel-
|
|
39
|
+
"@pieceful/ravel-asciidoc": "0.2.0",
|
|
40
|
+
"@pieceful/ravel-core": "0.2.0",
|
|
41
|
+
"@pieceful/ravel-html": "0.2.0",
|
|
42
|
+
"@pieceful/ravel-markdown": "0.2.0",
|
|
43
|
+
"@pieceful/ravel-markdown-litpro": "0.2.0",
|
|
44
|
+
"@pieceful/ravel-map": "0.2.0",
|
|
45
|
+
"@pieceful/ravel-myst": "0.2.0",
|
|
46
|
+
"@pieceful/ravel-noweb": "0.2.0",
|
|
47
|
+
"@pieceful/ravel-org": "0.2.0",
|
|
23
48
|
"smol-toml": "^1.7.0"
|
|
24
49
|
}
|
|
25
50
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -3,11 +3,45 @@ export interface SourceRange { start: SourcePosition; end: SourcePosition; }
|
|
|
3
3
|
export interface SourceLocation { uri: string; range: SourceRange; }
|
|
4
4
|
export interface Diagnostic { code: string; severity: "error" | "warning" | "info"; message: string; source: SourceLocation; }
|
|
5
5
|
export interface RavelProgram { version?: number; deliverables: Record<string, { name: string; from: string; value: string; segments?: unknown[] }>; }
|
|
6
|
-
export interface
|
|
6
|
+
export interface LiveModuleDeclaration { specifier: string; from: string; source?: SourceLocation; }
|
|
7
|
+
export interface LiveResourceDeclaration { name: string; path: string; source?: SourceLocation; }
|
|
8
|
+
export interface LiveConfiguration { modules: LiveModuleDeclaration[]; resources: Record<string, string>; resourceDeclarations?: LiveResourceDeclaration[]; }
|
|
9
|
+
export interface BuildInput {
|
|
10
|
+
pretransform: unknown;
|
|
11
|
+
outputDirectory?: string;
|
|
12
|
+
rootDirectory: string;
|
|
13
|
+
/** Relative source documents actually parsed through a source adapter. */
|
|
14
|
+
authoredSourceUris: string[];
|
|
15
|
+
/** Exact authored text consumed by source adapters for this evaluation. */
|
|
16
|
+
authoredSourceTexts: Record<string, string>;
|
|
17
|
+
/** Relative JSON/markup/config inputs whose overlays invalidate analysis. */
|
|
18
|
+
loadedInputUris: string[];
|
|
19
|
+
/** False when any supplied JSON map makes automatic edit provenance unverified. */
|
|
20
|
+
sourceEditsAllowed: boolean;
|
|
21
|
+
buildOptions?: { clean: boolean; backup: boolean | string };
|
|
22
|
+
live?: LiveConfiguration;
|
|
23
|
+
}
|
|
24
|
+
export interface SourceAdapterOptions {
|
|
25
|
+
document?: string;
|
|
26
|
+
mode?: "opt-in" | "primary";
|
|
27
|
+
profile?: "fences" | "modern" | "litpro";
|
|
28
|
+
adapter?: "asciidoc" | "html" | "markdown" | "markdown-litpro" | "myst" | "noweb" | "org";
|
|
29
|
+
dialect?: "litpro-2017" | "pieceful-2020" | "litpro-plus" | "noweb" | "noweb-plus";
|
|
30
|
+
references?: "noweb" | "org-noweb" | "underscore-quote" | "both";
|
|
31
|
+
language?: string;
|
|
32
|
+
nowebPipes?: boolean;
|
|
33
|
+
executionOwner?: "org" | "myst" | "ravel";
|
|
34
|
+
run?: boolean;
|
|
35
|
+
provider?: string;
|
|
36
|
+
overlays?: Map<string, string | { text: string; version?: number }> | Record<string, string | { text: string; version?: number }>;
|
|
37
|
+
/** Keep live resource declarations but do not read their contents. Intended for editor analysis. */
|
|
38
|
+
readLiveResources?: boolean;
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}
|
|
7
41
|
export class RavelInputError extends Error { diagnostics: Diagnostic[]; }
|
|
8
|
-
export function loadPretransformGraph(entryPath: string, options?:
|
|
9
|
-
export function loadTomlBuild(configPath: string): Promise<BuildInput>;
|
|
10
|
-
export function loadBuildInput(inputPath: string, options?:
|
|
42
|
+
export function loadPretransformGraph(entryPath: string, options?: SourceAdapterOptions): Promise<unknown>;
|
|
43
|
+
export function loadTomlBuild(configPath: string, options?: SourceAdapterOptions): Promise<BuildInput>;
|
|
44
|
+
export function loadBuildInput(inputPath: string, options?: SourceAdapterOptions): Promise<BuildInput>;
|
|
11
45
|
export function planDeliverables(program: RavelProgram, outputDirectory: string): { version: 1; outputDirectory: string; manifest: string; deliverables: Array<Record<string, unknown>> };
|
|
12
46
|
export function planStaleDeliverables(program: RavelProgram, outputDirectory: string, options?: { rootDirectory?: string; staleSince?: string }): Promise<Array<Record<string, unknown>>>;
|
|
13
47
|
export function writeDeliverables(program: RavelProgram, outputDirectory: string, options?: { rootDirectory?: string }): Promise<string[]>;
|
package/src/index.js
CHANGED
|
@@ -8,11 +8,23 @@ import {
|
|
|
8
8
|
createDeliverableProvenanceMap,
|
|
9
9
|
provenanceMapVersion
|
|
10
10
|
} from "@pieceful/ravel-core";
|
|
11
|
+
import { asciidocToMap } from "@pieceful/ravel-asciidoc";
|
|
12
|
+
import { htmlToMap } from "@pieceful/ravel-html";
|
|
11
13
|
import { markdownToMap } from "@pieceful/ravel-markdown";
|
|
14
|
+
import { isLitproMarkdown, litproMarkdownToMap } from "@pieceful/ravel-markdown-litpro";
|
|
12
15
|
import { assertRavelMap } from "@pieceful/ravel-map";
|
|
16
|
+
import { mystToMap } from "@pieceful/ravel-myst";
|
|
17
|
+
import { nowebToMap } from "@pieceful/ravel-noweb";
|
|
18
|
+
import { orgToMap } from "@pieceful/ravel-org";
|
|
13
19
|
|
|
14
20
|
const missing = (error) => error?.code === "ENOENT";
|
|
15
21
|
|
|
22
|
+
const throwIfAborted = (signal) => {
|
|
23
|
+
if (!signal?.aborted) return;
|
|
24
|
+
if (signal.reason instanceof Error) throw signal.reason;
|
|
25
|
+
throw new DOMException("The Ravel input operation was cancelled.", "AbortError");
|
|
26
|
+
};
|
|
27
|
+
|
|
16
28
|
const inputSource = (uri) => ({
|
|
17
29
|
uri: typeof uri === "string" && uri.length ? uri : "<ravel-input>",
|
|
18
30
|
range: {
|
|
@@ -37,7 +49,25 @@ const inputError = (code, message, uri) => new RavelInputError([{
|
|
|
37
49
|
source: inputSource(uri)
|
|
38
50
|
}]);
|
|
39
51
|
|
|
40
|
-
const
|
|
52
|
+
const overlayText = (overlays, path) => {
|
|
53
|
+
if (!overlays) return undefined;
|
|
54
|
+
const key = resolve(path);
|
|
55
|
+
const entry = overlays instanceof Map ? overlays.get(key) : overlays[key];
|
|
56
|
+
if (typeof entry === "string") return entry;
|
|
57
|
+
return typeof entry?.text === "string" ? entry.text : undefined;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const overlaysWithText = (overlays, path, text) => {
|
|
61
|
+
const captured = overlays instanceof Map
|
|
62
|
+
? new Map(overlays)
|
|
63
|
+
: new Map(Object.entries(overlays ?? {}).map(([key, value]) => [resolve(key), value]));
|
|
64
|
+
captured.set(resolve(path), { text });
|
|
65
|
+
return captured;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const readInputText = async (path, description, code, overlays) => {
|
|
69
|
+
const overlay = overlayText(overlays, path);
|
|
70
|
+
if (overlay !== undefined) return overlay;
|
|
41
71
|
try {
|
|
42
72
|
return await readFile(path, "utf8");
|
|
43
73
|
} catch (error) {
|
|
@@ -170,8 +200,8 @@ const createOutputScope = async (outputDirectory, rootDirectory) => {
|
|
|
170
200
|
return { root: output, path: (path, description) => scopedPath(output, path, description) };
|
|
171
201
|
};
|
|
172
202
|
|
|
173
|
-
const readMap = async (path) => {
|
|
174
|
-
const text = await readInputText(path, "Ravel Map", "RM201");
|
|
203
|
+
const readMap = async (path, overlays) => {
|
|
204
|
+
const text = await readInputText(path, "Ravel Map", "RM201", overlays);
|
|
175
205
|
let map;
|
|
176
206
|
try {
|
|
177
207
|
map = JSON.parse(text);
|
|
@@ -181,56 +211,235 @@ const readMap = async (path) => {
|
|
|
181
211
|
return assertRavelMap(map, { uri: path });
|
|
182
212
|
};
|
|
183
213
|
|
|
184
|
-
const loadMarkdownFile = async (path, options = {}) =>
|
|
185
|
-
await readInputText(path, "Markdown input", "RM201")
|
|
186
|
-
|
|
187
|
-
)
|
|
214
|
+
const loadMarkdownFile = async (path, options = {}, overlays) => {
|
|
215
|
+
const text = await readInputText(path, "Markdown input", "RM201", overlays);
|
|
216
|
+
if (options.adapter === "markdown-litpro" || options.profile === "litpro" ||
|
|
217
|
+
(options.adapter === undefined && options.profile === undefined && isLitproMarkdown(text))) {
|
|
218
|
+
return litproMarkdownToMap(text, {
|
|
219
|
+
uri: path,
|
|
220
|
+
document: options.document,
|
|
221
|
+
dialect: options.dialect,
|
|
222
|
+
headings: options.headings
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return markdownToMap(text, {
|
|
226
|
+
uri: path,
|
|
227
|
+
document: options.document,
|
|
228
|
+
mode: options.mode,
|
|
229
|
+
profile: options.profile
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const loadNowebFile = async (path, options = {}, overlays) => {
|
|
234
|
+
const text = await readInputText(path, "noweb input", "RM201", overlays);
|
|
235
|
+
return nowebToMap(text, {
|
|
236
|
+
uri: path,
|
|
237
|
+
document: options.document,
|
|
238
|
+
dialect: options.dialect,
|
|
239
|
+
references: options.references,
|
|
240
|
+
language: options.language,
|
|
241
|
+
run: options.run,
|
|
242
|
+
provider: options.provider
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const loadOrgFile = async (path, options = {}, overlays) => {
|
|
247
|
+
const text = await readInputText(path, "Org input", "RM201", overlays);
|
|
248
|
+
return orgToMap(text, {
|
|
249
|
+
uri: path,
|
|
250
|
+
document: options.document,
|
|
251
|
+
references: options.references,
|
|
252
|
+
nowebPipes: options.nowebPipes,
|
|
253
|
+
executionOwner: options.executionOwner,
|
|
254
|
+
run: options.run,
|
|
255
|
+
provider: options.provider
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const loadMystFile = async (path, options = {}, overlays) => {
|
|
260
|
+
const text = await readInputText(path, "MyST input", "RM201", overlays);
|
|
261
|
+
return mystToMap(text, {
|
|
262
|
+
uri: path,
|
|
263
|
+
document: options.document,
|
|
264
|
+
executionOwner: options.executionOwner,
|
|
265
|
+
run: options.run,
|
|
266
|
+
provider: options.provider
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const loadAsciiDocFile = async (path, options = {}, overlays) => {
|
|
271
|
+
const text = await readInputText(path, "AsciiDoc input", "RM201", overlays);
|
|
272
|
+
return asciidocToMap(text, {
|
|
273
|
+
uri: path,
|
|
274
|
+
document: options.document,
|
|
275
|
+
run: options.run,
|
|
276
|
+
provider: options.provider
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const loadHtmlFile = async (path, options = {}, overlays) => {
|
|
281
|
+
const text = await readInputText(path, "HTML input", "RM201", overlays);
|
|
282
|
+
return htmlToMap(text, {
|
|
283
|
+
uri: path,
|
|
284
|
+
document: options.document,
|
|
285
|
+
run: options.run,
|
|
286
|
+
provider: options.provider
|
|
287
|
+
});
|
|
288
|
+
};
|
|
188
289
|
|
|
189
|
-
const collectPretransformMaps = async (
|
|
290
|
+
const collectPretransformMaps = async (
|
|
291
|
+
entryPath,
|
|
292
|
+
entryOptions = {},
|
|
293
|
+
scope,
|
|
294
|
+
overlays = entryOptions.overlays
|
|
295
|
+
) => {
|
|
296
|
+
const signal = entryOptions.signal;
|
|
297
|
+
throwIfAborted(signal);
|
|
190
298
|
const activeScope = scope ?? await createInputScope(dirname(resolve(entryPath)));
|
|
191
299
|
const visited = new Set();
|
|
192
300
|
const maps = [];
|
|
193
301
|
const diagnostics = [];
|
|
302
|
+
const authoredSourcePaths = new Set();
|
|
303
|
+
const authoredSourceTexts = new Map();
|
|
304
|
+
let containsJsonMaps = false;
|
|
194
305
|
|
|
195
306
|
const visit = async (path, options = {}) => {
|
|
307
|
+
throwIfAborted(signal);
|
|
196
308
|
const absolutePath = await activeScope.path(path, "Ravel input");
|
|
197
309
|
if (visited.has(absolutePath)) return;
|
|
198
310
|
visited.add(absolutePath);
|
|
199
311
|
|
|
200
312
|
const extension = extname(absolutePath).toLowerCase();
|
|
201
313
|
let map;
|
|
314
|
+
let sourceOverlays = overlays;
|
|
315
|
+
let sourceText;
|
|
316
|
+
const mystExtension = absolutePath.toLowerCase().endsWith(".myst.md");
|
|
202
317
|
if (extension === ".json") {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
318
|
+
containsJsonMaps = true;
|
|
319
|
+
map = await readMap(absolutePath, overlays);
|
|
320
|
+
} else if (mystExtension || options.adapter === "myst") {
|
|
321
|
+
authoredSourcePaths.add(absolutePath);
|
|
322
|
+
sourceText = await readInputText(absolutePath, "MyST input", "RM201", overlays);
|
|
323
|
+
sourceOverlays = overlaysWithText(overlays, absolutePath, sourceText);
|
|
324
|
+
const result = await loadMystFile(absolutePath, options, sourceOverlays);
|
|
325
|
+
map = result.map;
|
|
326
|
+
diagnostics.push(...result.diagnostics);
|
|
327
|
+
assertRavelMap(map, { uri: absolutePath });
|
|
328
|
+
} else if (extension === ".adoc" || extension === ".asciidoc" ||
|
|
329
|
+
options.adapter === "asciidoc") {
|
|
330
|
+
authoredSourcePaths.add(absolutePath);
|
|
331
|
+
sourceText = await readInputText(absolutePath, "AsciiDoc input", "RM201", overlays);
|
|
332
|
+
sourceOverlays = overlaysWithText(overlays, absolutePath, sourceText);
|
|
333
|
+
const result = await loadAsciiDocFile(absolutePath, options, sourceOverlays);
|
|
334
|
+
map = result.map;
|
|
335
|
+
diagnostics.push(...result.diagnostics);
|
|
336
|
+
assertRavelMap(map, { uri: absolutePath });
|
|
337
|
+
} else if (extension === ".html" || extension === ".htm" ||
|
|
338
|
+
options.adapter === "html") {
|
|
339
|
+
authoredSourcePaths.add(absolutePath);
|
|
340
|
+
sourceText = await readInputText(absolutePath, "HTML input", "RM201", overlays);
|
|
341
|
+
sourceOverlays = overlaysWithText(overlays, absolutePath, sourceText);
|
|
342
|
+
const result = await loadHtmlFile(absolutePath, options, sourceOverlays);
|
|
343
|
+
map = result.map;
|
|
344
|
+
diagnostics.push(...result.diagnostics);
|
|
345
|
+
assertRavelMap(map, { uri: absolutePath });
|
|
346
|
+
} else if (extension === ".org" || options.adapter === "org") {
|
|
347
|
+
authoredSourcePaths.add(absolutePath);
|
|
348
|
+
sourceText = await readInputText(absolutePath, "Org input", "RM201", overlays);
|
|
349
|
+
sourceOverlays = overlaysWithText(overlays, absolutePath, sourceText);
|
|
350
|
+
const result = await loadOrgFile(absolutePath, options, sourceOverlays);
|
|
351
|
+
map = result.map;
|
|
352
|
+
diagnostics.push(...result.diagnostics);
|
|
353
|
+
assertRavelMap(map, { uri: absolutePath });
|
|
354
|
+
} else if (extension === ".nw" || extension === ".noweb" || options.adapter === "noweb") {
|
|
355
|
+
authoredSourcePaths.add(absolutePath);
|
|
356
|
+
sourceText = await readInputText(absolutePath, "noweb input", "RM201", overlays);
|
|
357
|
+
sourceOverlays = overlaysWithText(overlays, absolutePath, sourceText);
|
|
358
|
+
const result = await loadNowebFile(absolutePath, options, sourceOverlays);
|
|
359
|
+
map = result.map;
|
|
360
|
+
diagnostics.push(...result.diagnostics);
|
|
361
|
+
assertRavelMap(map, { uri: absolutePath });
|
|
362
|
+
} else if (extension === ".md" || extension === ".markdown" || extension === ".mdown" || extension === ".qmd") {
|
|
363
|
+
authoredSourcePaths.add(absolutePath);
|
|
364
|
+
sourceText = await readInputText(absolutePath, "Markdown input", "RM201", overlays);
|
|
365
|
+
sourceOverlays = overlaysWithText(overlays, absolutePath, sourceText);
|
|
366
|
+
const result = await loadMarkdownFile(absolutePath, {
|
|
367
|
+
...options,
|
|
368
|
+
profile: options.profile ?? (extension === ".qmd" ? "modern" : undefined)
|
|
369
|
+
}, sourceOverlays);
|
|
206
370
|
map = result.map;
|
|
207
371
|
diagnostics.push(...result.diagnostics);
|
|
208
372
|
assertRavelMap(map, { uri: absolutePath });
|
|
209
373
|
} else {
|
|
210
|
-
throw inputError("RH101", "Ravel input must be a .json map or
|
|
374
|
+
throw inputError("RH101", "Ravel input must be a .json map or supported markup source.", absolutePath);
|
|
211
375
|
}
|
|
376
|
+
if (sourceText !== undefined) authoredSourceTexts.set(absolutePath, sourceText);
|
|
377
|
+
throwIfAborted(signal);
|
|
212
378
|
for (const directive of map.directives ?? []) {
|
|
213
379
|
if (directive.kind !== "in") continue;
|
|
214
380
|
const target = directive.target ?? directive.name;
|
|
215
381
|
if (typeof target !== "string" || !target) {
|
|
216
382
|
throw inputError("RH102", "in directive requires a target path.", directive.source?.uri ?? absolutePath);
|
|
217
383
|
}
|
|
218
|
-
|
|
384
|
+
const adapter = directive.metadata?.adapter;
|
|
385
|
+
const alias = directive.metadata?.legacy?.alias;
|
|
386
|
+
await visit(resolve(dirname(absolutePath), target), {
|
|
387
|
+
...(adapter ? { adapter } : {}),
|
|
388
|
+
...(directive.metadata?.dialect ? { dialect: directive.metadata.dialect } : {}),
|
|
389
|
+
...(typeof alias === "string" && alias ? { document: alias } : {})
|
|
390
|
+
});
|
|
219
391
|
}
|
|
220
392
|
maps.push(map);
|
|
221
393
|
};
|
|
222
394
|
|
|
223
395
|
await visit(entryPath, entryOptions);
|
|
224
|
-
|
|
396
|
+
throwIfAborted(signal);
|
|
397
|
+
return {
|
|
398
|
+
maps,
|
|
399
|
+
diagnostics,
|
|
400
|
+
rootDirectory: activeScope.root,
|
|
401
|
+
authoredSourcePaths: [...authoredSourcePaths].sort(),
|
|
402
|
+
authoredSourceTexts,
|
|
403
|
+
loadedInputPaths: [...visited].sort(),
|
|
404
|
+
containsJsonMaps
|
|
405
|
+
};
|
|
225
406
|
};
|
|
226
407
|
|
|
227
|
-
|
|
228
|
-
const collected = await collectPretransformMaps(resolve(entryPath), options);
|
|
408
|
+
const graphFromCollectedMaps = (collected) => {
|
|
229
409
|
const graph = combineMaps(collected.maps);
|
|
230
410
|
graph.diagnostics.push(...collected.diagnostics);
|
|
231
411
|
return relativizeSourceUris(graph, collected.rootDirectory);
|
|
232
412
|
};
|
|
233
413
|
|
|
414
|
+
const authoredSourceUris = (collected) => collected.authoredSourcePaths
|
|
415
|
+
.map((path) => relative(collected.rootDirectory, path).split(sep).join("/"))
|
|
416
|
+
.sort();
|
|
417
|
+
|
|
418
|
+
const loadedInputUris = (collected) => collected.loadedInputPaths
|
|
419
|
+
.map((path) => relative(collected.rootDirectory, path).split(sep).join("/"))
|
|
420
|
+
.sort();
|
|
421
|
+
|
|
422
|
+
const authoredSourceTexts = (collected) => Object.fromEntries(
|
|
423
|
+
[...collected.authoredSourceTexts.entries()]
|
|
424
|
+
.map(([path, text]) => [
|
|
425
|
+
relative(collected.rootDirectory, path).split(sep).join("/"),
|
|
426
|
+
text
|
|
427
|
+
])
|
|
428
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
export const loadPretransformGraph = async (entryPath, options = {}) => {
|
|
432
|
+
throwIfAborted(options.signal);
|
|
433
|
+
const collected = await collectPretransformMaps(
|
|
434
|
+
resolve(entryPath),
|
|
435
|
+
options,
|
|
436
|
+
undefined,
|
|
437
|
+
options.overlays
|
|
438
|
+
);
|
|
439
|
+
throwIfAborted(options.signal);
|
|
440
|
+
return graphFromCollectedMaps(collected);
|
|
441
|
+
};
|
|
442
|
+
|
|
234
443
|
const configSource = inputSource;
|
|
235
444
|
|
|
236
445
|
const requireConfigString = (value, description, configPath) => {
|
|
@@ -246,48 +455,213 @@ const reportUnknownKeys = (value, allowed, description, configPath) => {
|
|
|
246
455
|
}
|
|
247
456
|
};
|
|
248
457
|
|
|
249
|
-
|
|
458
|
+
const loadLiveConfiguration = async (
|
|
459
|
+
live,
|
|
460
|
+
scope,
|
|
461
|
+
configPath,
|
|
462
|
+
overlays,
|
|
463
|
+
{ readResources = true } = {}
|
|
464
|
+
) => {
|
|
465
|
+
if (live === undefined) return undefined;
|
|
466
|
+
if (!live || typeof live !== "object" || Array.isArray(live)) {
|
|
467
|
+
throw inputError("RC102", "live must be a [live] table.", configPath);
|
|
468
|
+
}
|
|
469
|
+
reportUnknownKeys(live, new Set(["modules", "resources"]), "live", configPath);
|
|
470
|
+
if (live.modules !== undefined && !Array.isArray(live.modules)) {
|
|
471
|
+
throw inputError("RC102", "live.modules must contain [[live.modules]] entries.", configPath);
|
|
472
|
+
}
|
|
473
|
+
if (live.resources !== undefined && !Array.isArray(live.resources)) {
|
|
474
|
+
throw inputError("RC102", "live.resources must contain [[live.resources]] entries.", configPath);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const moduleSpecifiers = new Set();
|
|
478
|
+
const modules = (live.modules ?? []).map((entry, index) => {
|
|
479
|
+
const description = "live.modules[" + index + "]";
|
|
480
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
481
|
+
throw inputError("RC102", description + " must be a table.", configPath);
|
|
482
|
+
}
|
|
483
|
+
reportUnknownKeys(entry, new Set(["specifier", "from"]), description, configPath);
|
|
484
|
+
const specifier = requireConfigString(entry.specifier, description + ".specifier", configPath);
|
|
485
|
+
const from = requireConfigString(entry.from, description + ".from", configPath);
|
|
486
|
+
if (moduleSpecifiers.has(specifier)) {
|
|
487
|
+
throw inputError("RC102", "Duplicate live module specifier: " + specifier, configPath);
|
|
488
|
+
}
|
|
489
|
+
moduleSpecifiers.add(specifier);
|
|
490
|
+
return { specifier, from, source: configSource(configPath) };
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const resourceNames = new Set();
|
|
494
|
+
const resourceResults = await Promise.all((live.resources ?? []).map(async (entry, index) => {
|
|
495
|
+
const description = "live.resources[" + index + "]";
|
|
496
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
497
|
+
throw inputError("RC102", description + " must be a table.", configPath);
|
|
498
|
+
}
|
|
499
|
+
reportUnknownKeys(entry, new Set(["name", "path"]), description, configPath);
|
|
500
|
+
const name = requireConfigString(entry.name, description + ".name", configPath);
|
|
501
|
+
const path = requireConfigString(entry.path, description + ".path", configPath);
|
|
502
|
+
if (resourceNames.has(name)) {
|
|
503
|
+
throw inputError("RC102", "Duplicate live resource name: " + name, configPath);
|
|
504
|
+
}
|
|
505
|
+
resourceNames.add(name);
|
|
506
|
+
const absolutePath = await scope.path(path, description + ".path");
|
|
507
|
+
const declaration = { name, path, source: configSource(configPath) };
|
|
508
|
+
if (!readResources) return { declaration };
|
|
509
|
+
return {
|
|
510
|
+
declaration,
|
|
511
|
+
entry: [name, await readInputText(
|
|
512
|
+
absolutePath,
|
|
513
|
+
"live resource",
|
|
514
|
+
"RC103",
|
|
515
|
+
overlays
|
|
516
|
+
)]
|
|
517
|
+
};
|
|
518
|
+
}));
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
modules,
|
|
522
|
+
resources: Object.fromEntries(resourceResults.flatMap((result) =>
|
|
523
|
+
result.entry ? [result.entry] : []
|
|
524
|
+
)),
|
|
525
|
+
resourceDeclarations: resourceResults.map(({ declaration }) => declaration)
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
export const loadTomlBuild = async (configPath, options = {}) => {
|
|
530
|
+
throwIfAborted(options.signal);
|
|
250
531
|
const absoluteConfig = resolve(configPath);
|
|
251
532
|
const scope = await createInputScope(dirname(absoluteConfig));
|
|
252
533
|
const configFile = await scope.path(absoluteConfig, "Ravel TOML config");
|
|
253
534
|
let config;
|
|
254
535
|
try {
|
|
255
|
-
config = parseToml(await readInputText(
|
|
536
|
+
config = parseToml(await readInputText(
|
|
537
|
+
configFile,
|
|
538
|
+
"Ravel TOML config",
|
|
539
|
+
"RC101",
|
|
540
|
+
options.overlays
|
|
541
|
+
));
|
|
542
|
+
throwIfAborted(options.signal);
|
|
256
543
|
} catch (error) {
|
|
257
544
|
if (Array.isArray(error?.diagnostics)) throw error;
|
|
258
545
|
throw inputError("RC101", "Invalid Ravel TOML config: " + (error?.message ?? String(error)), absoluteConfig);
|
|
259
546
|
}
|
|
260
|
-
reportUnknownKeys(config, new Set(["version", "files", "build", "outputs"]), "config", absoluteConfig);
|
|
547
|
+
reportUnknownKeys(config, new Set(["version", "files", "build", "outputs", "live"]), "config", absoluteConfig);
|
|
261
548
|
if (config.version !== 1) throw inputError("RC102", "version must be 1.", absoluteConfig);
|
|
262
549
|
if (!Array.isArray(config.files) || config.files.length === 0) {
|
|
263
550
|
throw inputError("RC102", "files must contain one or more [[files]] entries.", absoluteConfig);
|
|
264
551
|
}
|
|
265
|
-
if (!config.build || typeof config.build !== "object") {
|
|
552
|
+
if (config.build !== undefined && (!config.build || typeof config.build !== "object" || Array.isArray(config.build))) {
|
|
266
553
|
throw inputError("RC102", "build must be a [build] table.", absoluteConfig);
|
|
267
554
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (
|
|
555
|
+
const build = config.build ?? {};
|
|
556
|
+
reportUnknownKeys(build, new Set(["name", "out_dir", "clean", "backup"]), "build", absoluteConfig);
|
|
557
|
+
if (build.name !== undefined) requireConfigString(build.name, "build.name", absoluteConfig);
|
|
558
|
+
if (build.clean !== undefined && typeof build.clean !== "boolean") {
|
|
271
559
|
throw inputError("RC102", "build.clean must be true or false.", absoluteConfig);
|
|
272
560
|
}
|
|
273
|
-
if (
|
|
274
|
-
(typeof
|
|
561
|
+
if (build.backup !== undefined && typeof build.backup !== "boolean" &&
|
|
562
|
+
(typeof build.backup !== "string" || build.backup.length === 0)) {
|
|
275
563
|
throw inputError("RC102", "build.backup must be true, false, or a non-empty .zip path.", absoluteConfig);
|
|
276
564
|
}
|
|
277
|
-
if (typeof
|
|
565
|
+
if (typeof build.backup === "string" && extname(build.backup).toLowerCase() !== ".zip") {
|
|
278
566
|
throw inputError("RC102", "build.backup must name a .zip file.", absoluteConfig);
|
|
279
567
|
}
|
|
280
|
-
const outDirectory =
|
|
568
|
+
const outDirectory = build.out_dir === undefined
|
|
569
|
+
? undefined
|
|
570
|
+
: requireConfigString(build.out_dir, "build.out_dir", absoluteConfig);
|
|
281
571
|
const baseDirectory = scope.root;
|
|
572
|
+
const live = await loadLiveConfiguration(
|
|
573
|
+
config.live,
|
|
574
|
+
scope,
|
|
575
|
+
absoluteConfig,
|
|
576
|
+
options.overlays,
|
|
577
|
+
{ readResources: options.readLiveResources !== false }
|
|
578
|
+
);
|
|
282
579
|
const results = await Promise.all(config.files.map(async (file, index) => {
|
|
283
580
|
if (!file || typeof file !== "object") throw inputError("RC102", "files[" + index + "] must be a table.", absoluteConfig);
|
|
284
|
-
reportUnknownKeys(file, new Set([
|
|
581
|
+
reportUnknownKeys(file, new Set([
|
|
582
|
+
"path", "document", "mode", "profile", "adapter", "dialect",
|
|
583
|
+
"references", "language", "noweb_pipes", "execution_owner", "run", "provider"
|
|
584
|
+
]), "files[" + index + "]", absoluteConfig);
|
|
285
585
|
const path = requireConfigString(file.path, "files[" + index + "].path", absoluteConfig);
|
|
286
586
|
if (file.document !== undefined) requireConfigString(file.document, "files[" + index + "].document", absoluteConfig);
|
|
287
587
|
const mode = file.mode ?? "opt-in";
|
|
288
588
|
if (!["opt-in", "primary"].includes(mode)) throw inputError("RC102", "files[" + index + "].mode must be opt-in or primary.", absoluteConfig);
|
|
289
|
-
|
|
589
|
+
const profile = file.profile;
|
|
590
|
+
if (profile !== undefined && !["fences", "modern", "litpro"].includes(profile)) {
|
|
591
|
+
throw inputError("RC102", "files[" + index + "].profile must be fences, modern, or litpro.", absoluteConfig);
|
|
592
|
+
}
|
|
593
|
+
const adapter = file.adapter;
|
|
594
|
+
if (adapter !== undefined && !["asciidoc", "html", "markdown", "markdown-litpro", "myst", "noweb", "org"].includes(adapter)) {
|
|
595
|
+
throw inputError("RC102", "files[" + index + "].adapter must be asciidoc, html, markdown, markdown-litpro, myst, noweb, or org.", absoluteConfig);
|
|
596
|
+
}
|
|
597
|
+
const dialect = file.dialect;
|
|
598
|
+
const supportedDialects = adapter === "noweb"
|
|
599
|
+
? ["noweb", "noweb-plus"]
|
|
600
|
+
: adapter === "asciidoc" || adapter === "html" ||
|
|
601
|
+
adapter === "org" || adapter === "myst"
|
|
602
|
+
? []
|
|
603
|
+
: ["litpro-2017", "pieceful-2020", "litpro-plus"];
|
|
604
|
+
if (dialect !== undefined && !supportedDialects.includes(dialect)) {
|
|
605
|
+
throw inputError("RC102", "files[" + index + "].dialect is not supported by its adapter.", absoluteConfig);
|
|
606
|
+
}
|
|
607
|
+
const references = file.references;
|
|
608
|
+
const supportedReferences = adapter === "org"
|
|
609
|
+
? ["org-noweb", "underscore-quote", "both"]
|
|
610
|
+
: ["noweb", "underscore-quote", "both"];
|
|
611
|
+
if (references !== undefined && !supportedReferences.includes(references)) {
|
|
612
|
+
throw inputError("RC102", "files[" + index + "].references is not supported by its adapter.", absoluteConfig);
|
|
613
|
+
}
|
|
614
|
+
if (file.language !== undefined) requireConfigString(file.language, "files[" + index + "].language", absoluteConfig);
|
|
615
|
+
if (file.noweb_pipes !== undefined && typeof file.noweb_pipes !== "boolean") {
|
|
616
|
+
throw inputError("RC102", "files[" + index + "].noweb_pipes must be true or false.", absoluteConfig);
|
|
617
|
+
}
|
|
618
|
+
const executionOwner = file.execution_owner;
|
|
619
|
+
const supportedExecutionOwners = adapter === "myst"
|
|
620
|
+
? ["myst", "ravel"]
|
|
621
|
+
: ["org", "ravel"];
|
|
622
|
+
if (executionOwner !== undefined && !supportedExecutionOwners.includes(executionOwner)) {
|
|
623
|
+
throw inputError(
|
|
624
|
+
"RC102",
|
|
625
|
+
"files[" + index + "].execution_owner is not supported by its adapter.",
|
|
626
|
+
absoluteConfig
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (file.run !== undefined && typeof file.run !== "boolean") {
|
|
630
|
+
throw inputError("RC102", "files[" + index + "].run must be true or false.", absoluteConfig);
|
|
631
|
+
}
|
|
632
|
+
if (file.provider !== undefined) requireConfigString(file.provider, "files[" + index + "].provider", absoluteConfig);
|
|
633
|
+
if (file.language !== undefined && adapter !== "noweb") {
|
|
634
|
+
throw inputError("RC102", "files[" + index + "].language currently requires adapter = \"noweb\".", absoluteConfig);
|
|
635
|
+
}
|
|
636
|
+
if (file.noweb_pipes !== undefined && adapter !== "org") {
|
|
637
|
+
throw inputError("RC102", "noweb_pipes requires adapter = \"org\".", absoluteConfig);
|
|
638
|
+
}
|
|
639
|
+
if (executionOwner !== undefined && !["org", "myst"].includes(adapter)) {
|
|
640
|
+
throw inputError("RC102", "execution_owner requires adapter = \"org\" or \"myst\".", absoluteConfig);
|
|
641
|
+
}
|
|
642
|
+
if (references !== undefined && !["noweb", "org"].includes(adapter)) {
|
|
643
|
+
throw inputError("RC102", "references requires adapter = \"noweb\" or \"org\".", absoluteConfig);
|
|
644
|
+
}
|
|
645
|
+
if ((file.run !== undefined || file.provider !== undefined) &&
|
|
646
|
+
!["asciidoc", "html", "noweb", "org", "myst"].includes(adapter)) {
|
|
647
|
+
throw inputError("RC102", "run and provider settings require adapter = \"asciidoc\", \"html\", \"noweb\", \"org\", or \"myst\".", absoluteConfig);
|
|
648
|
+
}
|
|
649
|
+
return collectPretransformMaps(resolve(baseDirectory, path), {
|
|
650
|
+
document: file.document,
|
|
651
|
+
mode,
|
|
652
|
+
profile,
|
|
653
|
+
adapter,
|
|
654
|
+
dialect,
|
|
655
|
+
references,
|
|
656
|
+
language: file.language,
|
|
657
|
+
nowebPipes: file.noweb_pipes,
|
|
658
|
+
executionOwner,
|
|
659
|
+
run: file.run,
|
|
660
|
+
provider: file.provider,
|
|
661
|
+
signal: options.signal
|
|
662
|
+
}, scope, options.overlays);
|
|
290
663
|
}));
|
|
664
|
+
throwIfAborted(options.signal);
|
|
291
665
|
const pretransform = combineMaps(results.flatMap((result) => result.maps));
|
|
292
666
|
pretransform.diagnostics.push(...results.flatMap((result) => result.diagnostics));
|
|
293
667
|
for (const output of config.outputs ?? []) {
|
|
@@ -302,35 +676,50 @@ export const loadTomlBuild = async (configPath) => {
|
|
|
302
676
|
}
|
|
303
677
|
return {
|
|
304
678
|
pretransform: relativizeSourceUris(pretransform, scope.root),
|
|
305
|
-
|
|
679
|
+
authoredSourceUris: [...new Set(results.flatMap(authoredSourceUris))].sort(),
|
|
680
|
+
authoredSourceTexts: Object.assign({}, ...results.map(authoredSourceTexts)),
|
|
681
|
+
loadedInputUris: [...new Set([
|
|
682
|
+
relative(scope.root, absoluteConfig).split(sep).join("/"),
|
|
683
|
+
...results.flatMap(loadedInputUris)
|
|
684
|
+
])].sort(),
|
|
685
|
+
sourceEditsAllowed: results.every((result) => !result.containsJsonMaps),
|
|
686
|
+
...(outDirectory === undefined ? {} : { outputDirectory: await scope.path(outDirectory, "build.out_dir") }),
|
|
306
687
|
rootDirectory: scope.root,
|
|
307
688
|
buildOptions: {
|
|
308
|
-
clean:
|
|
309
|
-
backup:
|
|
310
|
-
}
|
|
689
|
+
clean: build.clean === true,
|
|
690
|
+
backup: build.backup ?? false
|
|
691
|
+
},
|
|
692
|
+
...(live ? { live } : {})
|
|
311
693
|
};
|
|
312
694
|
};
|
|
313
695
|
|
|
314
|
-
/** Load a JSON map, one
|
|
696
|
+
/** Load a JSON map, one markup source document, or a Ravel TOML build run. */
|
|
315
697
|
export const loadBuildInput = async (inputPath, options = {}) => {
|
|
698
|
+
throwIfAborted(options.signal);
|
|
316
699
|
const extension = extname(inputPath).toLowerCase();
|
|
317
|
-
if (extension === ".toml") return loadTomlBuild(inputPath);
|
|
318
|
-
if (extension === ".md" || extension === ".markdown" || extension === ".mdown"
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
700
|
+
if (extension === ".toml") return loadTomlBuild(inputPath, options);
|
|
701
|
+
if (extension === ".md" || extension === ".markdown" || extension === ".mdown" || extension === ".qmd" ||
|
|
702
|
+
extension === ".nw" || extension === ".noweb" || extension === ".org" ||
|
|
703
|
+
extension === ".adoc" || extension === ".asciidoc" ||
|
|
704
|
+
extension === ".html" || extension === ".htm" || extension === ".json") {
|
|
705
|
+
const collected = await collectPretransformMaps(
|
|
706
|
+
resolve(inputPath),
|
|
707
|
+
options,
|
|
708
|
+
undefined,
|
|
709
|
+
options.overlays
|
|
710
|
+
);
|
|
711
|
+
throwIfAborted(options.signal);
|
|
327
712
|
return {
|
|
328
|
-
pretransform:
|
|
713
|
+
pretransform: graphFromCollectedMaps(collected),
|
|
329
714
|
outputDirectory: undefined,
|
|
330
|
-
rootDirectory:
|
|
715
|
+
rootDirectory: collected.rootDirectory,
|
|
716
|
+
authoredSourceUris: authoredSourceUris(collected),
|
|
717
|
+
authoredSourceTexts: authoredSourceTexts(collected),
|
|
718
|
+
loadedInputUris: loadedInputUris(collected),
|
|
719
|
+
sourceEditsAllowed: !collected.containsJsonMaps
|
|
331
720
|
};
|
|
332
721
|
}
|
|
333
|
-
throw inputError("RH101", "Ravel input must be a .json map,
|
|
722
|
+
throw inputError("RH101", "Ravel input must be a .json map, supported markup document, or .toml build config.", inputPath);
|
|
334
723
|
};
|
|
335
724
|
|
|
336
725
|
const safeDestination = (outputDirectory, name) => {
|