@pieceful/ravel 0.1.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 +9 -0
- package/package.json +25 -0
- package/src/.gitkeep +1 -0
- package/src/index.js +440 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Taylor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pieceful/ravel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Ravel command-line interface.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "James Taylor",
|
|
8
|
+
"repository": { "type": "git", "url": "git+https://github.com/jostylr/ravel.git", "directory": "packages/cli" },
|
|
9
|
+
"bugs": { "url": "https://github.com/jostylr/ravel/issues" },
|
|
10
|
+
"homepage": "https://github.com/jostylr/ravel#readme",
|
|
11
|
+
"engines": { "node": ">=22" },
|
|
12
|
+
"keywords": ["ravel", "literate-programming", "cli", "build"],
|
|
13
|
+
"publishConfig": { "access": "public" },
|
|
14
|
+
"bin": {
|
|
15
|
+
"ravel": "./src/index.js"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./src/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": ["src", "LICENSE"],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@pieceful/ravel-core": "0.1.0",
|
|
23
|
+
"@pieceful/ravel-host-node": "0.1.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/src/index.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cleanManagedArtifacts, createOutputBackup, loadBuildInput, planDeliverables, planOutputBackup, planStaleDeliverables, refreshStaleArtifacts, writeBuildArtifacts, writeGraph } from "@pieceful/ravel-host-node";
|
|
3
|
+
import {
|
|
4
|
+
createDeliverableProvenanceMap,
|
|
5
|
+
explainGeneratedOffset,
|
|
6
|
+
generatedRangesForSource,
|
|
7
|
+
transformGraph
|
|
8
|
+
} from "@pieceful/ravel-core";
|
|
9
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
10
|
+
import { extname, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const EXIT_SOURCE = 1;
|
|
14
|
+
const EXIT_USAGE = 2;
|
|
15
|
+
const EXIT_INTERNAL = 3;
|
|
16
|
+
const RAVEL_VERSION = "0.1.0";
|
|
17
|
+
const valueOptions = new Set([
|
|
18
|
+
"--config",
|
|
19
|
+
"--out-dir",
|
|
20
|
+
"--document",
|
|
21
|
+
"--mode",
|
|
22
|
+
"--provenance",
|
|
23
|
+
"--generated-offset",
|
|
24
|
+
"--source-uri",
|
|
25
|
+
"--source-offset"
|
|
26
|
+
]);
|
|
27
|
+
const booleanOptions = new Set(["--json", "--dry-run", "--debug", "--chunks", "--trace", "--clean"]);
|
|
28
|
+
|
|
29
|
+
const usage = () => {
|
|
30
|
+
console.error("Usage: ravel check <map.json|document.md> [--config <run.toml>] [--document <name>] [--mode <opt-in|primary>] [--json]");
|
|
31
|
+
console.error(" ravel # builds ./ravel.toml when it exists");
|
|
32
|
+
console.error(" ravel build <map.json|document.md> --out-dir <directory> [--document <name>] [--mode <opt-in|primary>] [--graph <program.json>] [--backup [file.zip]] [--clean] [--dry-run] [--json]");
|
|
33
|
+
console.error(" ravel build --config <run.toml> [--out-dir <directory>] [--graph <program.json>] [--backup [file.zip]] [--clean] [--dry-run] [--json]");
|
|
34
|
+
console.error(" ravel inspect <map.json|document.md> [--config <run.toml>] [--document <name>] [--mode <opt-in|primary>] [--chunks|--graph|--trace] [--json]");
|
|
35
|
+
console.error(" ravel inspect <input> --provenance <deliverable> [--generated-offset <offset> | --source-uri <uri> --source-offset <offset>] [--json]");
|
|
36
|
+
console.error(" ravel refresh <output-directory> [--dry-run] [--json]");
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const formatDiagnostic = (entry) => {
|
|
40
|
+
const source = entry.source;
|
|
41
|
+
const start = source?.range?.start;
|
|
42
|
+
const location = source?.uri
|
|
43
|
+
? source.uri + (start ? ":" + (start.line + 1) + ":" + (start.column + 1) : "")
|
|
44
|
+
: "ravel";
|
|
45
|
+
return location + " " + (entry.severity ?? "error") + "[" + (entry.code ?? "RV900") + "]: " + entry.message;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const printDiagnostics = (diagnostics, json) => {
|
|
49
|
+
if (json) console.error(JSON.stringify(diagnostics, null, 2));
|
|
50
|
+
else for (const diagnostic of diagnostics) console.error(formatDiagnostic(diagnostic));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const parseArguments = (argumentsValue) => {
|
|
54
|
+
const command = argumentsValue[0];
|
|
55
|
+
const options = {};
|
|
56
|
+
const positional = [];
|
|
57
|
+
for (let index = 1; index < argumentsValue.length; index += 1) {
|
|
58
|
+
const argument = argumentsValue[index];
|
|
59
|
+
if (!argument.startsWith("--")) {
|
|
60
|
+
positional.push(argument);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (argument === "--backup") {
|
|
64
|
+
if (Object.hasOwn(options, argument)) return { error: "Option may be specified only once: " + argument };
|
|
65
|
+
const following = argumentsValue[index + 1];
|
|
66
|
+
if (following && !following.startsWith("--") && extname(following).toLowerCase() === ".zip") {
|
|
67
|
+
options[argument] = following;
|
|
68
|
+
index += 1;
|
|
69
|
+
} else {
|
|
70
|
+
options[argument] = true;
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (booleanOptions.has(argument) || (argument === "--graph" && command === "inspect")) {
|
|
75
|
+
if (options[argument]) return { error: "Option may be specified only once: " + argument };
|
|
76
|
+
options[argument] = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!valueOptions.has(argument) && !(argument === "--graph" && command === "build")) {
|
|
80
|
+
return { error: "Unknown option: " + argument };
|
|
81
|
+
}
|
|
82
|
+
const value = argumentsValue[index + 1];
|
|
83
|
+
if (!value || value.startsWith("--")) return { error: "Option requires a value: " + argument };
|
|
84
|
+
if (Object.hasOwn(options, argument)) return { error: "Option may be specified only once: " + argument };
|
|
85
|
+
options[argument] = value;
|
|
86
|
+
index += 1;
|
|
87
|
+
}
|
|
88
|
+
if (positional.length > 1) return { error: "Expected at most one input path." };
|
|
89
|
+
if (options["--config"] && positional.length) return { error: "Use either an input path or --config, not both." };
|
|
90
|
+
return { command, options, input: options["--config"] ?? positional[0] };
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const sortedEntries = (value) => Object.entries(value ?? []).sort(([left], [right]) => left.localeCompare(right));
|
|
94
|
+
|
|
95
|
+
class CliUsageError extends Error {}
|
|
96
|
+
|
|
97
|
+
const inspectProgram = (program, options) => {
|
|
98
|
+
if (options["--provenance"]) {
|
|
99
|
+
const deliverable = program.deliverables[options["--provenance"]];
|
|
100
|
+
if (!deliverable) {
|
|
101
|
+
throw new CliUsageError("Unknown deliverable for --provenance: " + options["--provenance"]);
|
|
102
|
+
}
|
|
103
|
+
const map = createDeliverableProvenanceMap(deliverable);
|
|
104
|
+
const generatedOffset = options["--generated-offset"];
|
|
105
|
+
if (generatedOffset !== undefined) {
|
|
106
|
+
const offset = Number(generatedOffset);
|
|
107
|
+
const explanation = explainGeneratedOffset(program, deliverable.name, offset);
|
|
108
|
+
return {
|
|
109
|
+
version: program.version,
|
|
110
|
+
view: "provenance",
|
|
111
|
+
deliverable: { name: deliverable.name, from: deliverable.from },
|
|
112
|
+
query: { kind: "generated-offset", offset },
|
|
113
|
+
match: explanation?.segment ?? null,
|
|
114
|
+
definition: explanation?.definition ?? null,
|
|
115
|
+
references: explanation?.references ?? [],
|
|
116
|
+
dependencyPath: explanation?.dependencyPath ?? []
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (options["--source-uri"] !== undefined) {
|
|
120
|
+
const uri = options["--source-uri"];
|
|
121
|
+
const offset = Number(options["--source-offset"]);
|
|
122
|
+
return {
|
|
123
|
+
version: program.version,
|
|
124
|
+
view: "provenance",
|
|
125
|
+
deliverable: { name: deliverable.name, from: deliverable.from },
|
|
126
|
+
query: { kind: "source-offset", uri, offset },
|
|
127
|
+
matches: generatedRangesForSource(map, uri, offset)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
version: program.version,
|
|
132
|
+
view: "provenance",
|
|
133
|
+
deliverable: { name: deliverable.name, from: deliverable.from },
|
|
134
|
+
map
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (options["--chunks"]) {
|
|
138
|
+
return {
|
|
139
|
+
version: program.version,
|
|
140
|
+
view: "chunks",
|
|
141
|
+
chunks: sortedEntries(program.chunks).map(([id, chunk]) => ({
|
|
142
|
+
id,
|
|
143
|
+
name: chunk.name,
|
|
144
|
+
identity: chunk.identity,
|
|
145
|
+
metadata: chunk.metadata,
|
|
146
|
+
generated: chunk.generated,
|
|
147
|
+
dependencies: chunk.dependencies,
|
|
148
|
+
references: chunk.references
|
|
149
|
+
}))
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (options["--graph"]) {
|
|
153
|
+
return {
|
|
154
|
+
version: program.version,
|
|
155
|
+
view: "graph",
|
|
156
|
+
chunks: sortedEntries(program.chunks).map(([id, chunk]) => ({ id, dependencies: chunk.dependencies })),
|
|
157
|
+
deliverables: Object.values(program.deliverables).sort((left, right) => left.name.localeCompare(right.name)).map((deliverable) => ({
|
|
158
|
+
name: deliverable.name,
|
|
159
|
+
from: deliverable.from,
|
|
160
|
+
dependencies: deliverable.dependencies
|
|
161
|
+
}))
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (options["--trace"]) {
|
|
165
|
+
return { version: program.version, view: "trace", chunks: Object.fromEntries(sortedEntries(program.trace?.chunks)) };
|
|
166
|
+
}
|
|
167
|
+
return program;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const printInspectResult = (result, json) => {
|
|
171
|
+
if (json || !result.view) {
|
|
172
|
+
console.log(JSON.stringify(result, null, 2));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (result.view === "chunks") {
|
|
176
|
+
console.log("Ravel chunks:");
|
|
177
|
+
for (const chunk of result.chunks) {
|
|
178
|
+
const suffix = chunk.generated ? " (generated)" : "";
|
|
179
|
+
console.log(" " + chunk.id + suffix + (chunk.dependencies.length ? " → " + chunk.dependencies.join(", ") : ""));
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (result.view === "graph") {
|
|
184
|
+
console.log("Ravel dependency graph (dependent ← dependency):");
|
|
185
|
+
for (const chunk of result.chunks) console.log(" " + chunk.id + " ← " + (chunk.dependencies.length ? chunk.dependencies.join(", ") : "(none)"));
|
|
186
|
+
if (result.deliverables.length) {
|
|
187
|
+
console.log("Deliverables:");
|
|
188
|
+
for (const deliverable of result.deliverables) console.log(" " + deliverable.name + " ← " + deliverable.from);
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (result.view === "provenance") {
|
|
193
|
+
console.log("Ravel provenance for " + result.deliverable.name + " ← " + result.deliverable.from + ":");
|
|
194
|
+
const printVia = (segment) => {
|
|
195
|
+
for (const step of segment.via ?? []) {
|
|
196
|
+
if (step.kind === "reference") console.log(" via " + step.from + " → " + step.to);
|
|
197
|
+
else console.log(" via " + step.kind + (step.name ? " " + step.name : ""));
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const printSegment = (segment) => {
|
|
201
|
+
if (!segment) {
|
|
202
|
+
console.log(" (no matching generated segment)");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const source = segment.source;
|
|
206
|
+
const start = source?.range?.start;
|
|
207
|
+
const location = source
|
|
208
|
+
? source.uri + (start ? ":" + (start.line + 1) + ":" + (start.column + 1) : "")
|
|
209
|
+
: "(generated)";
|
|
210
|
+
console.log(
|
|
211
|
+
" " + segment.generated.start + ".." + segment.generated.end +
|
|
212
|
+
" ← " + location + " [" + segment.precision + ", " + segment.chunk + "]"
|
|
213
|
+
);
|
|
214
|
+
printVia(segment);
|
|
215
|
+
};
|
|
216
|
+
if (result.query?.kind === "generated-offset") {
|
|
217
|
+
console.log("Generated offset " + result.query.offset + ":");
|
|
218
|
+
printSegment(result.match);
|
|
219
|
+
if (result.dependencyPath.length) {
|
|
220
|
+
console.log(" dependency path: " + result.dependencyPath.join(" → "));
|
|
221
|
+
}
|
|
222
|
+
} else if (result.query?.kind === "source-offset") {
|
|
223
|
+
console.log("Source offset " + result.query.uri + ":" + result.query.offset + ":");
|
|
224
|
+
if (!result.matches.length) console.log(" (no generated ranges)");
|
|
225
|
+
for (const match of result.matches) {
|
|
226
|
+
console.log(
|
|
227
|
+
" " + match.generated.start + ".." + match.generated.end +
|
|
228
|
+
(match.generatedOffset === undefined ? "" : " (exact offset " + match.generatedOffset + ")") +
|
|
229
|
+
" [" + match.precision + ", " + match.chunk + "]"
|
|
230
|
+
);
|
|
231
|
+
printVia(match);
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
for (const segment of result.map.segments) printSegment(segment);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
console.log("Ravel evaluation trace:");
|
|
239
|
+
for (const [id, entries] of Object.entries(result.chunks)) {
|
|
240
|
+
console.log(" " + id + ": " + entries.map((entry) => entry.stage).join(" → "));
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const printBuildResult = (result, json) => {
|
|
245
|
+
if (json) {
|
|
246
|
+
console.log(JSON.stringify(result, null, 2));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (result.dryRun) {
|
|
250
|
+
console.log("Ravel build plan for " + result.outputDirectory + ":");
|
|
251
|
+
for (const deliverable of result.deliverables) console.log(" " + deliverable.path + " ← " + deliverable.from);
|
|
252
|
+
console.log(" manifest: " + result.manifest);
|
|
253
|
+
} else {
|
|
254
|
+
console.log("Ravel wrote " + result.written.length + " deliverable" + (result.written.length === 1 ? "" : "s") + " to " + result.outputDirectory + ".");
|
|
255
|
+
for (const deliverable of result.deliverables) console.log(" " + deliverable.path + " ← " + deliverable.from);
|
|
256
|
+
console.log("Manifest: " + result.manifest);
|
|
257
|
+
}
|
|
258
|
+
if (result.stale?.length) {
|
|
259
|
+
console.log("Stale managed outputs retained:");
|
|
260
|
+
for (const deliverable of result.stale) console.log(" " + deliverable.path + " ← " + (deliverable.from ?? "previous build"));
|
|
261
|
+
}
|
|
262
|
+
if (result.removed?.length) {
|
|
263
|
+
console.log((result.dryRun ? "Managed outputs that would be removed:" : "Managed outputs removed:"));
|
|
264
|
+
for (const deliverable of result.removed) console.log(" " + deliverable.path + " ← " + (deliverable.from ?? "previous build"));
|
|
265
|
+
}
|
|
266
|
+
if (result.backup) {
|
|
267
|
+
const action = result.dryRun ? "Backup plan" : "Backup";
|
|
268
|
+
const count = result.backup.files?.length;
|
|
269
|
+
console.log(action + ": " + result.backup.path + (count === undefined ? "" : " (" + count + " files)"));
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const printRefreshResult = (result, json) => {
|
|
274
|
+
if (json) {
|
|
275
|
+
console.log(JSON.stringify({ ok: true, command: "refresh", ...result }, null, 2));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const action = result.dryRun ? "would remove" : "removed";
|
|
279
|
+
console.log("Ravel refresh " + action + " " + result.removed.length + " stale managed output" + (result.removed.length === 1 ? "" : "s") + ".");
|
|
280
|
+
for (const deliverable of result.removed) console.log(" " + deliverable.path + " ← " + (deliverable.from ?? "previous build"));
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const invokedAsCli = (() => {
|
|
284
|
+
if (!process.argv[1]) return false;
|
|
285
|
+
try {
|
|
286
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
287
|
+
} catch {
|
|
288
|
+
return resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
289
|
+
}
|
|
290
|
+
})();
|
|
291
|
+
|
|
292
|
+
if (invokedAsCli) {
|
|
293
|
+
const rawArguments = process.argv.slice(2);
|
|
294
|
+
const argumentsValue = rawArguments.length === 0 && existsSync("ravel.toml")
|
|
295
|
+
? ["build", "--config", "ravel.toml"]
|
|
296
|
+
: rawArguments;
|
|
297
|
+
const command = argumentsValue[0];
|
|
298
|
+
if (command === "--help" || command === "-h" || argumentsValue.includes("--help")) {
|
|
299
|
+
usage();
|
|
300
|
+
process.exitCode = 0;
|
|
301
|
+
} else if (command === "--version" || command === "-v") {
|
|
302
|
+
console.log(RAVEL_VERSION);
|
|
303
|
+
process.exitCode = 0;
|
|
304
|
+
} else {
|
|
305
|
+
const parsed = parseArguments(argumentsValue);
|
|
306
|
+
const json = parsed.options?.["--json"] === true;
|
|
307
|
+
const debug = parsed.options?.["--debug"] === true;
|
|
308
|
+
const supported = new Set(["build", "inspect", "check", "refresh"]);
|
|
309
|
+
if (!supported.has(parsed.command) || parsed.error || !parsed.input) {
|
|
310
|
+
if (parsed.error) console.error("ravel usage error: " + parsed.error);
|
|
311
|
+
usage();
|
|
312
|
+
process.exitCode = EXIT_USAGE;
|
|
313
|
+
} else if (parsed.options["--dry-run"] && !new Set(["build", "refresh"]).has(parsed.command)) {
|
|
314
|
+
console.error("ravel usage error: --dry-run is available only with build or refresh.");
|
|
315
|
+
process.exitCode = EXIT_USAGE;
|
|
316
|
+
} else if (parsed.options["--clean"] && parsed.command !== "build") {
|
|
317
|
+
console.error("ravel usage error: --clean is available only with build.");
|
|
318
|
+
process.exitCode = EXIT_USAGE;
|
|
319
|
+
} else if (parsed.options["--backup"] && parsed.command !== "build") {
|
|
320
|
+
console.error("ravel usage error: --backup is available only with build.");
|
|
321
|
+
process.exitCode = EXIT_USAGE;
|
|
322
|
+
} else if (parsed.options["--out-dir"] && parsed.command !== "build") {
|
|
323
|
+
console.error("ravel usage error: --out-dir is available only with build.");
|
|
324
|
+
process.exitCode = EXIT_USAGE;
|
|
325
|
+
} else if ((parsed.options["--chunks"] || parsed.options["--trace"]) && parsed.command !== "inspect") {
|
|
326
|
+
console.error("ravel usage error: --chunks and --trace are available only with inspect.");
|
|
327
|
+
process.exitCode = EXIT_USAGE;
|
|
328
|
+
} else if (["--provenance", "--generated-offset", "--source-uri", "--source-offset"]
|
|
329
|
+
.some((option) => parsed.options[option] !== undefined) && parsed.command !== "inspect") {
|
|
330
|
+
console.error("ravel usage error: provenance options are available only with inspect.");
|
|
331
|
+
process.exitCode = EXIT_USAGE;
|
|
332
|
+
} else if (parsed.command === "inspect" && ["--chunks", "--graph", "--trace", "--provenance"].filter((option) => parsed.options[option]).length > 1) {
|
|
333
|
+
console.error("ravel usage error: choose only one inspect view.");
|
|
334
|
+
process.exitCode = EXIT_USAGE;
|
|
335
|
+
} else if ((parsed.options["--generated-offset"] !== undefined ||
|
|
336
|
+
parsed.options["--source-uri"] !== undefined ||
|
|
337
|
+
parsed.options["--source-offset"] !== undefined) && parsed.options["--provenance"] === undefined) {
|
|
338
|
+
console.error("ravel usage error: offset queries require --provenance <deliverable>.");
|
|
339
|
+
process.exitCode = EXIT_USAGE;
|
|
340
|
+
} else if (parsed.options["--generated-offset"] !== undefined &&
|
|
341
|
+
(parsed.options["--source-uri"] !== undefined || parsed.options["--source-offset"] !== undefined)) {
|
|
342
|
+
console.error("ravel usage error: choose either a generated-offset or source-offset provenance query.");
|
|
343
|
+
process.exitCode = EXIT_USAGE;
|
|
344
|
+
} else if ((parsed.options["--source-uri"] === undefined) !== (parsed.options["--source-offset"] === undefined)) {
|
|
345
|
+
console.error("ravel usage error: --source-uri and --source-offset must be used together.");
|
|
346
|
+
process.exitCode = EXIT_USAGE;
|
|
347
|
+
} else if (["--generated-offset", "--source-offset"].some((option) =>
|
|
348
|
+
parsed.options[option] !== undefined && !/^(?:0|[1-9][0-9]*)$/.test(parsed.options[option]))) {
|
|
349
|
+
console.error("ravel usage error: provenance offsets must be non-negative integers.");
|
|
350
|
+
process.exitCode = EXIT_USAGE;
|
|
351
|
+
} else if (parsed.command === "refresh" && ["--config", "--document", "--mode", "--chunks", "--trace"].some((option) => parsed.options[option])) {
|
|
352
|
+
console.error("ravel usage error: refresh accepts only an output directory, --dry-run, and --json.");
|
|
353
|
+
process.exitCode = EXIT_USAGE;
|
|
354
|
+
} else {
|
|
355
|
+
try {
|
|
356
|
+
if (parsed.command === "refresh") {
|
|
357
|
+
printRefreshResult(await refreshStaleArtifacts(parsed.input, {
|
|
358
|
+
rootDirectory: parsed.input,
|
|
359
|
+
dryRun: parsed.options["--dry-run"] === true
|
|
360
|
+
}), json);
|
|
361
|
+
process.exitCode = 0;
|
|
362
|
+
} else {
|
|
363
|
+
const loaded = await loadBuildInput(parsed.input, {
|
|
364
|
+
document: parsed.options["--document"],
|
|
365
|
+
mode: parsed.options["--mode"]
|
|
366
|
+
});
|
|
367
|
+
const program = transformGraph(loaded.pretransform);
|
|
368
|
+
const errors = program.diagnostics.filter((entry) => entry.severity === "error");
|
|
369
|
+
if (errors.length) {
|
|
370
|
+
printDiagnostics(program.diagnostics, json);
|
|
371
|
+
process.exitCode = EXIT_SOURCE;
|
|
372
|
+
} else if (parsed.command === "check") {
|
|
373
|
+
if (json) console.log(JSON.stringify({ ok: true, command: "check", diagnostics: program.diagnostics }, null, 2));
|
|
374
|
+
else console.log("Ravel check passed.");
|
|
375
|
+
} else if (parsed.command === "inspect") {
|
|
376
|
+
printInspectResult(inspectProgram(program, parsed.options), json);
|
|
377
|
+
} else {
|
|
378
|
+
const outputDirectory = parsed.options["--out-dir"] ?? loaded.outputDirectory;
|
|
379
|
+
if (!outputDirectory) throw new Error("build requires --out-dir or build.out_dir in the TOML config.");
|
|
380
|
+
const plan = planDeliverables(program, outputDirectory);
|
|
381
|
+
const rootDirectory = parsed.options["--out-dir"] ?? loaded.rootDirectory;
|
|
382
|
+
const configuredClean = loaded.buildOptions?.clean === true;
|
|
383
|
+
const configuredBackup = loaded.buildOptions?.backup ?? false;
|
|
384
|
+
const clean = parsed.options["--clean"] === true || configuredClean;
|
|
385
|
+
const backupSetting = parsed.options["--backup"] ?? configuredBackup;
|
|
386
|
+
const stale = await planStaleDeliverables(program, outputDirectory, { rootDirectory });
|
|
387
|
+
const backupOptions = {
|
|
388
|
+
outputRootDirectory: rootDirectory,
|
|
389
|
+
backupRootDirectory: loaded.rootDirectory,
|
|
390
|
+
...(typeof backupSetting === "string" ? { backupPath: backupSetting } : {})
|
|
391
|
+
};
|
|
392
|
+
const backup = backupSetting
|
|
393
|
+
? parsed.options["--dry-run"]
|
|
394
|
+
? await planOutputBackup(outputDirectory, backupOptions)
|
|
395
|
+
: await createOutputBackup(outputDirectory, backupOptions)
|
|
396
|
+
: null;
|
|
397
|
+
const cleanup = clean
|
|
398
|
+
? await cleanManagedArtifacts(outputDirectory, { rootDirectory, dryRun: parsed.options["--dry-run"] === true })
|
|
399
|
+
: { removed: [] };
|
|
400
|
+
const retainedStale = clean ? [] : stale;
|
|
401
|
+
if (parsed.options["--dry-run"]) {
|
|
402
|
+
printBuildResult({ ok: true, command: "build", dryRun: true, clean, ...plan, stale: retainedStale, removed: cleanup.removed, ...(backup ? { backup } : {}) }, json);
|
|
403
|
+
} else {
|
|
404
|
+
const artifacts = await writeBuildArtifacts(program, outputDirectory, { rootDirectory, stale: retainedStale });
|
|
405
|
+
if (parsed.options["--graph"]) await writeGraph(program, parsed.options["--graph"], { rootDirectory: loaded.rootDirectory });
|
|
406
|
+
printBuildResult({
|
|
407
|
+
ok: true,
|
|
408
|
+
command: "build",
|
|
409
|
+
outputDirectory: plan.outputDirectory,
|
|
410
|
+
written: artifacts.written,
|
|
411
|
+
manifest: artifacts.manifest.path,
|
|
412
|
+
deliverables: plan.deliverables,
|
|
413
|
+
stale: retainedStale,
|
|
414
|
+
removed: cleanup.removed,
|
|
415
|
+
...(backup ? { backup } : {}),
|
|
416
|
+
clean,
|
|
417
|
+
chunks: Object.keys(program.chunks).sort()
|
|
418
|
+
}, json);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (error instanceof CliUsageError) {
|
|
424
|
+
console.error("ravel usage error: " + error.message);
|
|
425
|
+
process.exitCode = EXIT_USAGE;
|
|
426
|
+
} else if (Array.isArray(error?.diagnostics)) {
|
|
427
|
+
printDiagnostics(error.diagnostics, json);
|
|
428
|
+
process.exitCode = EXIT_SOURCE;
|
|
429
|
+
} else if (json) {
|
|
430
|
+
console.error(JSON.stringify([{ code: "RV900", severity: "error", message: error?.message ?? String(error) }], null, 2));
|
|
431
|
+
process.exitCode = EXIT_INTERNAL;
|
|
432
|
+
} else {
|
|
433
|
+
console.error("ravel error: " + (error?.message ?? String(error)));
|
|
434
|
+
if (debug && error?.stack) console.error(error.stack);
|
|
435
|
+
process.exitCode = EXIT_INTERNAL;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|