burgee 0.11.0 → 0.12.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 +19 -4
- package/dist/cli.d.ts +2 -90
- package/dist/cli.js +4 -151
- package/dist/commander/command.d.ts +33 -1
- package/dist/commander/command.js +49 -11
- package/dist/compat.d.ts +39 -2
- package/dist/compat.js +90 -2
- package/dist/complete-dynamic.d.ts +18 -0
- package/dist/complete-dynamic.js +19 -0
- package/dist/completions.js +33 -15
- package/dist/config-explain.d.ts +22 -0
- package/dist/config-explain.js +33 -0
- package/dist/define-error.d.ts +29 -0
- package/dist/define-error.js +29 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +17 -0
- package/dist/execute.d.ts +6 -0
- package/dist/execute.js +74 -16
- package/dist/facade-failure.d.ts +17 -0
- package/dist/facade-failure.js +12 -0
- package/dist/fields.d.ts +32 -0
- package/dist/fields.js +45 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/manifest.d.ts +42 -5
- package/dist/manifest.js +6 -0
- package/dist/mcp.d.ts +5 -1
- package/dist/mcp.js +77 -11
- package/dist/migrate.d.ts +57 -7
- package/dist/migrate.js +413 -34
- package/dist/parse-hooks.d.ts +7 -0
- package/dist/parse-hooks.js +17 -0
- package/dist/plugin.d.ts +2 -6
- package/dist/plugin.js +1 -1
- package/dist/program-schema.json +1 -0
- package/dist/program.d.ts +90 -0
- package/dist/program.js +151 -0
- package/dist/runtime.d.ts +3 -1
- package/dist/runtime.js +3 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.js +6 -11
- package/dist/schema.json +1 -1
- package/dist/stdin-dash.d.ts +15 -0
- package/dist/stdin-dash.js +12 -0
- package/dist/validate.d.ts +1 -27
- package/dist/validate.js +2 -17
- package/dist/yargs/factory.js +39 -13
- package/dist/yargs-parser.d.ts +8 -1
- package/dist/yargs-parser.js +1 -1
- package/dist/yargs.d.ts +1 -0
- package/dist/yargs.js +1 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ run(defineCommand({
|
|
|
46
46
|
name: 'greet',
|
|
47
47
|
description: 'Greet someone by name',
|
|
48
48
|
options: { name: { type: 'string', required: true, description: 'who to greet' } },
|
|
49
|
+
effects: 'read_only', // what running it does to the world; required, and what --mcp reads
|
|
49
50
|
run: ({ options }) => ({ greeting: `hello, ${options.name}` }),
|
|
50
51
|
}));
|
|
51
52
|
```
|
|
@@ -55,7 +56,7 @@ $ node cli.mjs --name ada
|
|
|
55
56
|
greeting: hello, ada
|
|
56
57
|
|
|
57
58
|
$ node cli.mjs --json --name ada
|
|
58
|
-
{"ok":true,"data":{"greeting":"hello, ada"}}
|
|
59
|
+
{"ok":true,"data":{"greeting":"hello, ada"},"meta":{"provenance":{"name":{"source":"flag","location":"--name"}}}}
|
|
59
60
|
|
|
60
61
|
$ node cli.mjs # exit 2
|
|
61
62
|
error: missing required option --name
|
|
@@ -94,6 +95,18 @@ published and ratcheting:
|
|
|
94
95
|
Your code and your tests are unchanged. A façade is never called "compatible" until its
|
|
95
96
|
host's own suite passes 100%; below that the rate is published instead of claimed.
|
|
96
97
|
|
|
98
|
+
Or let the codemod make that change, and the same one for chalk, ora, string-width,
|
|
99
|
+
cross-spawn, signal-exit and every other incumbent the family replaces at full grade:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx burgee migrate --dry-run
|
|
103
|
+
npx burgee migrate
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It rewrites import specifiers and nothing else, leaves a replacement that is not level yet
|
|
107
|
+
alone with its grade, refuses a file it cannot rewrite whole, and prints the install command
|
|
108
|
+
to run next — [Migrate](https://burgee.interlace.tools/docs/migrate).
|
|
109
|
+
|
|
97
110
|
## What is in the box
|
|
98
111
|
|
|
99
112
|
| Import | Gives you |
|
|
@@ -154,9 +167,11 @@ the declaration read by a different reader.
|
|
|
154
167
|
|
|
155
168
|
### How do I expose a CLI over MCP?
|
|
156
169
|
|
|
157
|
-
Run it with `--mcp`: the same manifest is served as MCP tools over stdio.
|
|
158
|
-
|
|
159
|
-
|
|
170
|
+
Run it with `--mcp`: the same manifest is served as MCP tools over stdio. Every runnable
|
|
171
|
+
command declares its `effects` — `read_only`, `idempotent` or `non_idempotent`, which become
|
|
172
|
+
MCP's hints, or `withheld`, which keeps it out of the tool list — so nothing reaches an
|
|
173
|
+
agent by accident. On `burgee/commander` and `burgee/yargs`, a command that declared
|
|
174
|
+
nothing is still listed, marked `effects: 'undeclared'`. Register it with any stdio client:
|
|
160
175
|
|
|
161
176
|
```json
|
|
162
177
|
{ "mcpServers": { "mytool": { "command": "npx", "args": ["mytool", "--mcp"] } } }
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,90 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
readonly type: "string";
|
|
4
|
-
readonly required: true;
|
|
5
|
-
readonly description: "leading colour, hex. Your primary; it leads the charge upper-left";
|
|
6
|
-
};
|
|
7
|
-
readonly follow: {
|
|
8
|
-
readonly type: "string";
|
|
9
|
-
readonly required: true;
|
|
10
|
-
readonly description: "following colour, hex. Your secondary; it follows lower-right";
|
|
11
|
-
};
|
|
12
|
-
readonly name: {
|
|
13
|
-
readonly type: "string";
|
|
14
|
-
readonly description: "brand name, used as the accessible label and card title";
|
|
15
|
-
};
|
|
16
|
-
readonly ground: {
|
|
17
|
-
readonly type: "string";
|
|
18
|
-
readonly default: "#0a0a0a";
|
|
19
|
-
readonly description: "the field’s dark midpoint, which is what keeps the charge legible";
|
|
20
|
-
};
|
|
21
|
-
readonly charge: {
|
|
22
|
-
readonly type: "string";
|
|
23
|
-
readonly description: "path to an SVG whose contents replace the bars, drawn in a 0 0 100 100 box";
|
|
24
|
-
};
|
|
25
|
-
readonly bordure: {
|
|
26
|
-
readonly type: "string";
|
|
27
|
-
readonly description: "outline colour, hex. Omit for no outline";
|
|
28
|
-
};
|
|
29
|
-
readonly bordureWidth: {
|
|
30
|
-
readonly type: "string";
|
|
31
|
-
readonly default: "1.5";
|
|
32
|
-
readonly description: "outline width";
|
|
33
|
-
};
|
|
34
|
-
readonly tagline: {
|
|
35
|
-
readonly type: "string";
|
|
36
|
-
readonly description: "one line under the name on the card and cover";
|
|
37
|
-
};
|
|
38
|
-
readonly out: {
|
|
39
|
-
readonly type: "string";
|
|
40
|
-
readonly description: "directory to write into. Omit to print the flag only";
|
|
41
|
-
};
|
|
42
|
-
readonly on: {
|
|
43
|
-
readonly type: "string";
|
|
44
|
-
readonly description: "page colour(s) the flag will fly on, comma separated. Checked for contrast";
|
|
45
|
-
};
|
|
46
|
-
readonly allowLowContrast: {
|
|
47
|
-
readonly type: "boolean";
|
|
48
|
-
readonly description: "emit anyway when a contrast check fails. Says so in the output";
|
|
49
|
-
};
|
|
50
|
-
}>;
|
|
51
|
-
/**
|
|
52
|
-
* `burgee dev <entry>` — watch the entry, reload it on change, serve it as MCP on stdio
|
|
53
|
-
* and print every surface on each save (dev-loop). Loaded only when asked for, so the
|
|
54
|
-
* CLI's own weight and the framework's stay what they were (W4).
|
|
55
|
-
*/
|
|
56
|
-
export declare const devCommand: import("./execute.js").Command<{
|
|
57
|
-
readonly noWatch: {
|
|
58
|
-
readonly type: "boolean";
|
|
59
|
-
readonly description: "load once and serve; do not watch for changes";
|
|
60
|
-
};
|
|
61
|
-
}>;
|
|
62
|
-
/**
|
|
63
|
-
* `burgee migrate [dir]` — rewrite a commander or yargs project's imports to burgee's
|
|
64
|
-
* drop-in front-ends and report what changed, with the numbers that say why it was safe.
|
|
65
|
-
*
|
|
66
|
-
* The engine is loaded on this path only (K6), the same way `dev` is: a dynamic import, so
|
|
67
|
-
* `burgee`'s own start-up and the framework's weight are what they were. `weight.test.ts`
|
|
68
|
-
* denies `migrate.js` to the root entry by name, so that cannot drift back.
|
|
69
|
-
*
|
|
70
|
-
* `idempotent` is the honest answer rather than the conservative one, and it is earned:
|
|
71
|
-
* `burgee/commander` is not a key in the mapping, so a second run over a migrated tree
|
|
72
|
-
* rewrites nothing. N6 then requires the result to report `changed`, which it does.
|
|
73
|
-
*/
|
|
74
|
-
export declare const migrateCommand: import("./execute.js").Command<{
|
|
75
|
-
readonly dryRun: {
|
|
76
|
-
readonly type: "boolean";
|
|
77
|
-
readonly description: "scan and report; write nothing";
|
|
78
|
-
};
|
|
79
|
-
readonly force: {
|
|
80
|
-
readonly type: "boolean";
|
|
81
|
-
readonly description: "migrate even though the git tree has uncommitted changes";
|
|
82
|
-
};
|
|
83
|
-
}>;
|
|
84
|
-
/**
|
|
85
|
-
* `burgee check <plugin-file>` — the feedback loop PRINCIPLES 7 asks every extension surface
|
|
86
|
-
* for, and the one burgee did not have. See `check.ts`: this one returns its report as data, so
|
|
87
|
-
* `--json` is the form an agent that just wrote a plugin reads.
|
|
88
|
-
*/
|
|
89
|
-
export declare const pluginCheckCommand: import("./execute.js").Command<import("./execute.js").OptionSpecs>;
|
|
90
|
-
export declare const program: import("./manifest.js").Manifest;
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export * from './program.js';
|
package/dist/cli.js
CHANGED
|
@@ -1,152 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { auditBurgee, report } from './contrast.js';
|
|
5
|
-
import { defineCommand, defineProgram, run } from './execute.js';
|
|
6
|
-
const MASTER = 512;
|
|
7
|
-
const CONTRAST_HINT = 'hint: darken the --ground stop under the charge, or pass --allow-low-contrast';
|
|
8
|
-
const DEFAULT_BORDURE_WIDTH = '1.5';
|
|
9
|
-
function surfaces(brand, tagline) {
|
|
10
|
-
const burgee = defineBurgee(brand);
|
|
11
|
-
const subtitle = tagline === '' ? {} : { subtitle: tagline };
|
|
12
|
-
return [
|
|
13
|
-
{ file: 'flag.svg', svg: burgee.flag(MASTER) },
|
|
14
|
-
{ file: 'icon.svg', svg: burgee.favicon() },
|
|
15
|
-
{ file: 'og.svg', svg: burgee.og(subtitle) },
|
|
16
|
-
{ file: 'cover.svg', svg: burgee.cover(subtitle) },
|
|
17
|
-
{ file: 'lockup.svg', svg: burgee.lockup({ theme: 'dark' }) },
|
|
18
|
-
{ file: 'lockup-light.svg', svg: burgee.lockup({ theme: 'light' }) },
|
|
19
|
-
];
|
|
20
|
-
}
|
|
21
|
-
export const brandCommand = defineCommand({
|
|
22
|
-
name: 'brand',
|
|
23
|
-
description: 'Generate a burgee — flag, favicon, social card, cover and lockup — from two colours',
|
|
24
|
-
effects: 'non_idempotent',
|
|
25
|
-
options: {
|
|
26
|
-
lead: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
required: true,
|
|
29
|
-
description: 'leading colour, hex. Your primary; it leads the charge upper-left',
|
|
30
|
-
},
|
|
31
|
-
follow: {
|
|
32
|
-
type: 'string',
|
|
33
|
-
required: true,
|
|
34
|
-
description: 'following colour, hex. Your secondary; it follows lower-right',
|
|
35
|
-
},
|
|
36
|
-
name: { type: 'string', description: 'brand name, used as the accessible label and card title' },
|
|
37
|
-
ground: {
|
|
38
|
-
type: 'string',
|
|
39
|
-
default: DEFAULT_GROUND,
|
|
40
|
-
description: 'the field’s dark midpoint, which is what keeps the charge legible',
|
|
41
|
-
},
|
|
42
|
-
charge: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
description: 'path to an SVG whose contents replace the bars, drawn in a 0 0 100 100 box',
|
|
45
|
-
},
|
|
46
|
-
bordure: { type: 'string', description: 'outline colour, hex. Omit for no outline' },
|
|
47
|
-
bordureWidth: { type: 'string', default: DEFAULT_BORDURE_WIDTH, description: 'outline width' },
|
|
48
|
-
tagline: { type: 'string', description: 'one line under the name on the card and cover' },
|
|
49
|
-
out: { type: 'string', description: 'directory to write into. Omit to print the flag only' },
|
|
50
|
-
'on': {
|
|
51
|
-
type: 'string',
|
|
52
|
-
description: 'page colour(s) the flag will fly on, comma separated. Checked for contrast',
|
|
53
|
-
},
|
|
54
|
-
allowLowContrast: {
|
|
55
|
-
type: 'boolean',
|
|
56
|
-
description: 'emit anyway when a contrast check fails. Says so in the output',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
run: ({ options }) => {
|
|
60
|
-
const lead = options.lead ?? '';
|
|
61
|
-
const follow = options.follow ?? '';
|
|
62
|
-
const colors = { lead, follow };
|
|
63
|
-
const charge = options.charge === undefined
|
|
64
|
-
? {}
|
|
65
|
-
: { charge: readFileSync(options.charge, 'utf8').replace(/<\/?svg[^>]*>/g, '').trim() };
|
|
66
|
-
const bordure = options.bordure === undefined
|
|
67
|
-
? {}
|
|
68
|
-
: {
|
|
69
|
-
bordure: {
|
|
70
|
-
color: options.bordure,
|
|
71
|
-
width: Number(options.bordureWidth ?? DEFAULT_BORDURE_WIDTH),
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
const brand = {
|
|
75
|
-
...(options.name === undefined ? {} : { name: options.name }),
|
|
76
|
-
mark: colors,
|
|
77
|
-
field: opposedField(colors, options.ground ?? DEFAULT_GROUND),
|
|
78
|
-
...charge,
|
|
79
|
-
...bordure,
|
|
80
|
-
};
|
|
81
|
-
const grounds = (options.on ?? '')
|
|
82
|
-
.split(',')
|
|
83
|
-
.map((g) => g.trim())
|
|
84
|
-
.filter((g) => g !== '');
|
|
85
|
-
const findings = auditBurgee(brand, grounds);
|
|
86
|
-
const failed = findings.filter((f) => !f.passes);
|
|
87
|
-
if (failed.length > 0 && options.allowLowContrast !== true) {
|
|
88
|
-
throw new Error(`contrast below WCAG AA:\n${report(failed)}\n${CONTRAST_HINT}`);
|
|
89
|
-
}
|
|
90
|
-
const written = surfaces(brand, options.tagline ?? '');
|
|
91
|
-
const contrast = findings.map((f) => ({ what: f.what, ratio: f.ratio, passes: f.passes }));
|
|
92
|
-
if (options.out === undefined) {
|
|
93
|
-
return { flag: defineBurgee(brand).flag(MASTER), files: [], contrast };
|
|
94
|
-
}
|
|
95
|
-
mkdirSync(options.out, { recursive: true });
|
|
96
|
-
for (const s of written)
|
|
97
|
-
writeFileSync(join(options.out, s.file), `${s.svg}\n`);
|
|
98
|
-
return { out: options.out, files: written.map((s) => s.file), contrast };
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
export const devCommand = defineCommand({
|
|
102
|
-
name: 'dev',
|
|
103
|
-
description: 'Watch a CLI entry, reload it on change, and serve it as MCP on stdio while you write it',
|
|
104
|
-
arguments: [{ name: 'entry', description: 'the module that exports the program, as program or as its default export', required: true }],
|
|
105
|
-
options: {
|
|
106
|
-
noWatch: { type: 'boolean', description: 'load once and serve; do not watch for changes' },
|
|
107
|
-
},
|
|
108
|
-
effects: 'withheld',
|
|
109
|
-
run: async ({ positionals, options }) => {
|
|
110
|
-
const [entry] = positionals;
|
|
111
|
-
if (entry === undefined)
|
|
112
|
-
throw new Error('an entry file is required');
|
|
113
|
-
const [{ dev }, { processRuntime }] = await Promise.all([import('./dev.js'), import('./runtime.js')]);
|
|
114
|
-
const handle = dev({ entry, input: processRuntime.stdin, output: processRuntime.stdout, log: processRuntime.stderr, watch: options.noWatch !== true });
|
|
115
|
-
await handle.done;
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
export const migrateCommand = defineCommand({
|
|
119
|
-
name: 'migrate',
|
|
120
|
-
description: 'Rewrite commander and yargs imports to burgee’s drop-in front-ends, and report what changed',
|
|
121
|
-
arguments: [{ name: 'dir', description: 'the project to migrate. Defaults to the current directory', required: false }],
|
|
122
|
-
options: {
|
|
123
|
-
dryRun: { type: 'boolean', description: 'scan and report; write nothing' },
|
|
124
|
-
force: { type: 'boolean', description: 'migrate even though the git tree has uncommitted changes' },
|
|
125
|
-
},
|
|
126
|
-
effects: 'idempotent',
|
|
127
|
-
examples: [
|
|
128
|
-
{ command: 'burgee migrate --dry-run', description: 'what it would change, without changing it' },
|
|
129
|
-
{ command: 'burgee migrate --json', description: 'the same report as data; exit 1 when anything was refused' },
|
|
130
|
-
],
|
|
131
|
-
run: async ({ positionals, options }) => {
|
|
132
|
-
const [{ migrate }, { host }] = await Promise.all([import('./migrate.js'), import('./runtime.js')]);
|
|
133
|
-
return await migrate({ dir: positionals[0] ?? host.cwd(), dryRun: options.dryRun === true, force: options.force === true });
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
export const pluginCheckCommand = defineCommand({
|
|
137
|
-
name: 'check',
|
|
138
|
-
description: 'Validate a burgee plugin, register it into a throwaway program, and report what it contributes',
|
|
139
|
-
arguments: [{ name: 'file', description: 'the plugin module to check' }],
|
|
140
|
-
effects: 'read_only',
|
|
141
|
-
examples: [
|
|
142
|
-
{ command: 'burgee check ./my-plugin.mjs', description: 'what the plugin contributes, or why it was refused' },
|
|
143
|
-
{ command: 'burgee check ./my-plugin.mjs --json', description: 'the same, as data; exit 1 on a refusal' },
|
|
144
|
-
],
|
|
145
|
-
run: async ({ positionals }) => (await import('./check.js')).checkPlugin(positionals[0] ?? ''),
|
|
146
|
-
});
|
|
147
|
-
export const program = defineProgram({
|
|
148
|
-
name: 'burgee',
|
|
149
|
-
description: 'The agent-native CLI framework, and the tools that come with it',
|
|
150
|
-
commands: [brandCommand, devCommand, migrateCommand, pluginCheckCommand],
|
|
151
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { run } from './execute.js';
|
|
3
|
+
import { program } from './program.js';
|
|
152
4
|
run(program);
|
|
5
|
+
export * from './program.js';
|
|
@@ -14,6 +14,7 @@ import { EventEmitter } from 'node:events';
|
|
|
14
14
|
* then reports through the E1 taxonomy — the harness's seam (T1).
|
|
15
15
|
*/
|
|
16
16
|
import * as crossSpawn from 'bellpull/cross-spawn';
|
|
17
|
+
import { ExitCode } from '../exit-code.js';
|
|
17
18
|
import { type DeclaredEffects, Manifest, type OptionSpec, type Plugin } from '../manifest.js';
|
|
18
19
|
import { Argument, type ParseArg } from './argument.js';
|
|
19
20
|
import { CommanderError } from './error.js';
|
|
@@ -261,6 +262,14 @@ export declare class Command extends EventEmitter {
|
|
|
261
262
|
error(message: string, errorOptions?: ErrorOptions): never;
|
|
262
263
|
/** One failure as the envelope (E3, G5); `fix` only when one candidate was named. */
|
|
263
264
|
_reportJson(code: string, message: string): void;
|
|
265
|
+
/** The failure envelope on stdout, once per run however many paths see the same failure (G5). */
|
|
266
|
+
_writeFailure(error: Record<string, unknown>): void;
|
|
267
|
+
/**
|
|
268
|
+
* A handler's failure — a throw, a rejection, a thrown non-Error — reported on the surface the
|
|
269
|
+
* run asked for, with the E1 code its error names: `AuthError` is AUTH, `UsageError` USAGE,
|
|
270
|
+
* anything else RUNTIME (E6, D-140). Returns that code.
|
|
271
|
+
*/
|
|
272
|
+
_reportHandlerFailure(err: unknown): ExitCode;
|
|
264
273
|
/** Apply environment variables to options that have no value from the cli or client code. */
|
|
265
274
|
_parseOptionsEnv(): void;
|
|
266
275
|
/** Apply implied option values where the option is undefined or at its default. */
|
|
@@ -329,7 +338,17 @@ export declare class Command extends EventEmitter {
|
|
|
329
338
|
*/
|
|
330
339
|
get manifest(): Manifest;
|
|
331
340
|
_project(manifest: Manifest): void;
|
|
332
|
-
/**
|
|
341
|
+
/**
|
|
342
|
+
* Options as the manifest describes them, on a null-prototype record — keyed so that the
|
|
343
|
+
* spelling every surface derives from a key, `--${kebab(key)}`, is a flag commander accepts.
|
|
344
|
+
*
|
|
345
|
+
* Commander negates only what it was told to, where burgee's own parser negates every
|
|
346
|
+
* boolean. So a boolean is `negatable` here only when the program declared its `--no-` twin,
|
|
347
|
+
* which folds onto it rather than appearing twice; and a `--no-x` declared alone, which has
|
|
348
|
+
* no `--x`, is described as the switch it is, under `noX`. Before this, `--no-color` was
|
|
349
|
+
* published as `color` with `flag: '--color'`: completions offered `--color` and
|
|
350
|
+
* `--no-skip-blank`, and `--mcp` sent `--color` — each refused by the parser behind them.
|
|
351
|
+
*/
|
|
333
352
|
_optionSpecs(): Record<string, OptionSpec>;
|
|
334
353
|
/** burgee: declare what the command does to the world (N6). This is what exposes it as an MCP tool (N2). */
|
|
335
354
|
effects(value: DeclaredEffects): this;
|
|
@@ -352,6 +371,19 @@ export declare class Command extends EventEmitter {
|
|
|
352
371
|
_prepareBurgee(parseOptions?: BurgeeParseOptions): ParseOptions | undefined;
|
|
353
372
|
/** In burgee mode the whole run settles to one E1 exit; otherwise commander's behaviour, untouched. */
|
|
354
373
|
_runBurgee(run: () => unknown): unknown;
|
|
374
|
+
/**
|
|
375
|
+
* Nothing injected — `program.parseAsync(process.argv)`, the way every commander program is
|
|
376
|
+
* run. Commander's own contract, with one exception: a handler that fails under `--json`
|
|
377
|
+
* settles to the envelope on stdout and the E1 code its error names, set as the process's
|
|
378
|
+
* exit code, instead of escaping `parse`/`parseAsync` for Node to print as a stack (D-140).
|
|
379
|
+
*
|
|
380
|
+
* The seam check in `_runBurgee` cannot see this case, and that was the defect: it runs
|
|
381
|
+
* before argv is parsed, and `--json` is only recognised *during* the parse, so the run was
|
|
382
|
+
* already committed to "no burgee" when the handler threw. A `CommanderError` is left alone —
|
|
383
|
+
* `error()` has reported it already, and an `exitOverride` caller is owed the throw — and so
|
|
384
|
+
* is every failure without `--json`, which is commander's to surface as it always has.
|
|
385
|
+
*/
|
|
386
|
+
_runCommanderWay(run: () => unknown): unknown;
|
|
355
387
|
/** Any command from here down declares this flag, so burgee does not serve it: additive only. */
|
|
356
388
|
_declares(flag: string): boolean;
|
|
357
389
|
/** The action, wrapped in the plugin hooks and followed by the envelope or the rendering. */
|
|
@@ -4,7 +4,9 @@ import path from 'node:path';
|
|
|
4
4
|
import { stripVTControlCharacters } from 'node:util';
|
|
5
5
|
import * as crossSpawn from 'bellpull/cross-spawn';
|
|
6
6
|
import { ExitCode } from '../exit-code.js';
|
|
7
|
+
import { handlerFailure } from '../facade-failure.js';
|
|
7
8
|
import { Manifest } from '../manifest.js';
|
|
9
|
+
import { camel } from '../names.js';
|
|
8
10
|
import { host } from '../runtime.js';
|
|
9
11
|
import { machineJson, schemaOf } from '../schema.js';
|
|
10
12
|
import { suggestSimilar } from '../suggest.js';
|
|
@@ -1034,13 +1036,24 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
|
|
|
1034
1036
|
this._exit(exitCode, code, message);
|
|
1035
1037
|
}
|
|
1036
1038
|
_reportJson(code, message) {
|
|
1039
|
+
const [first = '', ...rest] = message.replace(/^error: /, '').split('\n');
|
|
1040
|
+
const guess = /\(Did you mean (\S+)\?\)/.exec(rest.join(''));
|
|
1041
|
+
this._writeFailure({ code, message: first, ...(guess === null ? {} : { fix: guess[1] }) });
|
|
1042
|
+
}
|
|
1043
|
+
_writeFailure(error) {
|
|
1037
1044
|
const burgee = this._root()._burgee;
|
|
1038
1045
|
if (burgee === undefined || !burgee.json || burgee.reported)
|
|
1039
1046
|
return;
|
|
1040
1047
|
burgee.reported = true;
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1048
|
+
this._outputConfiguration.writeOut(`${JSON.stringify({ ok: false, error })}\n`);
|
|
1049
|
+
}
|
|
1050
|
+
_reportHandlerFailure(err) {
|
|
1051
|
+
const { exit, error } = handlerFailure(err);
|
|
1052
|
+
if (this._burgee?.json === true)
|
|
1053
|
+
this._writeFailure(error);
|
|
1054
|
+
else
|
|
1055
|
+
this._outputConfiguration.writeErr(`error: ${error.message}\n`);
|
|
1056
|
+
return exit;
|
|
1044
1057
|
}
|
|
1045
1058
|
_parseOptionsEnv() {
|
|
1046
1059
|
for (const option of this.options) {
|
|
@@ -1413,6 +1426,10 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1413
1426
|
_optionSpecs() {
|
|
1414
1427
|
const specs = Object.create(null);
|
|
1415
1428
|
for (const option of this.options) {
|
|
1429
|
+
const name = option.attributeName();
|
|
1430
|
+
const paired = this.options.some((o) => o.negate !== option.negate && o.attributeName() === name);
|
|
1431
|
+
if (option.negate && paired)
|
|
1432
|
+
continue;
|
|
1416
1433
|
const spec = { type: option.required || option.optional ? 'string' : 'boolean' };
|
|
1417
1434
|
if (option.description)
|
|
1418
1435
|
spec.description = option.description;
|
|
@@ -1424,7 +1441,9 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1424
1441
|
spec.default = option.defaultValue;
|
|
1425
1442
|
if (option.envVar)
|
|
1426
1443
|
spec.env = option.envVar;
|
|
1427
|
-
|
|
1444
|
+
if (spec.type === 'boolean')
|
|
1445
|
+
spec.negatable = paired;
|
|
1446
|
+
Object.defineProperty(specs, option.negate ? camel(option.name()) : name, { value: spec, enumerable: true, writable: true, configurable: true });
|
|
1428
1447
|
}
|
|
1429
1448
|
return specs;
|
|
1430
1449
|
}
|
|
@@ -1510,7 +1529,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1510
1529
|
const root = this._root();
|
|
1511
1530
|
const burgee = root._burgee;
|
|
1512
1531
|
if (burgee === undefined)
|
|
1513
|
-
return run
|
|
1532
|
+
return root._runCommanderWay(run);
|
|
1514
1533
|
const finish = (code) => {
|
|
1515
1534
|
root._burgee = undefined;
|
|
1516
1535
|
burgee.exit?.(code);
|
|
@@ -1522,12 +1541,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1522
1541
|
finish(e1(err));
|
|
1523
1542
|
return;
|
|
1524
1543
|
}
|
|
1525
|
-
|
|
1526
|
-
if (burgee.json)
|
|
1527
|
-
root._reportJson('runtime', message);
|
|
1528
|
-
else
|
|
1529
|
-
root._outputConfiguration.writeErr(`error: ${message}\n`);
|
|
1530
|
-
finish(ExitCode.RUNTIME);
|
|
1544
|
+
finish(root._reportHandlerFailure(err));
|
|
1531
1545
|
};
|
|
1532
1546
|
try {
|
|
1533
1547
|
const result = run();
|
|
@@ -1541,6 +1555,30 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1541
1555
|
return undefined;
|
|
1542
1556
|
}
|
|
1543
1557
|
}
|
|
1558
|
+
_runCommanderWay(run) {
|
|
1559
|
+
const done = () => {
|
|
1560
|
+
this._burgee = undefined;
|
|
1561
|
+
};
|
|
1562
|
+
const fail = (err) => {
|
|
1563
|
+
const json = this._burgee?.json === true && !(err instanceof CommanderError);
|
|
1564
|
+
if (json)
|
|
1565
|
+
host.exitCode = this._reportHandlerFailure(err);
|
|
1566
|
+
done();
|
|
1567
|
+
if (!json)
|
|
1568
|
+
throw err;
|
|
1569
|
+
};
|
|
1570
|
+
try {
|
|
1571
|
+
const result = run();
|
|
1572
|
+
if (isThenable(result))
|
|
1573
|
+
return Promise.resolve(result).then(done, fail);
|
|
1574
|
+
done();
|
|
1575
|
+
return undefined;
|
|
1576
|
+
}
|
|
1577
|
+
catch (err) {
|
|
1578
|
+
fail(err);
|
|
1579
|
+
return undefined;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1544
1582
|
_declares(flag) {
|
|
1545
1583
|
return this._findOption(flag) !== undefined || this.commands.some((sub) => sub._declares(flag));
|
|
1546
1584
|
}
|
package/dist/compat.d.ts
CHANGED
|
@@ -26,5 +26,42 @@ export interface Graded {
|
|
|
26
26
|
/** `passed / reference`, carried rather than recomputed so it is the oracle's own division. */
|
|
27
27
|
rate: number;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
export
|
|
29
|
+
/** One host's grade, with the control beside it: the incumbent's own suite run against the incumbent in the same harness. */
|
|
30
|
+
export interface Row extends Graded {
|
|
31
|
+
/**
|
|
32
|
+
* Cases the incumbent itself passes here — from the published compatibility page, which
|
|
33
|
+
* `npm run compat:page` generates on ubuntu. A drop-in that passes as many as the control
|
|
34
|
+
* is **level**: every case it misses, the incumbent misses too, in this harness.
|
|
35
|
+
*/
|
|
36
|
+
control: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Every host the oracle grades, keyed by its `hosts.ts` name. The lock holds `reference`,
|
|
40
|
+
* `passed` and `rate` equal to `baseline/<host>.json` and `control` equal to the page's
|
|
41
|
+
* control column.
|
|
42
|
+
*/
|
|
43
|
+
export declare const GRADED: Readonly<Record<string, Row>>;
|
|
44
|
+
/** One graded path: an incumbent's specifier, and the family specifier that replaces it. */
|
|
45
|
+
export interface DropIn {
|
|
46
|
+
/** The `hosts.ts` name, which keys `GRADED`. */
|
|
47
|
+
host: string;
|
|
48
|
+
from: string;
|
|
49
|
+
to: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Every drop-in pair the oracle grades, derived from `hosts.ts`: `from` is what the
|
|
53
|
+
* incumbent's own tests import (`<host><subpath>`, or the import's `control` where the
|
|
54
|
+
* incumbent is a separate package, as `yargs-parser` is), `to` is `<target><subpath>`.
|
|
55
|
+
* `scripts/migrate-drop-ins-lock.test.ts` re-derives this list and fails on any difference.
|
|
56
|
+
*/
|
|
57
|
+
export declare const DROP_INS: readonly DropIn[];
|
|
58
|
+
/**
|
|
59
|
+
* The version each incumbent package was graded at: `vendor/<host>/.source.json`, or for
|
|
60
|
+
* `yargs-parser` the copy the yargs control resolves. The grade is for that major and no other
|
|
61
|
+
* — `signal-exit` 3 exports a function where 4 exports `onExit`, and `chalk` 4 is CommonJS where
|
|
62
|
+
* 6 is not — so `migrate` leaves a project on another major alone and says so.
|
|
63
|
+
* `scripts/migrate-drop-ins-lock.test.ts` holds each equal to the oracle's.
|
|
64
|
+
*/
|
|
65
|
+
export declare const GRADED_VERSIONS: Readonly<Record<string, string>>;
|
|
66
|
+
/** Level: the drop-in passes every case the incumbent passes against its own suite (D-137). */
|
|
67
|
+
export declare const isLevel: (host: string) => boolean;
|
package/dist/compat.js
CHANGED
|
@@ -1,4 +1,92 @@
|
|
|
1
1
|
export const GRADED = {
|
|
2
|
-
commander: { reference: 1360, passed: 1360, rate: 1 },
|
|
3
|
-
yargs: { reference: 804, passed: 804, rate: 1 },
|
|
2
|
+
commander: { reference: 1360, passed: 1360, rate: 1, control: 1360 },
|
|
3
|
+
yargs: { reference: 804, passed: 804, rate: 1, control: 802 },
|
|
4
|
+
chalk: { reference: 58, passed: 58, rate: 1, control: 58 },
|
|
5
|
+
ora: { reference: 99, passed: 99, rate: 1, control: 99 },
|
|
6
|
+
'log-update': { reference: 99, passed: 99, rate: 1, control: 99 },
|
|
7
|
+
boxen: { reference: 84, passed: 84, rate: 1, control: 84 },
|
|
8
|
+
'cli-table3': { reference: 29, passed: 29, rate: 1, control: 29 },
|
|
9
|
+
'string-width': { reference: 229, passed: 229, rate: 1, control: 229 },
|
|
10
|
+
'strip-ansi': { reference: 8, passed: 8, rate: 1, control: 8 },
|
|
11
|
+
'cross-spawn': { reference: 68, passed: 68, rate: 1, control: 68 },
|
|
12
|
+
which: { reference: 5, passed: 5, rate: 1, control: 5 },
|
|
13
|
+
rc: { reference: 1, passed: 1, rate: 1, control: 1 },
|
|
14
|
+
'wrap-ansi': { reference: 80, passed: 80, rate: 1, control: 80 },
|
|
15
|
+
'slice-ansi': { reference: 15, passed: 15, rate: 1, control: 15 },
|
|
16
|
+
cosmiconfig: { reference: 243, passed: 186, rate: 0.7654320987654321, control: 240 },
|
|
17
|
+
lilconfig: { reference: 77, passed: 77, rate: 1, control: 77 },
|
|
18
|
+
dotenv: { reference: 141, passed: 106, rate: 0.75177304964539, control: 141 },
|
|
19
|
+
clack: { reference: 17, passed: 14, rate: 0.8235294117647058, control: 17 },
|
|
20
|
+
'inquirer-core': { reference: 41, passed: 41, rate: 1, control: 41 },
|
|
21
|
+
meow: { reference: 148, passed: 132, rate: 0.8918918918918919, control: 146 },
|
|
22
|
+
'ansi-escapes': { reference: 4, passed: 4, rate: 1, control: 4 },
|
|
23
|
+
'terminal-link': { reference: 8, passed: 8, rate: 1, control: 8 },
|
|
24
|
+
'term-img': { reference: 18, passed: 12, rate: 0.6666666666666666, control: 18 },
|
|
25
|
+
'restore-cursor': { reference: 6, passed: 6, rate: 1, control: 6 },
|
|
26
|
+
'exit-hook': { reference: 21, passed: 21, rate: 1, control: 21 },
|
|
27
|
+
'signal-exit': { reference: 135, passed: 134, rate: 0.9925925925925926, control: 134 },
|
|
28
|
+
};
|
|
29
|
+
export const DROP_INS = [
|
|
30
|
+
{ host: 'commander', from: 'commander', to: 'burgee/commander' },
|
|
31
|
+
{ host: 'yargs', from: 'yargs', to: 'burgee/yargs' },
|
|
32
|
+
{ host: 'yargs', from: 'yargs/helpers', to: 'burgee/yargs/helpers' },
|
|
33
|
+
{ host: 'yargs', from: 'yargs-parser', to: 'burgee/yargs/parser' },
|
|
34
|
+
{ host: 'chalk', from: 'chalk', to: 'roundel/chalk' },
|
|
35
|
+
{ host: 'ora', from: 'ora', to: 'flagstaff/ora' },
|
|
36
|
+
{ host: 'log-update', from: 'log-update', to: 'flagstaff/log-update' },
|
|
37
|
+
{ host: 'boxen', from: 'boxen', to: 'flagstaff/boxen' },
|
|
38
|
+
{ host: 'cli-table3', from: 'cli-table3', to: 'flagstaff/cli-table3' },
|
|
39
|
+
{ host: 'string-width', from: 'string-width', to: 'linegauge' },
|
|
40
|
+
{ host: 'strip-ansi', from: 'strip-ansi', to: 'linegauge/strip' },
|
|
41
|
+
{ host: 'cross-spawn', from: 'cross-spawn', to: 'bellpull/cross-spawn' },
|
|
42
|
+
{ host: 'which', from: 'which', to: 'bellpull/node-which' },
|
|
43
|
+
{ host: 'rc', from: 'rc', to: 'seniority/rc' },
|
|
44
|
+
{ host: 'wrap-ansi', from: 'wrap-ansi', to: 'linegauge/wrap' },
|
|
45
|
+
{ host: 'slice-ansi', from: 'slice-ansi', to: 'linegauge/slice' },
|
|
46
|
+
{ host: 'cosmiconfig', from: 'cosmiconfig', to: 'seniority' },
|
|
47
|
+
{ host: 'lilconfig', from: 'lilconfig', to: 'seniority/lilconfig' },
|
|
48
|
+
{ host: 'dotenv', from: 'dotenv', to: 'seniority/dotenv' },
|
|
49
|
+
{ host: 'clack', from: '@clack/prompts', to: 'caique/clack' },
|
|
50
|
+
{ host: 'inquirer-core', from: '@inquirer/core', to: 'caique/inquirer' },
|
|
51
|
+
{ host: 'meow', from: 'meow', to: 'burgee/meow' },
|
|
52
|
+
{ host: 'ansi-escapes', from: 'ansi-escapes', to: 'paratext' },
|
|
53
|
+
{ host: 'terminal-link', from: 'terminal-link', to: 'paratext/terminal-link' },
|
|
54
|
+
{ host: 'term-img', from: 'term-img', to: 'paratext/term-img' },
|
|
55
|
+
{ host: 'restore-cursor', from: 'restore-cursor', to: 'closeout/restore-cursor' },
|
|
56
|
+
{ host: 'exit-hook', from: 'exit-hook', to: 'closeout/exit-hook' },
|
|
57
|
+
{ host: 'signal-exit', from: 'signal-exit', to: 'closeout/signal-exit' },
|
|
58
|
+
{ host: 'signal-exit', from: 'signal-exit/signals', to: 'closeout/signal-exit/signals' },
|
|
59
|
+
];
|
|
60
|
+
export const GRADED_VERSIONS = {
|
|
61
|
+
'@clack/prompts': '1.8.1',
|
|
62
|
+
'@inquirer/core': '12.0.3',
|
|
63
|
+
'ansi-escapes': '7.3.0',
|
|
64
|
+
boxen: '8.0.1',
|
|
65
|
+
chalk: '6.0.0',
|
|
66
|
+
'cli-table3': '0.6.5',
|
|
67
|
+
commander: '15.0.0',
|
|
68
|
+
cosmiconfig: '10.0.1',
|
|
69
|
+
'cross-spawn': '7.0.6',
|
|
70
|
+
dotenv: '17.4.2',
|
|
71
|
+
'exit-hook': '5.1.0',
|
|
72
|
+
lilconfig: '3.1.3',
|
|
73
|
+
'log-update': '8.0.0',
|
|
74
|
+
meow: '14.1.0',
|
|
75
|
+
ora: '9.4.1',
|
|
76
|
+
rc: '1.2.8',
|
|
77
|
+
'restore-cursor': '5.1.0',
|
|
78
|
+
'signal-exit': '4.1.0',
|
|
79
|
+
'slice-ansi': '7.1.2',
|
|
80
|
+
'string-width': '8.2.2',
|
|
81
|
+
'strip-ansi': '7.2.0',
|
|
82
|
+
'term-img': '7.1.0',
|
|
83
|
+
'terminal-link': '5.0.0',
|
|
84
|
+
which: '7.0.0',
|
|
85
|
+
'wrap-ansi': '10.0.1',
|
|
86
|
+
yargs: '18.1.0',
|
|
87
|
+
'yargs-parser': '22.0.0',
|
|
88
|
+
};
|
|
89
|
+
export const isLevel = (host) => {
|
|
90
|
+
const row = GRADED[host];
|
|
91
|
+
return row !== undefined && row.passed >= row.control;
|
|
4
92
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* D3 / D-119 — the program's side of a dynamic completion. A generated script calls
|
|
8
|
+
* `<program> __complete <command…> --<option> <partial>` for an option that declared
|
|
9
|
+
* `complete`, and prints what comes back, one candidate per line.
|
|
10
|
+
*
|
|
11
|
+
* It never fails loudly. A TAB that prints an error into someone's prompt is worse than one
|
|
12
|
+
* that offers nothing, so an unknown command, an option without a completer, or a completer
|
|
13
|
+
* that throws all print nothing and leave 0.
|
|
14
|
+
*
|
|
15
|
+
* Imported by `execute.ts` only when argv starts with `__complete` (M2).
|
|
16
|
+
*/
|
|
17
|
+
import { type Manifest } from './manifest.js';
|
|
18
|
+
export declare function completeDynamic(manifest: Manifest, argv: readonly string[], write: (s: string) => unknown): Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { kebab } from './names.js';
|
|
2
|
+
export async function completeDynamic(manifest, argv, write) {
|
|
3
|
+
const flag = argv.findIndex((a) => a.startsWith('--'));
|
|
4
|
+
if (flag === -1)
|
|
5
|
+
return;
|
|
6
|
+
const { node } = manifest.resolve(argv.slice(0, flag), manifest.rootPath);
|
|
7
|
+
const name = argv[flag]?.slice(2);
|
|
8
|
+
const spec = Object.entries(node?.options ?? {}).find(([key]) => kebab(key) === name)?.[1];
|
|
9
|
+
if (spec?.complete === undefined)
|
|
10
|
+
return;
|
|
11
|
+
const partial = argv[flag + 1] ?? '';
|
|
12
|
+
const { complete } = spec;
|
|
13
|
+
const offered = await Promise.resolve()
|
|
14
|
+
.then(async () => [...(await complete(partial))])
|
|
15
|
+
.then((all) => all, () => []);
|
|
16
|
+
const found = offered.filter((c) => c.startsWith(partial));
|
|
17
|
+
if (found.length > 0)
|
|
18
|
+
write(`${found.join('\n')}\n`);
|
|
19
|
+
}
|