burgee 0.12.1 → 0.13.1
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 +2 -1
- package/dist/agent-format.d.ts +7 -0
- package/dist/agent-format.js +32 -0
- package/dist/argv.d.ts +16 -0
- package/dist/argv.js +12 -0
- package/dist/commander/command.d.ts +12 -0
- package/dist/commander/command.js +11 -5
- package/dist/compat.d.ts +22 -0
- package/dist/compat.js +29 -0
- package/dist/config-layers.d.ts +16 -0
- package/dist/config-layers.js +19 -0
- package/dist/execute.d.ts +1 -3
- package/dist/execute.js +20 -238
- package/dist/failure.d.ts +26 -0
- package/dist/failure.js +90 -0
- package/dist/fields.d.ts +6 -6
- package/dist/fields.js +10 -4
- package/dist/migrate.js +2 -2
- package/dist/program-schema.json +1 -1
- package/dist/relations.d.ts +7 -0
- package/dist/relations.js +49 -0
- package/dist/schema.d.ts +7 -0
- package/dist/surfaces.d.ts +81 -0
- package/dist/surfaces.js +116 -0
- package/dist/validate.d.ts +1 -6
- package/dist/validate.js +1 -48
- package/dist/yargs/factory.d.ts +8 -1
- package/dist/yargs/factory.js +26 -13
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
22
|
<p align="center">
|
|
23
|
-
Docs: <a href="https://burgee.interlace.tools/docs/packages/burgee">https://burgee.interlace.tools/docs/packages/burgee</a
|
|
23
|
+
Docs: <a href="https://burgee.interlace.tools/docs/packages/burgee">https://burgee.interlace.tools/docs/packages/burgee</a><br />
|
|
24
|
+
Migrating from: <a href="https://burgee.interlace.tools/docs/vs/commander">commander</a> · <a href="https://burgee.interlace.tools/docs/vs/yargs">yargs</a>
|
|
24
25
|
</p>
|
|
25
26
|
|
|
26
27
|
A **burgee** is the small swallowtail flag a boat flies to say which club or fleet it
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/** The result as `--format=agent` prints it: one line per record, each ending in a newline. */
|
|
7
|
+
export declare function agentLines(data: unknown): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const SPACE = /\s+/g;
|
|
2
|
+
const UNSAFE = /^$|[ =",\p{Cc}]/u;
|
|
3
|
+
const text = (value) => String(value).replace(SPACE, ' ').trim();
|
|
4
|
+
function scalar(value) {
|
|
5
|
+
const s = text(value);
|
|
6
|
+
return UNSAFE.test(s) ? JSON.stringify(s) : s;
|
|
7
|
+
}
|
|
8
|
+
const isScalar = (value) => value === null || typeof value !== 'object';
|
|
9
|
+
const children = (value, key) => Object.entries(value).map(([k, v]) => [key === '' ? k : `${key}.${k}`, v]);
|
|
10
|
+
function line(record) {
|
|
11
|
+
if (isScalar(record))
|
|
12
|
+
return text(record);
|
|
13
|
+
const out = [];
|
|
14
|
+
const stack = children(record, '').reverse();
|
|
15
|
+
for (let next = stack.pop(); next !== undefined; next = stack.pop()) {
|
|
16
|
+
const [key, value] = next;
|
|
17
|
+
if (isScalar(value))
|
|
18
|
+
out.push(`${key}=${scalar(value)}`);
|
|
19
|
+
else if (Array.isArray(value) && value.every(isScalar))
|
|
20
|
+
out.push(`${key}=${value.map(scalar).join(',')}`);
|
|
21
|
+
else
|
|
22
|
+
for (const child of children(value, key).reverse())
|
|
23
|
+
stack.push(child);
|
|
24
|
+
}
|
|
25
|
+
return out.join(' ');
|
|
26
|
+
}
|
|
27
|
+
export function agentLines(data) {
|
|
28
|
+
const json = JSON.stringify(data);
|
|
29
|
+
const value = json === undefined ? null : JSON.parse(json);
|
|
30
|
+
const records = value === null ? [] : [value].flat();
|
|
31
|
+
return records.map((r) => `${line(r)}\n`).join('');
|
|
32
|
+
}
|
package/dist/argv.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The readings of argv that both the engine and its lazily loaded surfaces make, in one place so
|
|
3
|
+
* they cannot disagree: where options end, which flag asks for `--json`, and whether a surface
|
|
4
|
+
* might be asked for.
|
|
5
|
+
*/
|
|
6
|
+
/** The part of argv the parser will read as options: everything before `--`. */
|
|
7
|
+
export declare function beforeTerminator(argv: readonly string[]): readonly string[];
|
|
8
|
+
/** `--json`, or `--json=<fields>` (N14). */
|
|
9
|
+
export declare function isJsonFlag(arg: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Whether argv could be asking for a surface — the cheap test the engine runs on every
|
|
12
|
+
* invocation, so `surfaces.js` loads only when it might answer (U5). It admits a superset:
|
|
13
|
+
* `serve` there still checks each surface's own condition (a program that defines its own
|
|
14
|
+
* `completion`, `config explain` without config) and answers `false` when none applies.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mayServe(argv: readonly string[]): boolean;
|
package/dist/argv.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function beforeTerminator(argv) {
|
|
2
|
+
const at = argv.indexOf('--');
|
|
3
|
+
return at === -1 ? argv : argv.slice(0, at);
|
|
4
|
+
}
|
|
5
|
+
export function isJsonFlag(arg) {
|
|
6
|
+
return arg === '--json' || arg.startsWith('--json=');
|
|
7
|
+
}
|
|
8
|
+
export function mayServe(argv) {
|
|
9
|
+
const head = beforeTerminator(argv);
|
|
10
|
+
const first = argv[0];
|
|
11
|
+
return first === '__complete' || first === 'completion' || first === 'help' || head[0] === '--mcp' || (head[0] === 'config' && head[1] === 'explain') || head.includes('--schema');
|
|
12
|
+
}
|
|
@@ -151,6 +151,8 @@ export declare class Command extends EventEmitter {
|
|
|
151
151
|
_deprecationWarned: boolean;
|
|
152
152
|
/** burgee: set for the duration of a parse that injected the streams or the exit. */
|
|
153
153
|
_burgee: Burgee | undefined;
|
|
154
|
+
/** burgee: the behavioural floor, read on the root and turned on by `.burgee({ floor: true })` (J3). */
|
|
155
|
+
_floor: boolean;
|
|
154
156
|
constructor(name?: string);
|
|
155
157
|
/** Copy settings useful to share between the root and its subcommands. */
|
|
156
158
|
copyInheritedSettings(sourceCommand: Command): this;
|
|
@@ -365,6 +367,16 @@ export declare class Command extends EventEmitter {
|
|
|
365
367
|
_burgeeSurface(userArgs: string[]): boolean | Promise<boolean>;
|
|
366
368
|
/** The surfaces after `completion`: `--schema` is synchronous, `--mcp` serves until stdin closes. */
|
|
367
369
|
_burgeeSurfaceRest(head: string[]): boolean | Promise<boolean>;
|
|
370
|
+
/**
|
|
371
|
+
* burgee: turn on what changes commander's observable behaviour, in one call (J3, D-121). Off
|
|
372
|
+
* by default, because commander's own suite asserts the old behaviour. With `floor: true` a
|
|
373
|
+
* usage error exits 2 (E1) rather than 1, and an action that throws or rejects prints one
|
|
374
|
+
* `error:` line and sets its E1 exit code rather than escaping as a stack. A program that
|
|
375
|
+
* called `exitOverride()` took its exits over and keeps them.
|
|
376
|
+
*/
|
|
377
|
+
burgee(options: {
|
|
378
|
+
floor?: boolean;
|
|
379
|
+
}): this;
|
|
368
380
|
/** Additive, and the point of the whole exercise: plugins commander has never had (#2505, unlanded). */
|
|
369
381
|
use(plugin: Plugin): this;
|
|
370
382
|
/** Inject the streams and the exit for one parse; returns commander's own parse options. */
|
|
@@ -102,6 +102,7 @@ export class Command extends EventEmitter {
|
|
|
102
102
|
_deprecated = undefined;
|
|
103
103
|
_deprecationWarned = false;
|
|
104
104
|
_burgee = undefined;
|
|
105
|
+
_floor = false;
|
|
105
106
|
constructor(name) {
|
|
106
107
|
super();
|
|
107
108
|
this._args = this.registeredArguments;
|
|
@@ -304,7 +305,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
|
|
|
304
305
|
if (this._exitCallback) {
|
|
305
306
|
this._exitCallback(new CommanderError(exitCode, code, message));
|
|
306
307
|
}
|
|
307
|
-
return host.exit(exitCode);
|
|
308
|
+
return host.exit(this._root()._floor ? e1({ code, exitCode }) : exitCode);
|
|
308
309
|
}
|
|
309
310
|
action(fn) {
|
|
310
311
|
const listener = (args) => {
|
|
@@ -1479,7 +1480,8 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1479
1480
|
_burgeeSurfaceRest(head) {
|
|
1480
1481
|
const root = this._root();
|
|
1481
1482
|
if (head.includes('--schema') && !root._declares('--schema')) {
|
|
1482
|
-
|
|
1483
|
+
const shadows = ['--json', '--mcp', 'completion'].filter((name) => root._declares(name) || root._findCommand(name) !== undefined);
|
|
1484
|
+
root._outputConfiguration.writeOut(`${machineJson({ ...schemaOf(this.manifest), ...(shadows.length > 0 && { shadows }) }, head)}\n`);
|
|
1483
1485
|
return true;
|
|
1484
1486
|
}
|
|
1485
1487
|
if (head[0] === '--mcp' && !root._declares('--mcp')) {
|
|
@@ -1495,6 +1497,10 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1495
1497
|
}
|
|
1496
1498
|
return false;
|
|
1497
1499
|
}
|
|
1500
|
+
burgee(options) {
|
|
1501
|
+
this._root()._floor = options.floor === true;
|
|
1502
|
+
return this;
|
|
1503
|
+
}
|
|
1498
1504
|
use(plugin) {
|
|
1499
1505
|
this.manifest.use(plugin);
|
|
1500
1506
|
return this;
|
|
@@ -1560,11 +1566,11 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1560
1566
|
this._burgee = undefined;
|
|
1561
1567
|
};
|
|
1562
1568
|
const fail = (err) => {
|
|
1563
|
-
const
|
|
1564
|
-
if (
|
|
1569
|
+
const report = (this._burgee?.json === true || (this._floor && this._exitCallback === null)) && !(err instanceof CommanderError);
|
|
1570
|
+
if (report)
|
|
1565
1571
|
host.exitCode = this._reportHandlerFailure(err);
|
|
1566
1572
|
done();
|
|
1567
|
-
if (!
|
|
1573
|
+
if (!report)
|
|
1568
1574
|
throw err;
|
|
1569
1575
|
};
|
|
1570
1576
|
try {
|
package/dist/compat.d.ts
CHANGED
|
@@ -63,5 +63,27 @@ export declare const DROP_INS: readonly DropIn[];
|
|
|
63
63
|
* `scripts/migrate-drop-ins-lock.test.ts` holds each equal to the oracle's.
|
|
64
64
|
*/
|
|
65
65
|
export declare const GRADED_VERSIONS: Readonly<Record<string, string>>;
|
|
66
|
+
/**
|
|
67
|
+
* C1 — the majors of each incumbent its drop-in claims, per incumbent package, highest last.
|
|
68
|
+
*
|
|
69
|
+
* A major is on this list only when the incumbent's **own suite at that major** grades the
|
|
70
|
+
* drop-in level with the incumbent itself (D-137), so the list is a measurement and not a
|
|
71
|
+
* range somebody believed. The current major is the one in `GRADED_VERSIONS`. An older one
|
|
72
|
+
* needs a row in `compat-oracle`'s `PREVIOUS_MAJORS` — the suite vendored at that major's last
|
|
73
|
+
* tag and graded in CI by `compat.yml`'s ratchet job — whose baseline passes as many cases as
|
|
74
|
+
* its control does.
|
|
75
|
+
*
|
|
76
|
+
* Two older majors are graded today and **neither is claimed**, because neither is level:
|
|
77
|
+
* commander 14 grades 1329 / 1331 (15 names the surplus argument in the excess-arguments
|
|
78
|
+
* message, 14 does not) and yargs 17 grades 191 / 794 (17's `require('yargs')` is a
|
|
79
|
+
* singleton, which 18 removed). The measurements are on the compatibility page; the claim
|
|
80
|
+
* waits for the number.
|
|
81
|
+
*
|
|
82
|
+
* `migrate` reads this, and not `GRADED_VERSIONS`, to decide a project is on a major it may
|
|
83
|
+
* rewrite — so a major that becomes level here is a major `migrate` serves, with no second
|
|
84
|
+
* edit. `scripts/supported-majors-lock.test.ts` holds every entry to `GRADED_VERSIONS` and to
|
|
85
|
+
* the oracle's graded rows.
|
|
86
|
+
*/
|
|
87
|
+
export declare const SUPPORTED_MAJORS: Readonly<Record<string, readonly number[]>>;
|
|
66
88
|
/** Level: the drop-in passes every case the incumbent passes against its own suite (D-137). */
|
|
67
89
|
export declare const isLevel: (host: string) => boolean;
|
package/dist/compat.js
CHANGED
|
@@ -86,6 +86,35 @@ export const GRADED_VERSIONS = {
|
|
|
86
86
|
yargs: '18.1.0',
|
|
87
87
|
'yargs-parser': '22.0.0',
|
|
88
88
|
};
|
|
89
|
+
export const SUPPORTED_MAJORS = {
|
|
90
|
+
'@clack/prompts': [1],
|
|
91
|
+
'@inquirer/core': [12],
|
|
92
|
+
'ansi-escapes': [7],
|
|
93
|
+
boxen: [8],
|
|
94
|
+
chalk: [6],
|
|
95
|
+
'cli-table3': [0],
|
|
96
|
+
commander: [15],
|
|
97
|
+
cosmiconfig: [10],
|
|
98
|
+
'cross-spawn': [7],
|
|
99
|
+
dotenv: [17],
|
|
100
|
+
'exit-hook': [5],
|
|
101
|
+
lilconfig: [3],
|
|
102
|
+
'log-update': [8],
|
|
103
|
+
meow: [14],
|
|
104
|
+
ora: [9],
|
|
105
|
+
rc: [1],
|
|
106
|
+
'restore-cursor': [5],
|
|
107
|
+
'signal-exit': [4],
|
|
108
|
+
'slice-ansi': [7],
|
|
109
|
+
'string-width': [8],
|
|
110
|
+
'strip-ansi': [7],
|
|
111
|
+
'term-img': [7],
|
|
112
|
+
'terminal-link': [5],
|
|
113
|
+
which: [7],
|
|
114
|
+
'wrap-ansi': [10],
|
|
115
|
+
yargs: [18],
|
|
116
|
+
'yargs-parser': [22],
|
|
117
|
+
};
|
|
89
118
|
export const isLevel = (host) => {
|
|
90
119
|
const row = GRADED[host];
|
|
91
120
|
return row !== undefined && row.passed >= row.control;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V6 — the config file and the package.json field, as precedence layers, for a program that
|
|
3
|
+
* opted into config discovery.
|
|
4
|
+
*
|
|
5
|
+
* Its own module since 2026-09-24 (U5): `execute.ts` imports it only when the manifest declares
|
|
6
|
+
* `config`, so a program that never reads a config file carries none of this — as it already
|
|
7
|
+
* carried none of `seniority/config`, which this reaches the same way.
|
|
8
|
+
*/
|
|
9
|
+
import { type Layers } from 'seniority/precedence';
|
|
10
|
+
import { type Package } from './pkg.js';
|
|
11
|
+
/** The config file and the package.json field, for a program that opted in; loaded lazily (K6). */
|
|
12
|
+
export declare function configLayers(name: string, values: Record<string, unknown>, io: {
|
|
13
|
+
cwd: string;
|
|
14
|
+
env: Record<string, string | undefined>;
|
|
15
|
+
pkg: Package | undefined;
|
|
16
|
+
}): Promise<Pick<Layers, 'config' | 'pkg'>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function packageLayer(pkg, name) {
|
|
2
|
+
if (pkg === undefined || name === undefined)
|
|
3
|
+
return undefined;
|
|
4
|
+
const field = pkg.data[name];
|
|
5
|
+
return typeof field === 'object' && field !== null && !Array.isArray(field) ? { path: pkg.path, data: field } : undefined;
|
|
6
|
+
}
|
|
7
|
+
export async function configLayers(name, values, io) {
|
|
8
|
+
const { discover } = await import('seniority/config');
|
|
9
|
+
const explicit = values['config'];
|
|
10
|
+
const disabled = values['noConfig'] === true;
|
|
11
|
+
const loaded = await discover({ name, cwd: io.cwd, env: io.env, ...(typeof explicit === 'string' ? { explicit } : {}), disabled });
|
|
12
|
+
const out = {};
|
|
13
|
+
if (loaded !== undefined)
|
|
14
|
+
out.config = { path: loaded.chain.join(' ← '), data: loaded.data };
|
|
15
|
+
const pkg = disabled ? undefined : packageLayer(io.pkg, name);
|
|
16
|
+
if (pkg !== undefined)
|
|
17
|
+
out.pkg = pkg;
|
|
18
|
+
return out;
|
|
19
|
+
}
|
package/dist/execute.d.ts
CHANGED
|
@@ -108,8 +108,6 @@ export interface RunOptions {
|
|
|
108
108
|
/** The entry file, whose nearest package.json owns the program's version (V4); `process.argv[1]` otherwise. */
|
|
109
109
|
entry?: string;
|
|
110
110
|
}
|
|
111
|
-
/** The part of argv the parser will read as options: everything before `--`. */
|
|
112
|
-
export declare function beforeTerminator(argv: readonly string[]): readonly string[];
|
|
113
111
|
export declare function execute(manifest: Manifest, opts?: RunOptions & {
|
|
114
112
|
root?: string[];
|
|
115
113
|
from?: 'node' | 'user';
|
|
@@ -131,4 +129,4 @@ export declare function runCommand(manifest: Manifest, argv: readonly string[],
|
|
|
131
129
|
* through `execute`, so there is exactly one code path from argv to exit.
|
|
132
130
|
*/
|
|
133
131
|
export declare function run<S extends OptionSpecs>(target: Command<S> | Manifest, opts?: RunOptions): Promise<void>;
|
|
134
|
-
export {};
|
|
132
|
+
export { beforeTerminator } from './argv.js';
|
package/dist/execute.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
-
import {
|
|
3
|
+
import { resolve as resolveLayers } from 'seniority/precedence';
|
|
4
4
|
import { detectAgent } from './agent.js';
|
|
5
|
+
import { beforeTerminator, isJsonFlag, mayServe } from './argv.js';
|
|
5
6
|
import { checkCommand } from './definition.js';
|
|
6
7
|
import { ExitCode, isExitCode } from './exit-code.js';
|
|
7
8
|
import { Manifest, relationsOf } from './manifest.js';
|
|
@@ -9,7 +10,7 @@ import { camel, kebab } from './names.js';
|
|
|
9
10
|
import { nearestPackage } from './pkg.js';
|
|
10
11
|
import { host } from './runtime.js';
|
|
11
12
|
import { detachedTeardown, processTeardown } from './shutdown.js';
|
|
12
|
-
import {
|
|
13
|
+
import { coerce, UsageError } from './validate.js';
|
|
13
14
|
function helpFields(c) {
|
|
14
15
|
const node = {};
|
|
15
16
|
if (c.description !== undefined)
|
|
@@ -152,31 +153,12 @@ function canonical(values, specs, tokens) {
|
|
|
152
153
|
}
|
|
153
154
|
return out;
|
|
154
155
|
}
|
|
155
|
-
function packageLayer(pkg, name) {
|
|
156
|
-
if (pkg === undefined || name === undefined)
|
|
157
|
-
return undefined;
|
|
158
|
-
const field = pkg.data[name];
|
|
159
|
-
return typeof field === 'object' && field !== null && !Array.isArray(field) ? { path: pkg.path, data: field } : undefined;
|
|
160
|
-
}
|
|
161
|
-
async function configLayers(name, values, io) {
|
|
162
|
-
const { discover } = await import('seniority/config');
|
|
163
|
-
const explicit = values['config'];
|
|
164
|
-
const disabled = values['noConfig'] === true;
|
|
165
|
-
const loaded = await discover({ name, cwd: io.cwd, env: io.env, ...(typeof explicit === 'string' ? { explicit } : {}), disabled });
|
|
166
|
-
const out = {};
|
|
167
|
-
if (loaded !== undefined)
|
|
168
|
-
out.config = { path: loaded.chain.join(' ← '), data: loaded.data };
|
|
169
|
-
const pkg = disabled ? undefined : packageLayer(io.pkg, name);
|
|
170
|
-
if (pkg !== undefined)
|
|
171
|
-
out.pkg = pkg;
|
|
172
|
-
return out;
|
|
173
|
-
}
|
|
174
156
|
async function resolution(manifest, specs, values, io) {
|
|
175
157
|
const layers = { flags: values, env: io.env };
|
|
176
158
|
if (manifest.envPrefix !== undefined)
|
|
177
159
|
layers.envPrefix = manifest.envPrefix;
|
|
178
160
|
if (manifest.config !== undefined)
|
|
179
|
-
Object.assign(layers, await configLayers(manifest.config.name, values, io));
|
|
161
|
+
Object.assign(layers, await (await import('./config-layers.js')).configLayers(manifest.config.name, values, io));
|
|
180
162
|
return resolveLayers(specs, layers);
|
|
181
163
|
}
|
|
182
164
|
async function resolveValues(manifest, specs, values, io) {
|
|
@@ -206,192 +188,12 @@ function splitPositionals(tokens) {
|
|
|
206
188
|
}
|
|
207
189
|
return { positionals, passthrough };
|
|
208
190
|
}
|
|
209
|
-
function isParseArgsFailure(cause) {
|
|
210
|
-
if (!(cause instanceof Error))
|
|
211
|
-
return false;
|
|
212
|
-
const { code } = cause;
|
|
213
|
-
return typeof code === 'string' && code.startsWith('ERR_PARSE_ARGS_');
|
|
214
|
-
}
|
|
215
|
-
function exitSignal(cause) {
|
|
216
|
-
const code = cause?.code;
|
|
217
|
-
return typeof code === 'number' && isExitCode(code) ? code : undefined;
|
|
218
|
-
}
|
|
219
|
-
const CLASSIFIED = [
|
|
220
|
-
[UsageError, ExitCode.USAGE],
|
|
221
|
-
[AuthError, ExitCode.AUTH],
|
|
222
|
-
[ConfigError, ExitCode.CONFIG],
|
|
223
|
-
];
|
|
224
|
-
const NAMED = new Map([
|
|
225
|
-
['USAGE', ExitCode.USAGE],
|
|
226
|
-
['CONFIG', ExitCode.CONFIG],
|
|
227
|
-
['CANCELLED', ExitCode.CANCELLED],
|
|
228
|
-
['AUTH', ExitCode.AUTH],
|
|
229
|
-
]);
|
|
230
|
-
function namedCode(cause) {
|
|
231
|
-
return NAMED.get(cause?.code);
|
|
232
|
-
}
|
|
233
|
-
function messageOf(cause) {
|
|
234
|
-
if (cause instanceof Error)
|
|
235
|
-
return cause.message;
|
|
236
|
-
const said = cause?.message;
|
|
237
|
-
return typeof said === 'string' ? said : String(cause);
|
|
238
|
-
}
|
|
239
|
-
function carried(cause) {
|
|
240
|
-
const { hint, fix } = (cause ?? {});
|
|
241
|
-
return {
|
|
242
|
-
...(typeof hint === 'string' ? { hint } : {}),
|
|
243
|
-
...(typeof fix === 'string' ? { fix } : {}),
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
async function describeFailure(cause, argv, node) {
|
|
247
|
-
const signal = exitSignal(cause);
|
|
248
|
-
if (signal !== undefined)
|
|
249
|
-
return { code: signal, message: '', silent: true };
|
|
250
|
-
const message = messageOf(cause);
|
|
251
|
-
if (cause instanceof ActionRequired)
|
|
252
|
-
return { code: ExitCode.CANCELLED, message, action: cause.spec, ...(cause.spec.hint === undefined ? {} : { hint: cause.spec.hint }) };
|
|
253
|
-
const named = CLASSIFIED.find(([Class]) => cause instanceof Class);
|
|
254
|
-
if (named !== undefined)
|
|
255
|
-
return { code: named[1], message, ...carried(cause) };
|
|
256
|
-
const own = cause?.constructor?.[Symbol.for('burgee.exitCode')];
|
|
257
|
-
if (typeof own === 'number')
|
|
258
|
-
return { code: own, message, ...carried(cause) };
|
|
259
|
-
const byName = namedCode(cause);
|
|
260
|
-
if (byName !== undefined)
|
|
261
|
-
return { code: byName, message, ...carried(cause) };
|
|
262
|
-
if (isParseArgsFailure(cause)) {
|
|
263
|
-
const explain = await import('./unknown-option.js');
|
|
264
|
-
const dash = explain.singleDashHint(argv);
|
|
265
|
-
if (dash !== undefined)
|
|
266
|
-
return { code: ExitCode.USAGE, message, hint: dash };
|
|
267
|
-
const better = explain.unknownOption(cause, Object.keys(node?.options ?? {}).map(kebab));
|
|
268
|
-
return { code: ExitCode.USAGE, message, hint: 'run --help to see the available options', ...better };
|
|
269
|
-
}
|
|
270
|
-
return { code: ExitCode.RUNTIME, message };
|
|
271
|
-
}
|
|
272
|
-
function textFailure(failure) {
|
|
273
|
-
const hint = failure.hint === undefined ? '' : `hint: ${failure.hint}\n`;
|
|
274
|
-
const fix = failure.fix === undefined ? '' : `fix: ${failure.fix}\n`;
|
|
275
|
-
if (failure.action !== undefined) {
|
|
276
|
-
const next = (failure.action.next ?? []).map((n) => ` ${n.command} ${n.when}\n`).join('');
|
|
277
|
-
return `action required (${failure.action.reason}): ${failure.message}\n${next === '' ? '' : `next:\n${next}`}${hint}`;
|
|
278
|
-
}
|
|
279
|
-
return `error: ${failure.message}\n${hint}${fix}`;
|
|
280
|
-
}
|
|
281
|
-
function runnableNext(manifest, spec, json) {
|
|
282
|
-
const program = manifest.rootPath.join(' ');
|
|
283
|
-
return (spec.next ?? []).map((n) => ({ command: `${program} ${n.command}${json && !n.command.includes('--json') ? ' --json' : ''}`, when: n.when }));
|
|
284
|
-
}
|
|
285
|
-
const HELP_FLAGS = new Set(['--help', '-h']);
|
|
286
|
-
async function helpDocumentOf(manifest, node) {
|
|
287
|
-
const { commandSchemaOf, typedName } = await import('./schema.js');
|
|
288
|
-
const root = manifest.rootPath;
|
|
289
|
-
const children = manifest.commands
|
|
290
|
-
.filter((c) => c.path.length === node.path.length + 1 && c.path.slice(0, node.path.length).join(' ') === node.path.join(' '))
|
|
291
|
-
.map((c) => typedName(c, root));
|
|
292
|
-
return {
|
|
293
|
-
schemaVersion: 1,
|
|
294
|
-
...commandSchemaOf(node, root),
|
|
295
|
-
...(children.length === 0 ? {} : { commands: children }),
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
191
|
const HELP_WIDTH = 100;
|
|
299
192
|
const processExit = (code) => host.exit(code);
|
|
300
193
|
async function leave(io, code) {
|
|
301
194
|
await io.teardown.run(code);
|
|
302
195
|
io.exit(code);
|
|
303
196
|
}
|
|
304
|
-
export function beforeTerminator(argv) {
|
|
305
|
-
const at = argv.indexOf('--');
|
|
306
|
-
return at === -1 ? argv : argv.slice(0, at);
|
|
307
|
-
}
|
|
308
|
-
function rootNode(manifest, root) {
|
|
309
|
-
return manifest.find(root) ?? { path: root, options: {} };
|
|
310
|
-
}
|
|
311
|
-
const renderHelp = async (manifest, node, io) => {
|
|
312
|
-
const help = await import('./help.js');
|
|
313
|
-
return help.renderHelp(manifest, node, { width: io.width, color: help.colorFor(io.env, detectAgent(io.env, io.tty).interactive) });
|
|
314
|
-
};
|
|
315
|
-
const machineJson = async (...args) => (await import('./schema.js')).machineJson(...args);
|
|
316
|
-
async function unresolved({ manifest, root, io }, argv, at) {
|
|
317
|
-
const node = at ?? rootNode(manifest, root);
|
|
318
|
-
const typed = argv.slice(node.path.length - root.length);
|
|
319
|
-
const first = typed[0] ?? '';
|
|
320
|
-
if (typed.length > 0 && HELP_FLAGS.has(first)) {
|
|
321
|
-
if (beforeTerminator(typed).some(isJsonFlag))
|
|
322
|
-
return { text: `${await machineJson(await helpDocumentOf(manifest, node), beforeTerminator(argv))}\n`, code: ExitCode.OK };
|
|
323
|
-
return { text: await renderHelp(manifest, node, io), code: ExitCode.OK };
|
|
324
|
-
}
|
|
325
|
-
if (first === '--version' || first === '-V')
|
|
326
|
-
return { text: `${versionOf(manifest, io)}\n`, code: ExitCode.OK };
|
|
327
|
-
if (typed.length === 0)
|
|
328
|
-
return { text: await renderHelp(manifest, node, io), code: ExitCode.USAGE };
|
|
329
|
-
throw new UsageError(`unknown command "${typed[0] ?? ''}"`, 'run --help to see the available commands');
|
|
330
|
-
}
|
|
331
|
-
async function completion(manifest, argv, io) {
|
|
332
|
-
if (argv[0] !== 'completion' || manifest.find([...manifest.rootPath, 'completion']) !== undefined)
|
|
333
|
-
return false;
|
|
334
|
-
const { renderCompletion, renderFigSpec, SHELLS } = await import('./completions.js');
|
|
335
|
-
const shell = argv[1] ?? '';
|
|
336
|
-
if (shell === 'fig') {
|
|
337
|
-
io.out.write(`${JSON.stringify(renderFigSpec(manifest), null, 2)}\n`);
|
|
338
|
-
return true;
|
|
339
|
-
}
|
|
340
|
-
const known = SHELLS.find((s) => s === shell);
|
|
341
|
-
if (known === undefined)
|
|
342
|
-
throw new UsageError(`unknown shell "${shell}"`, `completion ${SHELLS.join('|')}|fig`);
|
|
343
|
-
io.out.write(renderCompletion(manifest, known));
|
|
344
|
-
return true;
|
|
345
|
-
}
|
|
346
|
-
async function surface(manifest, argv, io) {
|
|
347
|
-
const head = beforeTerminator(argv);
|
|
348
|
-
if (argv[0] === '__complete') {
|
|
349
|
-
await (await import('./complete-dynamic.js')).completeDynamic(manifest, argv.slice(1), (t) => io.out.write(t));
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
const root = manifest.rootPath;
|
|
353
|
-
if (head[0] === 'config' && head[1] === 'explain' && manifest.config !== undefined && manifest.find([...root, 'config', 'explain']) === undefined) {
|
|
354
|
-
const { explainConfig } = await import('./config-explain.js');
|
|
355
|
-
io.out.write(await explainConfig(manifest, head.slice(2), (specs, flags) => resolution(manifest, specs, flags, io)));
|
|
356
|
-
return true;
|
|
357
|
-
}
|
|
358
|
-
if (await completion(manifest, argv, io))
|
|
359
|
-
return true;
|
|
360
|
-
if (argv[0] === 'help') {
|
|
361
|
-
io.out.write(await helpCommand(manifest, argv.slice(1), manifest.rootPath, io));
|
|
362
|
-
return true;
|
|
363
|
-
}
|
|
364
|
-
if (head.includes('--schema')) {
|
|
365
|
-
const { schemaSurface } = await import('./schema-surface.js');
|
|
366
|
-
io.out.write(`${await machineJson(await schemaSurface(manifest, argv), head)}\n`);
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
if (head[0] === '--mcp') {
|
|
370
|
-
const invoke = async (args) => {
|
|
371
|
-
const out = [];
|
|
372
|
-
const err = [];
|
|
373
|
-
let code = 0;
|
|
374
|
-
await execute(manifest, {
|
|
375
|
-
argv: args,
|
|
376
|
-
env: io.env,
|
|
377
|
-
stdout: { write: (s) => out.push(s) },
|
|
378
|
-
stderr: { write: (s) => err.push(s) },
|
|
379
|
-
exit: (c) => {
|
|
380
|
-
code = c;
|
|
381
|
-
},
|
|
382
|
-
});
|
|
383
|
-
return { stdout: out.join(''), stderr: err.join(''), code };
|
|
384
|
-
};
|
|
385
|
-
const { serveMcp } = await import('./mcp.js');
|
|
386
|
-
await serveMcp(manifest, { input: io.stdin, output: io.out, invoke });
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
async function helpCommand(manifest, argv, root, io) {
|
|
392
|
-
const { node } = manifest.resolve(argv, root);
|
|
393
|
-
return renderHelp(manifest, node ?? rootNode(manifest, root), io);
|
|
394
|
-
}
|
|
395
197
|
function changedOf(node, data) {
|
|
396
198
|
const value = isPlainObject(data) ? data['changed'] : undefined;
|
|
397
199
|
if (typeof value === 'boolean')
|
|
@@ -405,18 +207,10 @@ function exitCodeOf(data) {
|
|
|
405
207
|
const code = isPlainObject(data) ? data['exitCode'] : undefined;
|
|
406
208
|
return isExitCode(code) ? code : ExitCode.OK;
|
|
407
209
|
}
|
|
408
|
-
|
|
409
|
-
return arg === '--json' || arg.startsWith('--json=');
|
|
410
|
-
}
|
|
411
|
-
function versionOf(manifest, io) {
|
|
412
|
-
const declared = manifest.version ?? (typeof io.pkg?.data['version'] === 'string' ? io.pkg.data['version'] : undefined);
|
|
413
|
-
if (declared === undefined)
|
|
414
|
-
throw new ConfigError('no version declared', 'pass version to defineProgram, or set "version" in the owning package.json');
|
|
415
|
-
return declared;
|
|
416
|
-
}
|
|
210
|
+
const readsLazily = (arg) => arg.startsWith('--json=') || arg === '--format=agent';
|
|
417
211
|
async function dispatch(manifest, { node, rest: typed, name }, io) {
|
|
418
|
-
const select = typed.some(
|
|
419
|
-
const { args: rest, fields } = select?.jsonFields(typed) ?? { args: typed };
|
|
212
|
+
const select = typed.some(readsLazily) ? await import('./fields.js') : undefined;
|
|
213
|
+
const { args: rest, fields, lines } = select?.jsonFields(typed, node) ?? { args: typed };
|
|
420
214
|
if (fields?.length === 0)
|
|
421
215
|
return { json: true, text: `${JSON.stringify(select?.listFields(node))}\n` };
|
|
422
216
|
if (fields !== undefined)
|
|
@@ -424,17 +218,17 @@ async function dispatch(manifest, { node, rest: typed, name }, io) {
|
|
|
424
218
|
const parsed = parseArgs({ args: rest, options: toParseConfig(node.options, manifest.config !== undefined), allowPositionals: true, strict: true, tokens: true });
|
|
425
219
|
const flags = canonical(parsed.values, node.options, parsed.tokens);
|
|
426
220
|
const json = flags.json === true;
|
|
427
|
-
if (flags.help === true && json)
|
|
428
|
-
return { json, text: `${await machineJson(await helpDocumentOf(manifest, node), json ? ['--json'] : [])}\n` };
|
|
429
221
|
if (flags.help === true)
|
|
430
|
-
return { json, text: await
|
|
222
|
+
return { json, text: await (await import('./surfaces.js')).helpFor(manifest, node, io, json) };
|
|
431
223
|
if (flags.version === true)
|
|
432
|
-
return { json, text: `${versionOf(manifest, io)}\n` };
|
|
224
|
+
return { json, text: `${(await import('./surfaces.js')).versionOf(manifest, io)}\n` };
|
|
433
225
|
const resolved = await resolveValues(manifest, node.options, flags, io);
|
|
434
226
|
if (resolved.explainText !== undefined)
|
|
435
227
|
return { json, text: resolved.explainText };
|
|
436
228
|
const { provenance } = resolved;
|
|
437
|
-
|
|
229
|
+
const relations = relationsOf(node);
|
|
230
|
+
if (relations.length > 0)
|
|
231
|
+
(await import('./relations.js')).checkRelations(relations, resolved.values, provenance);
|
|
438
232
|
const values = await coerce(node.options, resolved.values);
|
|
439
233
|
const { positionals, passthrough } = splitPositionals(parsed.tokens);
|
|
440
234
|
requirePositionals(node, positionals);
|
|
@@ -447,7 +241,7 @@ async function dispatch(manifest, { node, rest: typed, name }, io) {
|
|
|
447
241
|
await manifest.fire('postRun', name, values);
|
|
448
242
|
const changed = changedOf(node, data);
|
|
449
243
|
const selected = fields === undefined || select === undefined ? data : select.selectFields(data, fields);
|
|
450
|
-
return { json, data: selected, provenance, ...(changed === undefined ? {} : { changed }) };
|
|
244
|
+
return { json, lines, data: selected, provenance, ...(changed === undefined ? {} : { changed }) };
|
|
451
245
|
}
|
|
452
246
|
function requirePositionals(node, positionals) {
|
|
453
247
|
const required = (node.arguments ?? []).filter((a) => a.required !== false && a.variadic !== true);
|
|
@@ -475,29 +269,16 @@ async function emit(io, outcome) {
|
|
|
475
269
|
}
|
|
476
270
|
const meta = { provenance: outcome.provenance ?? {}, ...(outcome.changed === undefined ? {} : { changed: outcome.changed }) };
|
|
477
271
|
const envelope = { ok: true, data: outcome.data, meta };
|
|
478
|
-
io.out.write(outcome.json ? `${JSON.stringify(envelope)}\n` : `${render(outcome.data)}\n`);
|
|
272
|
+
io.out.write(outcome.json ? `${JSON.stringify(envelope)}\n` : (outcome.lines?.(outcome.data) ?? `${render(outcome.data)}\n`));
|
|
479
273
|
return await leave(io, exitCodeOf(outcome.data));
|
|
480
274
|
}
|
|
481
275
|
async function report(cause, { manifest, io, argv, json, name }) {
|
|
482
|
-
const
|
|
276
|
+
const { describeFailure, failureText } = await import('./failure.js');
|
|
277
|
+
const failure = await describeFailure(cause, argv, resolveCommand(manifest, argv) ?? undefined, cause instanceof ActionRequired ? cause.spec : undefined);
|
|
483
278
|
if (failure.silent === true)
|
|
484
279
|
return await leave(io, failure.code);
|
|
485
280
|
await manifest.fire('onError', name, {});
|
|
486
|
-
|
|
487
|
-
const next = runnableNext(manifest, failure.action, json);
|
|
488
|
-
const rendered = { ...failure, action: { ...failure.action, next } };
|
|
489
|
-
const body = { ok: false, status: 'action_required', reason: failure.action.reason, message: failure.message, next, hint: failure.hint, error: { code: failure.code, message: failure.message } };
|
|
490
|
-
if (json)
|
|
491
|
-
io.out.write(`${JSON.stringify(body)}\n`);
|
|
492
|
-
else
|
|
493
|
-
io.err.write(textFailure(rendered));
|
|
494
|
-
return await leave(io, failure.code);
|
|
495
|
-
}
|
|
496
|
-
const body = { code: failure.code, message: failure.message, hint: failure.hint, ...(failure.fix === undefined ? {} : { fix: failure.fix }) };
|
|
497
|
-
if (json)
|
|
498
|
-
io.out.write(`${JSON.stringify({ ok: false, error: body })}\n`);
|
|
499
|
-
else
|
|
500
|
-
io.err.write(textFailure(failure));
|
|
281
|
+
(json ? io.out : io.err).write(failureText(failure, manifest, json));
|
|
501
282
|
return await leave(io, failure.code);
|
|
502
283
|
}
|
|
503
284
|
function ioOf(opts) {
|
|
@@ -528,11 +309,11 @@ export async function execute(manifest, opts = {}) {
|
|
|
528
309
|
try {
|
|
529
310
|
argv = manifest.declares('parse') ? await manifest.parse([...typed]) : typed;
|
|
530
311
|
json = beforeTerminator(argv).some(isJsonFlag);
|
|
531
|
-
if (await
|
|
312
|
+
if (mayServe(argv) && (await (await import('./surfaces.js')).serve(manifest, argv, io, { resolution: (specs, flags) => resolution(manifest, specs, flags, io), execute })))
|
|
532
313
|
return await leave(io, ExitCode.OK);
|
|
533
314
|
const { node, rest } = manifest.resolve(argv, root);
|
|
534
315
|
if (node?.run === undefined) {
|
|
535
|
-
const { text, code } = await unresolved({ manifest, root, io }, argv, node);
|
|
316
|
+
const { text, code } = await (await import('./surfaces.js')).unresolved({ manifest, root, io }, argv, node);
|
|
536
317
|
(code === ExitCode.OK ? io.out : io.err).write(text);
|
|
537
318
|
return await leave(io, code);
|
|
538
319
|
}
|
|
@@ -569,3 +350,4 @@ export async function run(target, opts = {}) {
|
|
|
569
350
|
});
|
|
570
351
|
return await execute(manifest, opts);
|
|
571
352
|
}
|
|
353
|
+
export { beforeTerminator } from './argv.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ExitCode as ExitCodeType } from './exit-code.js';
|
|
2
|
+
import { type ActionRequiredSpec, type CommandNode, type Manifest } from './manifest.js';
|
|
3
|
+
export interface Failure {
|
|
4
|
+
code: ExitCodeType;
|
|
5
|
+
message: string;
|
|
6
|
+
hint?: string;
|
|
7
|
+
/** E3 — the exact command or flag to run next, where one exists. Never a guess. */
|
|
8
|
+
fix?: string;
|
|
9
|
+
/** An exit signal: honour the code, print nothing. */
|
|
10
|
+
silent?: boolean;
|
|
11
|
+
/** N11: the caller must act; carried into the envelope with the runnable `next[]`. */
|
|
12
|
+
action?: ActionRequiredSpec;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* E2/E3 — a usage error never prints a stack, a runtime failure never prints help.
|
|
16
|
+
*
|
|
17
|
+
* `action` is the spec of a `ctx.actionRequired(…)` unwind, which the engine recognises by its
|
|
18
|
+
* own class and hands over; the class stays in `execute.ts` because a handler throws it on the
|
|
19
|
+
* startup path.
|
|
20
|
+
*/
|
|
21
|
+
export declare function describeFailure(cause: unknown, argv: string[], node: CommandNode | undefined, action: ActionRequiredSpec | undefined): Promise<Failure>;
|
|
22
|
+
/**
|
|
23
|
+
* What a described failure prints: under `--json` the envelope, for stdout; otherwise the prose,
|
|
24
|
+
* for stderr. The caller picks the stream by the same `json` it passes here (O1, D-140).
|
|
25
|
+
*/
|
|
26
|
+
export declare function failureText(failure: Failure, manifest: Manifest, json: boolean): string;
|