@cynodia/axiom-cli 0.16.0-alpha.2 → 0.16.0-alpha.4
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/README.md +26 -4
- package/dist/index.js +69 -13
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -65,10 +65,32 @@ axiom --help
|
|
|
65
65
|
|
|
66
66
|
## Machine-readable output
|
|
67
67
|
|
|
68
|
-
Pass `--json` on `explain`, `analyze` or `
|
|
69
|
-
deterministic, and
|
|
70
|
-
terminal decoration reaches `--json` mode).
|
|
71
|
-
rendering.
|
|
68
|
+
Pass `--json` on `explain`, `analyze`, `diff` or `validate` for structured output —
|
|
69
|
+
parseable, deterministic, and (for `explain`/`analyze`/`diff`) semantically identical to
|
|
70
|
+
the corresponding `AgentAPI` result (no human terminal decoration reaches `--json` mode).
|
|
71
|
+
Everything else prints a concise human-readable rendering.
|
|
72
|
+
|
|
73
|
+
**`--json` also governs failure, not only success.** Every CLI-owned error path — an
|
|
74
|
+
unknown node id, a missing required flag, a model file that will not load, an unparseable
|
|
75
|
+
command line — emits one JSON value on stdout instead of prose, and never a native stack
|
|
76
|
+
trace:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"ok": false,
|
|
81
|
+
"error": {
|
|
82
|
+
"code": "UNKNOWN_NODE",
|
|
83
|
+
"message": "No action node \"foo\" in this graph"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`error.code` is one of `INVALID_ARGUMENTS`, `UNKNOWN_COMMAND`, `UNKNOWN_NODE`,
|
|
89
|
+
`MISSING_ARGUMENT`, `MODEL_LOAD_FAILED` or `COMMAND_FAILED` (an uncategorized failure).
|
|
90
|
+
Match on `code`, never on `message` — the same discipline as every other structured
|
|
91
|
+
diagnostic in Axiom. Exit code is always nonzero on failure, in `--json` mode and out of
|
|
92
|
+
it. Without `--json`, error text is unchanged prose on stderr (or, for a handful of
|
|
93
|
+
pre-existing "not found"-style results, stdout — see the source), exactly as before.
|
|
72
94
|
|
|
73
95
|
## Exit codes
|
|
74
96
|
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,30 @@ const SUBCOMMANDS = {
|
|
|
13
13
|
schema: new Set(['status', 'diff']),
|
|
14
14
|
migrate: new Set(['plan', 'status']),
|
|
15
15
|
};
|
|
16
|
+
/** Thrown for a CLI-level failure that must carry one of the codes above under `--json`. */
|
|
17
|
+
class CliError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
constructor(code, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The one shape every CLI-owned failure takes under `--json` (spec16pt4 §4): structured,
|
|
26
|
+
* deterministic, machine-readable, and — unlike a bare thrown error reaching the top —
|
|
27
|
+
* carries no native stack trace. Printed to stdout, like every other `--json` result, so a
|
|
28
|
+
* caller parsing stdout as JSON never has to distinguish success from failure by channel.
|
|
29
|
+
* Human mode (`options.json` false) is untouched (spec16pt4 §6).
|
|
30
|
+
*/
|
|
31
|
+
function fail(json, code, message) {
|
|
32
|
+
if (json) {
|
|
33
|
+
console.log(JSON.stringify({ ok: false, error: { code, message } }, null, 2));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.error(message);
|
|
37
|
+
}
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
16
40
|
function parseArguments(argv) {
|
|
17
41
|
const positional = [];
|
|
18
42
|
let exportName;
|
|
@@ -110,7 +134,13 @@ async function loadGraph(options) {
|
|
|
110
134
|
async function loadGraphModule(modelFile, exportName) {
|
|
111
135
|
const options = { modelFile, exportName };
|
|
112
136
|
const resolved = path.resolve(process.cwd(), options.modelFile);
|
|
113
|
-
|
|
137
|
+
let module;
|
|
138
|
+
try {
|
|
139
|
+
module = (await import(pathToFileURL(resolved).href));
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new CliError('MODEL_LOAD_FAILED', `Could not load ${options.modelFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
}
|
|
114
144
|
const names = options.exportName
|
|
115
145
|
? [options.exportName]
|
|
116
146
|
: GRAPH_EXPORT_CANDIDATES.filter((name) => name in module);
|
|
@@ -131,12 +161,12 @@ async function loadGraphModule(modelFile, exportName) {
|
|
|
131
161
|
}
|
|
132
162
|
}
|
|
133
163
|
if (builders.length > 1) {
|
|
134
|
-
throw new
|
|
164
|
+
throw new CliError('MODEL_LOAD_FAILED', `${options.modelFile} exports several candidates. Choose one with --export=<name>: ${builders
|
|
135
165
|
.map(([name]) => name)
|
|
136
166
|
.join(', ')}`);
|
|
137
167
|
}
|
|
138
168
|
}
|
|
139
|
-
throw new
|
|
169
|
+
throw new CliError('MODEL_LOAD_FAILED', `Could not load an application graph from ${options.modelFile}`);
|
|
140
170
|
}
|
|
141
171
|
const SECTIONS = [
|
|
142
172
|
['Entities', 'entity'],
|
|
@@ -484,7 +514,7 @@ function formatExplanation(kind, result) {
|
|
|
484
514
|
}
|
|
485
515
|
async function explainCommand(options) {
|
|
486
516
|
if (!options.kind || !options.targetId) {
|
|
487
|
-
throw new
|
|
517
|
+
throw new CliError('INVALID_ARGUMENTS', 'usage: axiom explain <action|query|workflow|state> <id> <modelFile>');
|
|
488
518
|
}
|
|
489
519
|
const agent = new AgentAPI(await loadGraph(options));
|
|
490
520
|
let result;
|
|
@@ -502,11 +532,15 @@ async function explainCommand(options) {
|
|
|
502
532
|
result = agent.explainState(options.targetId);
|
|
503
533
|
break;
|
|
504
534
|
default:
|
|
505
|
-
throw new
|
|
535
|
+
throw new CliError('INVALID_ARGUMENTS', `explain: unknown kind "${options.kind}" (expected action, query, workflow or state)`);
|
|
506
536
|
}
|
|
507
537
|
if (!result) {
|
|
538
|
+
const message = `No ${options.kind} node "${options.targetId}" in this graph`;
|
|
539
|
+
if (options.json) {
|
|
540
|
+
throw new CliError('UNKNOWN_NODE', message);
|
|
541
|
+
}
|
|
508
542
|
process.exitCode = 1;
|
|
509
|
-
return
|
|
543
|
+
return message;
|
|
510
544
|
}
|
|
511
545
|
return options.json ? JSON.stringify(result, null, 2) : formatExplanation(options.kind, result);
|
|
512
546
|
}
|
|
@@ -530,7 +564,7 @@ async function analyzeCommand(options) {
|
|
|
530
564
|
}
|
|
531
565
|
async function diffCommand(options) {
|
|
532
566
|
if (!options.against) {
|
|
533
|
-
throw new
|
|
567
|
+
throw new CliError('MISSING_ARGUMENT', 'diff needs a --against=<file> naming the graph to compare against');
|
|
534
568
|
}
|
|
535
569
|
const before = await loadGraphModule(options.against, options.againstExport);
|
|
536
570
|
const after = await loadGraph(options);
|
|
@@ -578,16 +612,34 @@ const USAGE = [
|
|
|
578
612
|
].join('\n');
|
|
579
613
|
async function main() {
|
|
580
614
|
const argv = process.argv.slice(2);
|
|
615
|
+
// `--json` may be present even when parsing fails outright (a missing model file, an
|
|
616
|
+
// unparseable command line), so it is read directly from argv rather than from the
|
|
617
|
+
// `Options` parseArguments would otherwise have produced (spec16pt4 §3, "missing model
|
|
618
|
+
// file" / "invalid/missing command arguments").
|
|
619
|
+
const wantsJson = argv.includes('--json');
|
|
581
620
|
if (argv.includes('--help') || argv.includes('-h') || argv.length === 0) {
|
|
582
621
|
console.log(USAGE);
|
|
583
622
|
return;
|
|
584
623
|
}
|
|
585
624
|
const options = parseArguments(argv);
|
|
586
625
|
if (!options) {
|
|
587
|
-
|
|
588
|
-
|
|
626
|
+
if (wantsJson) {
|
|
627
|
+
fail(true, 'INVALID_ARGUMENTS', 'Missing or invalid command-line arguments. Run "axiom --help" for usage.');
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
console.error(USAGE);
|
|
631
|
+
process.exitCode = 1;
|
|
632
|
+
}
|
|
589
633
|
return;
|
|
590
634
|
}
|
|
635
|
+
try {
|
|
636
|
+
await runCommand(options);
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
fail(options.json === true, error instanceof CliError ? error.code : 'COMMAND_FAILED', error instanceof Error ? error.message : String(error));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async function runCommand(options) {
|
|
591
643
|
switch (options.command) {
|
|
592
644
|
case 'schema status':
|
|
593
645
|
console.log(schemaStatus(await loadGraph(options)));
|
|
@@ -632,11 +684,15 @@ async function main() {
|
|
|
632
684
|
console.log(await diffCommand(options));
|
|
633
685
|
return;
|
|
634
686
|
default:
|
|
635
|
-
|
|
636
|
-
|
|
687
|
+
if (options.json) {
|
|
688
|
+
fail(true, 'UNKNOWN_COMMAND', `Unknown command: ${options.command}`);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
console.error(`Unknown command: ${options.command}`);
|
|
692
|
+
process.exitCode = 1;
|
|
693
|
+
}
|
|
637
694
|
}
|
|
638
695
|
}
|
|
639
696
|
main().catch((error) => {
|
|
640
|
-
|
|
641
|
-
process.exitCode = 1;
|
|
697
|
+
fail(process.argv.slice(2).includes('--json'), error instanceof CliError ? error.code : 'COMMAND_FAILED', error instanceof Error ? error.message : String(error));
|
|
642
698
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cynodia/axiom-cli",
|
|
3
|
-
"version": "0.16.0-alpha.
|
|
3
|
+
"version": "0.16.0-alpha.4",
|
|
4
4
|
"description": "Command line tools for inspecting, validating and serving Axiom applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AskTech AS",
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
"axiom": "./dist/index.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@cynodia/axiom-core": "0.16.0-alpha.
|
|
38
|
-
"@cynodia/axiom-compiler": "0.16.0-alpha.
|
|
39
|
-
"@cynodia/axiom-server": "0.16.0-alpha.
|
|
40
|
-
"@cynodia/axiom-agent-api": "0.16.0-alpha.
|
|
37
|
+
"@cynodia/axiom-core": "0.16.0-alpha.4",
|
|
38
|
+
"@cynodia/axiom-compiler": "0.16.0-alpha.4",
|
|
39
|
+
"@cynodia/axiom-server": "0.16.0-alpha.4",
|
|
40
|
+
"@cynodia/axiom-agent-api": "0.16.0-alpha.4"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
|
-
"build": "tsc -b tsconfig.json"
|
|
43
|
+
"build": "tsc -b tsconfig.json tsconfig.test.json",
|
|
44
|
+
"test": "node --test dist-test/**/*.test.js"
|
|
44
45
|
}
|
|
45
46
|
}
|