burgee 0.7.1 → 0.9.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/README.md +1 -0
- package/dist/check.d.ts +22 -0
- package/dist/check.js +35 -0
- package/dist/cli.d.ts +31 -3
- package/dist/cli.js +36 -7
- package/dist/commander/argument.js +0 -3
- package/dist/commander/command.d.ts +7 -3
- package/dist/commander/command.js +54 -187
- package/dist/commander/error.js +0 -2
- package/dist/commander/help.js +0 -17
- package/dist/commander/option.js +0 -14
- package/dist/compat.d.ts +30 -0
- package/dist/compat.js +4 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +2 -0
- package/dist/definition.d.ts +24 -6
- package/dist/definition.js +18 -14
- package/dist/execute.d.ts +1 -0
- package/dist/execute.js +45 -27
- package/dist/exit-code.d.ts +17 -1
- package/dist/exit-code.js +1 -0
- package/dist/help-entry.d.ts +2 -0
- package/dist/help-entry.js +1 -0
- package/dist/help.d.ts +12 -0
- package/dist/help.js +6 -0
- package/dist/index.d.ts +32 -7
- package/dist/index.js +1 -7
- package/dist/mcp-entry.d.ts +2 -0
- package/dist/mcp-entry.js +1 -0
- package/dist/mcp.d.ts +33 -13
- package/dist/mcp.js +4 -3
- package/dist/meow/parse.d.ts +21 -0
- package/dist/meow/parse.js +43 -0
- package/dist/meow/present.d.ts +35 -0
- package/dist/meow/present.js +58 -0
- package/dist/meow/types.d.ts +47 -0
- package/dist/meow/types.js +3 -0
- package/dist/meow/validate.d.ts +35 -0
- package/dist/meow/validate.js +144 -0
- package/dist/meow.d.ts +6 -0
- package/dist/meow.js +146 -0
- package/dist/migrate.d.ts +142 -0
- package/dist/migrate.js +284 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +1 -1
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +3 -0
- package/dist/schema-entry.d.ts +3 -0
- package/dist/schema-entry.js +2 -0
- package/dist/schema.json +1 -1
- package/dist/testing-helpers.js +3 -1
- package/dist/validate.d.ts +15 -0
- package/dist/validate.js +10 -0
- package/dist/yargs/burgee.js +0 -14
- package/dist/yargs/cliui.js +0 -53
- package/dist/yargs/command.js +0 -7
- package/dist/yargs/completion.js +0 -5
- package/dist/yargs/factory.js +7 -57
- package/dist/yargs/middleware.js +0 -5
- package/dist/yargs/shim.js +0 -20
- package/dist/yargs/usage.js +0 -8
- package/dist/yargs/utils.js +0 -11
- package/dist/yargs/validation.js +0 -6
- package/dist/yargs/y18n.js +0 -6
- package/package.json +32 -7
package/dist/commander/error.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** commander's error classes, byte-for-byte in shape: `code`, `exitCode`, `nestedError`. */
|
|
2
1
|
export class CommanderError extends Error {
|
|
3
2
|
code;
|
|
4
3
|
exitCode;
|
|
@@ -19,4 +18,3 @@ export class InvalidArgumentError extends CommanderError {
|
|
|
19
18
|
this.name = this.constructor.name;
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
|
-
//# sourceMappingURL=error.js.map
|
package/dist/commander/help.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { stripVTControlCharacters } from 'node:util';
|
|
2
2
|
import { humanReadableArgName } from './argument.js';
|
|
3
|
-
/** Methods are static in style so a subclass, or plain functions via `configureHelp`, can override them. */
|
|
4
3
|
export class Help {
|
|
5
4
|
helpWidth = undefined;
|
|
6
5
|
minWidthToWrap = 40;
|
|
7
6
|
sortSubcommands = false;
|
|
8
7
|
sortOptions = false;
|
|
9
8
|
showGlobalOptions = false;
|
|
10
|
-
/** Called after `configureHelp` overrides are applied and just before `formatHelp`. */
|
|
11
9
|
prepareContext(contextOptions) {
|
|
12
10
|
this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
|
|
13
11
|
}
|
|
@@ -28,7 +26,6 @@ export class Help {
|
|
|
28
26
|
const visibleOptions = cmd.options.filter((option) => !option.hidden);
|
|
29
27
|
const helpOption = cmd._getHelpOption();
|
|
30
28
|
if (helpOption && !helpOption.hidden) {
|
|
31
|
-
// Historical: hide the built-in help flags a user option already took.
|
|
32
29
|
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
|
|
33
30
|
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
|
|
34
31
|
if (!removeShort && !removeLong)
|
|
@@ -54,7 +51,6 @@ export class Help {
|
|
|
54
51
|
return globalOptions;
|
|
55
52
|
}
|
|
56
53
|
visibleArguments(cmd) {
|
|
57
|
-
// Side effect: apply the legacy descriptions before the arguments are displayed.
|
|
58
54
|
if (cmd._argsDescription) {
|
|
59
55
|
for (const argument of cmd.registeredArguments) {
|
|
60
56
|
argument.description = argument.description || cmd._argsDescription[argument.name()] || '';
|
|
@@ -110,7 +106,6 @@ export class Help {
|
|
|
110
106
|
commandDescription(cmd) {
|
|
111
107
|
return cmd.description();
|
|
112
108
|
}
|
|
113
|
-
/** Summary, falling back to description for backwards compatibility. */
|
|
114
109
|
subcommandDescription(cmd) {
|
|
115
110
|
return cmd.summary() || cmd.description();
|
|
116
111
|
}
|
|
@@ -120,7 +115,6 @@ export class Help {
|
|
|
120
115
|
extraInfo.push(`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
|
|
121
116
|
}
|
|
122
117
|
if (option.defaultValue !== undefined) {
|
|
123
|
-
// Defaults for boolean and negated are more for the programmer than the end user.
|
|
124
118
|
const showDefault = option.required || option.optional || (option.isBoolean() && typeof option.defaultValue === 'boolean');
|
|
125
119
|
if (showDefault)
|
|
126
120
|
extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
|
|
@@ -154,7 +148,6 @@ export class Help {
|
|
|
154
148
|
return [];
|
|
155
149
|
return [helper.styleTitle(heading), ...items, ''];
|
|
156
150
|
}
|
|
157
|
-
/** Groups in order of first appearance among all items; members in order of the visible items. */
|
|
158
151
|
groupItems(unsortedItems, visibleItems, getGroup) {
|
|
159
152
|
const result = new Map();
|
|
160
153
|
for (const item of unsortedItems) {
|
|
@@ -203,7 +196,6 @@ export class Help {
|
|
|
203
196
|
}
|
|
204
197
|
return output.join('\n');
|
|
205
198
|
}
|
|
206
|
-
/** Width ignoring ANSI escapes, for padding and wrapping. */
|
|
207
199
|
displayWidth(str) {
|
|
208
200
|
return stripVTControlCharacters(str).length;
|
|
209
201
|
}
|
|
@@ -211,7 +203,6 @@ export class Help {
|
|
|
211
203
|
return str;
|
|
212
204
|
}
|
|
213
205
|
styleUsage(str) {
|
|
214
|
-
// Assume the default usage shape: command subcommand [options] [command] <foo> [bar]
|
|
215
206
|
return str
|
|
216
207
|
.split(' ')
|
|
217
208
|
.map((word) => {
|
|
@@ -273,15 +264,9 @@ export class Help {
|
|
|
273
264
|
padWidth(cmd, helper) {
|
|
274
265
|
return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
|
|
275
266
|
}
|
|
276
|
-
/** Manually wrapped text: a line break followed by whitespace. */
|
|
277
267
|
preformatted(str) {
|
|
278
268
|
return /\n[^\S\r\n]/.test(str);
|
|
279
269
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Pad the term and wrap the description, indenting the following lines:
|
|
282
|
-
* TTT DDD DDDD
|
|
283
|
-
* DD DDD
|
|
284
|
-
*/
|
|
285
270
|
formatItem(term, termWidth, description, helper) {
|
|
286
271
|
const itemIndent = 2;
|
|
287
272
|
const itemIndentStr = ' '.repeat(itemIndent);
|
|
@@ -301,7 +286,6 @@ export class Help {
|
|
|
301
286
|
}
|
|
302
287
|
return itemIndentStr + paddedTerm + ' '.repeat(spacerWidth) + formattedDescription.replace(/\n/g, `\n${itemIndentStr}`);
|
|
303
288
|
}
|
|
304
|
-
/** Wrap at whitespace, preserving existing line breaks; skipped below `minWidthToWrap`. */
|
|
305
289
|
boxWrap(str, width) {
|
|
306
290
|
if (width < this.minWidthToWrap)
|
|
307
291
|
return str;
|
|
@@ -333,4 +317,3 @@ export class Help {
|
|
|
333
317
|
return wrappedLines.join('\n');
|
|
334
318
|
}
|
|
335
319
|
}
|
|
336
|
-
//# sourceMappingURL=help.js.map
|
package/dist/commander/option.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {} from './argument.js';
|
|
2
1
|
import { InvalidArgumentError } from './error.js';
|
|
3
2
|
export class Option {
|
|
4
3
|
flags;
|
|
@@ -25,7 +24,6 @@ export class Option {
|
|
|
25
24
|
this.description = description || '';
|
|
26
25
|
this.required = flags.includes('<');
|
|
27
26
|
this.optional = flags.includes('[');
|
|
28
|
-
// `<value,...>` describes custom splitting of one argument, not a variadic option.
|
|
29
27
|
this.variadic = /\w\.\.\.[>\]]$/.test(flags);
|
|
30
28
|
const { shortFlag, longFlag } = splitOptionFlags(flags);
|
|
31
29
|
this.short = shortFlag;
|
|
@@ -38,7 +36,6 @@ export class Option {
|
|
|
38
36
|
this.defaultValueDescription = description;
|
|
39
37
|
return this;
|
|
40
38
|
}
|
|
41
|
-
/** Value used when the option appears without an option-argument. `parseArg` still runs. */
|
|
42
39
|
preset(arg) {
|
|
43
40
|
this.presetArg = arg;
|
|
44
41
|
return this;
|
|
@@ -47,7 +44,6 @@ export class Option {
|
|
|
47
44
|
this.conflictsWith = this.conflictsWith.concat(names);
|
|
48
45
|
return this;
|
|
49
46
|
}
|
|
50
|
-
/** Values implied for other options when this one is set and they are not. `parseArg` does not run on them. */
|
|
51
47
|
implies(impliedOptionValues) {
|
|
52
48
|
const newImplied = typeof impliedOptionValues === 'string' ? { [impliedOptionValues]: true } : impliedOptionValues;
|
|
53
49
|
this.implied = Object.assign(this.implied ?? {}, newImplied);
|
|
@@ -90,7 +86,6 @@ export class Option {
|
|
|
90
86
|
return this.long.replace(/^--/, '');
|
|
91
87
|
return (this.short ?? '').replace(/^-/, '');
|
|
92
88
|
}
|
|
93
|
-
/** camelCase key used on the options object; `--no-foo` shares `foo` with `--foo`. */
|
|
94
89
|
attributeName() {
|
|
95
90
|
return camelcase(this.negate ? this.name().replace(/^no-/, '') : this.name());
|
|
96
91
|
}
|
|
@@ -101,15 +96,10 @@ export class Option {
|
|
|
101
96
|
is(arg) {
|
|
102
97
|
return this.short === arg || this.long === arg;
|
|
103
98
|
}
|
|
104
|
-
/** Options are one of boolean, negated, required-argument or optional-argument. */
|
|
105
99
|
isBoolean() {
|
|
106
100
|
return !this.required && !this.optional && !this.negate;
|
|
107
101
|
}
|
|
108
102
|
}
|
|
109
|
-
/**
|
|
110
|
-
* `--build` and `--no-build` share one value. This works out which of the pair a
|
|
111
|
-
* value (probably) came from, so implied values are only applied for the right one.
|
|
112
|
-
*/
|
|
113
103
|
export class DualOptions {
|
|
114
104
|
positiveOptions = new Map();
|
|
115
105
|
negativeOptions = new Map();
|
|
@@ -135,7 +125,6 @@ export class DualOptions {
|
|
|
135
125
|
function camelcase(str) {
|
|
136
126
|
return str.split('-').reduce((acc, word) => acc + (word[0] ?? '').toUpperCase() + word.slice(1));
|
|
137
127
|
}
|
|
138
|
-
/** Split `'-m,--mixed <value>'` into its short and long flags, failing noisily on anything else. */
|
|
139
128
|
export function splitOptionFlags(flags) {
|
|
140
129
|
let shortFlag;
|
|
141
130
|
let longFlag;
|
|
@@ -147,10 +136,8 @@ export function splitOptionFlags(flags) {
|
|
|
147
136
|
shortFlag = flagParts.shift();
|
|
148
137
|
if (longFlagExp.test(head()))
|
|
149
138
|
longFlag = flagParts.shift();
|
|
150
|
-
// Long then short. Rarely used but fine.
|
|
151
139
|
if (!shortFlag && shortFlagExp.test(head()))
|
|
152
140
|
shortFlag = flagParts.shift();
|
|
153
|
-
// Two long flags, like '--ws, --workspace': the supported way to have a shortish flag.
|
|
154
141
|
if (!shortFlag && longFlagExp.test(head())) {
|
|
155
142
|
shortFlag = longFlag;
|
|
156
143
|
longFlag = flagParts.shift();
|
|
@@ -175,4 +162,3 @@ export function splitOptionFlags(flags) {
|
|
|
175
162
|
}
|
|
176
163
|
return { shortFlag, longFlag };
|
|
177
164
|
}
|
|
178
|
-
//# sourceMappingURL=option.js.map
|
package/dist/compat.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A7 — the graded pass rate per host, as `burgee migrate` reports it.
|
|
3
|
+
*
|
|
4
|
+
* These are `compat-oracle`'s own numbers: the incumbent's test suite, vendored and run
|
|
5
|
+
* against burgee's front-end, which is what "drop-in" means here and the only claim in the
|
|
6
|
+
* package that is measured rather than argued.
|
|
7
|
+
*
|
|
8
|
+
* **They live here as a data module rather than as a literal in the report**, and
|
|
9
|
+
* `compat-baseline-lock.test.ts` holds this file equal to
|
|
10
|
+
* `packages/compat-oracle/baseline/<host>.json` — the same files the compat page renders.
|
|
11
|
+
* Changing a number here without the measurement moving fails that lock, which is the
|
|
12
|
+
* whole point: a number typed into a report template is a number that goes stale silently,
|
|
13
|
+
* and this repository has published four of those and caught them all late.
|
|
14
|
+
*
|
|
15
|
+
* It cannot be read from the oracle at run time. `compat-oracle` is `private: true` and is
|
|
16
|
+
* never published, so a user who installs `burgee` has no baseline directory to read; a
|
|
17
|
+
* copy with a lock on it is the strongest control available on the published side, and the
|
|
18
|
+
* lock is what makes it a copy rather than a claim.
|
|
19
|
+
*/
|
|
20
|
+
/** One row of the baseline, in the shape `baseline/<host>.json` stores it. */
|
|
21
|
+
export interface Graded {
|
|
22
|
+
/** Cases in the incumbent's own suite. */
|
|
23
|
+
reference: number;
|
|
24
|
+
/** Cases that pass against burgee's front-end. */
|
|
25
|
+
passed: number;
|
|
26
|
+
/** `passed / reference`, carried rather than recomputed so it is the oracle's own division. */
|
|
27
|
+
rate: number;
|
|
28
|
+
}
|
|
29
|
+
/** The two hosts `migrate` rewrites, and nothing else — a row here without a mapping would claim a path that does not exist. */
|
|
30
|
+
export declare const GRADED: Readonly<Record<string, Graded>>;
|
package/dist/compat.js
ADDED
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `burgee/config` — the configuration layer, by itself.
|
|
3
|
+
*
|
|
4
|
+
* Precedence, provenance and `--explain` come from `seniority`, the package whose job that
|
|
5
|
+
* is. They were re-exported from the root barrel until the barrel's cost was measured
|
|
6
|
+
* (see `index.ts`): 3,135 bundled bytes on the startup path of every program, for a
|
|
7
|
+
* surface a program only touches when it wants to read or explain its own configuration.
|
|
8
|
+
*/
|
|
9
|
+
export { ConfigError, envName, resolve, screaming, type Candidate, type Layers, type Provenance, type Resolution, type Source } from 'seniority/precedence';
|
|
10
|
+
export { explain } from 'seniority/explain';
|
package/dist/config.js
ADDED
package/dist/definition.d.ts
CHANGED
|
@@ -26,11 +26,20 @@ import { type OptionSpec } from './manifest.js';
|
|
|
26
26
|
export declare const WITHHELD = "withheld";
|
|
27
27
|
/**
|
|
28
28
|
* What must be true of a declaration before anything runs (yargs #1198, #887, #1679):
|
|
29
|
-
* a known type, one short alias per command, no two keys that meet on the command line
|
|
29
|
+
* a known type, one short alias per command, no two keys that meet on the command line, and
|
|
30
|
+
* none of V5's reserved surfaces.
|
|
31
|
+
*
|
|
32
|
+
* The reserved names used to be a second loop over the same keys in {@link checkCommand},
|
|
33
|
+
* and the numeric bound used to be a fourth branch in this one. Both moved to pay for the
|
|
34
|
+
* canonical-key check below without raising a weight ceiling — `./plugin` had 5 bytes of
|
|
35
|
+
* headroom — and both are better where they are now: `flag` is computed once here and
|
|
36
|
+
* `kebab` is the identity on every reserved name, and a numeric bound is a fact about a
|
|
37
|
+
* spec rather than about a name. The file is 51 bytes smaller than before the check existed.
|
|
30
38
|
*/
|
|
31
39
|
export declare function checkDefinition(name: string, options: Record<string, OptionSpec>): void;
|
|
32
40
|
/**
|
|
33
|
-
* The whole door:
|
|
41
|
+
* The whole door: {@link checkDefinition} — which carries V5's reserved names — and
|
|
42
|
+
* {@link checkEffects}.
|
|
34
43
|
*
|
|
35
44
|
* This exists as one function because it was two. `defineCommand` ran both; `Manifest.use()`
|
|
36
45
|
* ran neither, so a plugin's command was admitted unread — and a plugin option named `json`
|
|
@@ -38,8 +47,17 @@ export declare function checkDefinition(name: string, options: Record<string, Op
|
|
|
38
47
|
* second copy of the guard beside `use()`; it is that there is one guard and both callers
|
|
39
48
|
* reach it, which is the only arrangement a reader can check by looking.
|
|
40
49
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
50
|
+
* It takes the declaration whole, for exactly that reason. It took `effects` and `runs` as
|
|
51
|
+
* required arguments so a caller could not decline a check by writing nothing; D1 would have
|
|
52
|
+
* made that five, and a sixth field would make it six. Reading the object means a field the
|
|
53
|
+
* door checks is one no caller has to remember to forward — including whether it runs.
|
|
44
54
|
*/
|
|
45
|
-
export declare function checkCommand(name: string,
|
|
55
|
+
export declare function checkCommand(name: string, declared: Declared): void;
|
|
56
|
+
/** What the door reads of a command: a first-party declaration and a plugin's have the same fields. */
|
|
57
|
+
export interface Declared {
|
|
58
|
+
options?: Record<string, OptionSpec>;
|
|
59
|
+
effects?: unknown;
|
|
60
|
+
deprecated?: boolean | string;
|
|
61
|
+
run?: unknown;
|
|
62
|
+
load?: unknown;
|
|
63
|
+
}
|
package/dist/definition.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { kebab } from './names.js';
|
|
1
|
+
import { camel, kebab } from './names.js';
|
|
2
2
|
const TYPES = new Set(['string', 'boolean', 'number']);
|
|
3
3
|
const EFFECTS = ['read_only', 'idempotent', 'non_idempotent'];
|
|
4
4
|
export const WITHHELD = 'withheld';
|
|
5
5
|
const DECLARED = [...EFFECTS, WITHHELD];
|
|
6
6
|
const RESERVED = new Set(['json', 'help', 'schema', 'mcp', 'version', 'explain']);
|
|
7
|
-
function
|
|
7
|
+
function checkSpec(name, key, spec, options) {
|
|
8
|
+
checkDeprecated(`option "${key}" of "${name}"`, spec.deprecated);
|
|
9
|
+
if ((spec.minimum !== undefined || spec.maximum !== undefined || spec.integer !== undefined) && spec.type !== 'number') {
|
|
10
|
+
throw new Error(`burgee: option "${key}" of "${name}" declares a numeric bound but is not a number`);
|
|
11
|
+
}
|
|
8
12
|
for (const field of ['dependsOn', 'exclusive']) {
|
|
9
13
|
for (const other of spec[field] ?? []) {
|
|
10
14
|
if (other !== key && other in options)
|
|
@@ -27,14 +31,13 @@ export function checkDefinition(name, options) {
|
|
|
27
31
|
shorts.set(spec.short, key);
|
|
28
32
|
}
|
|
29
33
|
const flag = kebab(key);
|
|
30
|
-
|
|
34
|
+
if (RESERVED.has(flag))
|
|
35
|
+
throw new Error(`burgee: option "${key}" is reserved and cannot be redefined`);
|
|
36
|
+
const clash = flags.get(flag) ?? (camel(flag) === key ? undefined : camel(flag));
|
|
31
37
|
if (clash !== undefined)
|
|
32
38
|
throw new Error(`burgee: options "${clash}" and "${key}" of "${name}" are both --${flag}`);
|
|
33
39
|
flags.set(flag, key);
|
|
34
|
-
|
|
35
|
-
throw new Error(`burgee: option "${key}" of "${name}" declares a numeric bound but is not a number`);
|
|
36
|
-
}
|
|
37
|
-
checkRelationNames(name, key, spec, options);
|
|
40
|
+
checkSpec(name, key, spec, options);
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
function checkEffects(name, effects, runs) {
|
|
@@ -47,11 +50,12 @@ function checkEffects(name, effects, runs) {
|
|
|
47
50
|
throw new Error(`burgee: command "${name}" declares effects ${JSON.stringify(effects)}, which is not an effects; use ${DECLARED.join(', ')}`);
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
function checkDeprecated(what, deprecated) {
|
|
54
|
+
if (deprecated === true || deprecated === '')
|
|
55
|
+
throw new Error(`burgee: ${what} is deprecated with no replacement; name it, e.g. deprecated: '--force'`);
|
|
56
|
+
}
|
|
57
|
+
export function checkCommand(name, declared) {
|
|
58
|
+
checkDeprecated(`command "${name}"`, declared.deprecated);
|
|
59
|
+
checkDefinition(name, declared.options ?? {});
|
|
60
|
+
checkEffects(name, declared.effects, declared.run !== undefined || declared.load !== undefined);
|
|
57
61
|
}
|
package/dist/execute.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface Command<S extends OptionSpecs = OptionSpecs> {
|
|
|
32
32
|
group?: string;
|
|
33
33
|
epilogue?: string;
|
|
34
34
|
hidden?: boolean;
|
|
35
|
+
/** What replaces this command, e.g. `'deploy'`: shown in help, `--schema` and the warning. `true` alone is refused (D1). */
|
|
35
36
|
deprecated?: boolean | string;
|
|
36
37
|
/**
|
|
37
38
|
* What running it does to the world (N6). Declaring one of the three is what exposes the
|
package/dist/execute.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
-
import { ConfigError,
|
|
3
|
+
import { ConfigError, resolve as resolveLayers } from 'seniority/precedence';
|
|
4
4
|
import { detectAgent } from './agent.js';
|
|
5
5
|
import { checkCommand } from './definition.js';
|
|
6
6
|
import { ExitCode, isExitCode } from './exit-code.js';
|
|
7
|
-
import { renderHelp } from './help.js';
|
|
8
7
|
import { Manifest, relationsOf } from './manifest.js';
|
|
9
|
-
import { serveMcp } from './mcp.js';
|
|
10
8
|
import { camel, kebab } from './names.js';
|
|
11
9
|
import { nearestPackage } from './pkg.js';
|
|
12
10
|
import { host } from './runtime.js';
|
|
13
|
-
import { commandSchemaOf, machineJson, schemaOf, summaryOf, typedName } from './schema.js';
|
|
14
11
|
import { detachedTeardown, processTeardown } from './shutdown.js';
|
|
15
|
-
import { checkRelations, coerce, UsageError } from './validate.js';
|
|
12
|
+
import { AuthError, checkRelations, coerce, UsageError } from './validate.js';
|
|
16
13
|
function helpFields(c) {
|
|
17
14
|
const node = {};
|
|
18
15
|
if (c.description !== undefined)
|
|
@@ -38,7 +35,7 @@ function helpFields(c) {
|
|
|
38
35
|
return node;
|
|
39
36
|
}
|
|
40
37
|
export function defineCommand(command) {
|
|
41
|
-
checkCommand(command.name, command
|
|
38
|
+
checkCommand(command.name, command);
|
|
42
39
|
return command;
|
|
43
40
|
}
|
|
44
41
|
function addTree(manifest, parent, commands) {
|
|
@@ -182,7 +179,7 @@ async function resolveValues(manifest, specs, values, io) {
|
|
|
182
179
|
const out = { values: resolution.values, provenance: resolution.provenance };
|
|
183
180
|
const asked = values['explain'];
|
|
184
181
|
if (typeof asked === 'string')
|
|
185
|
-
out.explainText = explain(asked, resolution);
|
|
182
|
+
out.explainText = (await import('seniority/explain')).explain(asked, resolution);
|
|
186
183
|
for (const [name, spec] of Object.entries(specs)) {
|
|
187
184
|
if (out.values[name] === undefined && spec.required === true && out.explainText === undefined) {
|
|
188
185
|
throw new UsageError(`missing required option --${kebab(name)}`, `pass --${kebab(name)} <value>`);
|
|
@@ -214,6 +211,18 @@ function exitSignal(cause) {
|
|
|
214
211
|
const code = cause?.code;
|
|
215
212
|
return typeof code === 'number' && isExitCode(code) ? code : undefined;
|
|
216
213
|
}
|
|
214
|
+
const CLASSIFIED = [
|
|
215
|
+
[UsageError, ExitCode.USAGE],
|
|
216
|
+
[AuthError, ExitCode.AUTH],
|
|
217
|
+
[ConfigError, ExitCode.CONFIG],
|
|
218
|
+
];
|
|
219
|
+
function carried(cause) {
|
|
220
|
+
const { hint, fix } = (cause ?? {});
|
|
221
|
+
return {
|
|
222
|
+
...(typeof hint === 'string' ? { hint } : {}),
|
|
223
|
+
...(typeof fix === 'string' ? { fix } : {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
217
226
|
async function describeFailure(cause, argv, node) {
|
|
218
227
|
const signal = exitSignal(cause);
|
|
219
228
|
if (signal !== undefined)
|
|
@@ -221,12 +230,9 @@ async function describeFailure(cause, argv, node) {
|
|
|
221
230
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
222
231
|
if (cause instanceof ActionRequired)
|
|
223
232
|
return { code: ExitCode.CANCELLED, message, action: cause.spec, ...(cause.spec.hint === undefined ? {} : { hint: cause.spec.hint }) };
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (cause instanceof ConfigError) {
|
|
228
|
-
return { code: ExitCode.CONFIG, message, ...(cause.hint === undefined ? {} : { hint: cause.hint }) };
|
|
229
|
-
}
|
|
233
|
+
const named = CLASSIFIED.find(([Class]) => cause instanceof Class);
|
|
234
|
+
if (named !== undefined)
|
|
235
|
+
return { code: named[1], message, ...carried(cause) };
|
|
230
236
|
if (isParseArgsFailure(cause)) {
|
|
231
237
|
const explain = await import('./unknown-option.js');
|
|
232
238
|
const dash = explain.singleDashHint(argv);
|
|
@@ -251,7 +257,8 @@ function runnableNext(manifest, spec, json) {
|
|
|
251
257
|
return (spec.next ?? []).map((n) => ({ command: `${program} ${n.command}${json && !n.command.includes('--json') ? ' --json' : ''}`, when: n.when }));
|
|
252
258
|
}
|
|
253
259
|
const HELP_FLAGS = new Set(['--help', '-h']);
|
|
254
|
-
function helpDocumentOf(manifest, node) {
|
|
260
|
+
async function helpDocumentOf(manifest, node) {
|
|
261
|
+
const { commandSchemaOf, typedName } = await import('./schema.js');
|
|
255
262
|
const root = manifest.rootPath;
|
|
256
263
|
const children = manifest.commands
|
|
257
264
|
.filter((c) => c.path.length === node.path.length + 1 && c.path.slice(0, node.path.length).join(' ') === node.path.join(' '))
|
|
@@ -275,19 +282,24 @@ export function beforeTerminator(argv) {
|
|
|
275
282
|
function rootNode(manifest, root) {
|
|
276
283
|
return manifest.find(root) ?? { path: root, options: {} };
|
|
277
284
|
}
|
|
278
|
-
|
|
285
|
+
const renderHelp = async (manifest, node, io) => {
|
|
286
|
+
const help = await import('./help.js');
|
|
287
|
+
return help.renderHelp(manifest, node, { width: io.width, color: help.colorFor(io.env, detectAgent(io.env, io.tty).interactive) });
|
|
288
|
+
};
|
|
289
|
+
const machineJson = async (...args) => (await import('./schema.js')).machineJson(...args);
|
|
290
|
+
async function unresolved({ manifest, root, io }, argv, at) {
|
|
279
291
|
const node = at ?? rootNode(manifest, root);
|
|
280
292
|
const typed = argv.slice(node.path.length - root.length);
|
|
281
293
|
const first = typed[0] ?? '';
|
|
282
294
|
if (typed.length > 0 && HELP_FLAGS.has(first)) {
|
|
283
295
|
if (beforeTerminator(typed).includes('--json'))
|
|
284
|
-
return { text: `${machineJson(helpDocumentOf(manifest, node), beforeTerminator(argv))}\n`, code: ExitCode.OK };
|
|
285
|
-
return { text: renderHelp(manifest, node,
|
|
296
|
+
return { text: `${await machineJson(await helpDocumentOf(manifest, node), beforeTerminator(argv))}\n`, code: ExitCode.OK };
|
|
297
|
+
return { text: await renderHelp(manifest, node, io), code: ExitCode.OK };
|
|
286
298
|
}
|
|
287
299
|
if (first === '--version' || first === '-V')
|
|
288
300
|
return { text: `${versionOf(manifest, io)}\n`, code: ExitCode.OK };
|
|
289
301
|
if (typed.length === 0)
|
|
290
|
-
return { text: renderHelp(manifest, node,
|
|
302
|
+
return { text: await renderHelp(manifest, node, io), code: ExitCode.USAGE };
|
|
291
303
|
throw new UsageError(`unknown command "${typed[0] ?? ''}"`, 'run --help to see the available commands');
|
|
292
304
|
}
|
|
293
305
|
async function completion(manifest, argv, io) {
|
|
@@ -310,11 +322,11 @@ async function surface(manifest, argv, io) {
|
|
|
310
322
|
if (await completion(manifest, argv, io))
|
|
311
323
|
return true;
|
|
312
324
|
if (argv[0] === 'help') {
|
|
313
|
-
io.out.write(helpCommand(manifest, argv.slice(1), manifest.rootPath, io
|
|
325
|
+
io.out.write(await helpCommand(manifest, argv.slice(1), manifest.rootPath, io));
|
|
314
326
|
return true;
|
|
315
327
|
}
|
|
316
328
|
if (head.includes('--schema')) {
|
|
317
|
-
io.out.write(`${machineJson(schemaSurface(manifest, argv), head)}\n`);
|
|
329
|
+
io.out.write(`${await machineJson(await schemaSurface(manifest, argv), head)}\n`);
|
|
318
330
|
return true;
|
|
319
331
|
}
|
|
320
332
|
if (head[0] === '--mcp') {
|
|
@@ -333,13 +345,15 @@ async function surface(manifest, argv, io) {
|
|
|
333
345
|
});
|
|
334
346
|
return { stdout: out.join(''), stderr: err.join(''), code };
|
|
335
347
|
};
|
|
348
|
+
const { serveMcp } = await import('./mcp.js');
|
|
336
349
|
await serveMcp(manifest, { input: io.stdin, output: io.out, invoke });
|
|
337
350
|
return true;
|
|
338
351
|
}
|
|
339
352
|
return false;
|
|
340
353
|
}
|
|
341
354
|
const SCHEMA_BUDGET = 48_000;
|
|
342
|
-
function schemaSurface(manifest, argv) {
|
|
355
|
+
async function schemaSurface(manifest, argv) {
|
|
356
|
+
const { commandSchemaOf, schemaOf, summaryOf } = await import('./schema.js');
|
|
343
357
|
const { node } = manifest.resolve(beforeTerminator(argv).filter((a) => a !== '--schema' && !a.startsWith('--format=')), manifest.rootPath);
|
|
344
358
|
if (node?.run !== undefined)
|
|
345
359
|
return commandSchemaOf(node, manifest.rootPath);
|
|
@@ -347,9 +361,9 @@ function schemaSurface(manifest, argv) {
|
|
|
347
361
|
const budget = manifest.schemaBudget ?? SCHEMA_BUDGET;
|
|
348
362
|
return JSON.stringify(full).length <= budget ? full : summaryOf(manifest, budget);
|
|
349
363
|
}
|
|
350
|
-
function helpCommand(manifest, argv, root,
|
|
364
|
+
async function helpCommand(manifest, argv, root, io) {
|
|
351
365
|
const { node } = manifest.resolve(argv, root);
|
|
352
|
-
return renderHelp(manifest, node ?? rootNode(manifest, root),
|
|
366
|
+
return renderHelp(manifest, node ?? rootNode(manifest, root), io);
|
|
353
367
|
}
|
|
354
368
|
function changedOf(node, data) {
|
|
355
369
|
const value = isPlainObject(data) ? data['changed'] : undefined;
|
|
@@ -360,6 +374,10 @@ function changedOf(node, data) {
|
|
|
360
374
|
}
|
|
361
375
|
return undefined;
|
|
362
376
|
}
|
|
377
|
+
function exitCodeOf(data) {
|
|
378
|
+
const code = isPlainObject(data) ? data['exitCode'] : undefined;
|
|
379
|
+
return isExitCode(code) ? code : ExitCode.OK;
|
|
380
|
+
}
|
|
363
381
|
function versionOf(manifest, io) {
|
|
364
382
|
const declared = manifest.version ?? (typeof io.pkg?.data['version'] === 'string' ? io.pkg.data['version'] : undefined);
|
|
365
383
|
if (declared === undefined)
|
|
@@ -371,9 +389,9 @@ async function dispatch(manifest, { node, rest, name }, io) {
|
|
|
371
389
|
const flags = canonical(parsed.values, node.options, parsed.tokens);
|
|
372
390
|
const json = flags.json === true;
|
|
373
391
|
if (flags.help === true && json)
|
|
374
|
-
return { json, text: `${machineJson(helpDocumentOf(manifest, node), json ? ['--json'] : [])}\n` };
|
|
392
|
+
return { json, text: `${await machineJson(await helpDocumentOf(manifest, node), json ? ['--json'] : [])}\n` };
|
|
375
393
|
if (flags.help === true)
|
|
376
|
-
return { json, text: renderHelp(manifest, node,
|
|
394
|
+
return { json, text: await renderHelp(manifest, node, io) };
|
|
377
395
|
if (flags.version === true)
|
|
378
396
|
return { json, text: `${versionOf(manifest, io)}\n` };
|
|
379
397
|
const resolved = await resolveValues(manifest, node.options, flags, io);
|
|
@@ -420,7 +438,7 @@ async function emit(io, outcome) {
|
|
|
420
438
|
const meta = { provenance: outcome.provenance ?? {}, ...(outcome.changed === undefined ? {} : { changed: outcome.changed }) };
|
|
421
439
|
const envelope = { ok: true, data: outcome.data, meta };
|
|
422
440
|
io.out.write(outcome.json ? `${JSON.stringify(envelope)}\n` : `${render(outcome.data)}\n`);
|
|
423
|
-
return await leave(io,
|
|
441
|
+
return await leave(io, exitCodeOf(outcome.data));
|
|
424
442
|
}
|
|
425
443
|
async function report(cause, { manifest, io, argv, json, name }) {
|
|
426
444
|
const failure = await describeFailure(cause, argv, resolveCommand(manifest, argv) ?? undefined);
|
|
@@ -465,7 +483,7 @@ export async function execute(manifest, opts = {}) {
|
|
|
465
483
|
return await leave(io, ExitCode.OK);
|
|
466
484
|
const { node, rest } = manifest.resolve(argv, root);
|
|
467
485
|
if (node?.run === undefined) {
|
|
468
|
-
const { text, code } = unresolved({ manifest, root, io }, argv, node);
|
|
486
|
+
const { text, code } = await unresolved({ manifest, root, io }, argv, node);
|
|
469
487
|
(code === ExitCode.OK ? io.out : io.err).write(text);
|
|
470
488
|
return await leave(io, code);
|
|
471
489
|
}
|
package/dist/exit-code.d.ts
CHANGED
|
@@ -10,9 +10,25 @@ export declare const ExitCode: {
|
|
|
10
10
|
readonly CONFIG: 3;
|
|
11
11
|
/** The user or caller cancelled. */
|
|
12
12
|
readonly CANCELLED: 4;
|
|
13
|
+
/**
|
|
14
|
+
* E6 — the far side said no: a credential is missing, expired, or refused.
|
|
15
|
+
*
|
|
16
|
+
* Its own code because it is the most actionable one in the survey. `RUNTIME` means *it
|
|
17
|
+
* failed, read the message*; `AUTH` means *log in and run it again*, and a script or an
|
|
18
|
+
* agent can branch on that without parsing prose. `USAGE` says fix the script, `CONFIG`
|
|
19
|
+
* says fix the runner, and this says fix the credential — three different responses that
|
|
20
|
+
* collapsed into one code before it existed.
|
|
21
|
+
*
|
|
22
|
+
* **5, where `gh` uses 4.** Four is `CANCELLED` here and has been since the contract was
|
|
23
|
+
* written, and moving a published code to match another tool's is a breaking change for
|
|
24
|
+
* every consumer that already branches on it. The survey's other citation, `aws` v2, uses
|
|
25
|
+
* 252/253/254 and agrees with nobody either; what matters is that the code is stable and
|
|
26
|
+
* documented, not that it matches a particular neighbour.
|
|
27
|
+
*/
|
|
28
|
+
readonly AUTH: 5;
|
|
13
29
|
/** SIGINT after the terminal was restored (E5). */
|
|
14
30
|
readonly SIGINT: 130;
|
|
15
31
|
};
|
|
16
32
|
export type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];
|
|
17
|
-
/** True for the
|
|
33
|
+
/** True for the seven codes in the contract and nothing else. */
|
|
18
34
|
export declare function isExitCode(n: unknown): n is ExitCode;
|
package/dist/exit-code.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderHelp } from './help.js';
|
package/dist/help.d.ts
CHANGED
|
@@ -24,6 +24,18 @@ export interface HelpOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
theme?: HelpTheme;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether the engine colours help (O2). `FORCE_COLOR` decides when set — `0` and `false`
|
|
29
|
+
* off, anything else, the empty string included, on — so it overrides a pipe and
|
|
30
|
+
* `NO_COLOR` both, as Node's own `getColorDepth` does. Otherwise colour needs someone to
|
|
31
|
+
* see it: an interactive terminal (the caller's answer, in which a detected agent is not
|
|
32
|
+
* one, N12), no non-empty `NO_COLOR`, and a `TERM` other than `dumb`.
|
|
33
|
+
*
|
|
34
|
+
* It lives here, not in the engine, so the startup path pays for none of it (W4).
|
|
35
|
+
* Not `tty.WriteStream.prototype.hasColors(env)`, which gives the same answers: with
|
|
36
|
+
* both variables set it calls `process.emitWarning`, and this runs under an injected env.
|
|
37
|
+
*/
|
|
38
|
+
export declare function colorFor(env: Record<string, string | undefined>, interactive: boolean): boolean;
|
|
27
39
|
/**
|
|
28
40
|
* Word-wrap one paragraph; lines the author indented are kept verbatim (yargs #2120).
|
|
29
41
|
*
|
package/dist/help.js
CHANGED
|
@@ -3,6 +3,12 @@ import { width as displayWidth, widest } from 'linegauge';
|
|
|
3
3
|
import { flagsOf, kebab } from './names.js';
|
|
4
4
|
const identity = (s) => s;
|
|
5
5
|
const PLAIN = { heading: identity, command: identity, flag: identity, value: identity };
|
|
6
|
+
export function colorFor(env, interactive) {
|
|
7
|
+
const force = env['FORCE_COLOR'];
|
|
8
|
+
if (force !== undefined)
|
|
9
|
+
return force !== '0' && force !== 'false';
|
|
10
|
+
return interactive && !env['NO_COLOR'] && env['TERM'] !== 'dumb';
|
|
11
|
+
}
|
|
6
12
|
const DEFAULTS = {
|
|
7
13
|
heading: (s) => styleText('bold', s, { validateStream: false }),
|
|
8
14
|
command: (s) => styleText('bold', s, { validateStream: false }),
|