burgee 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +61 -9
- package/dist/brand.d.ts +62 -1
- package/dist/brand.js +73 -7
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +17 -1
- package/dist/{commander-argument.js → commander/argument.js} +4 -1
- package/dist/{commander-command.d.ts → commander/command.d.ts} +15 -5
- package/dist/{commander-command.js → commander/command.js} +175 -22
- package/dist/{commander-error.js → commander/error.js} +2 -0
- package/dist/{commander-help.d.ts → commander/help.d.ts} +3 -3
- package/dist/{commander-help.js → commander/help.js} +18 -1
- package/dist/{commander-option.d.ts → commander/option.d.ts} +1 -1
- package/dist/{commander-option.js → commander/option.js} +15 -1
- package/dist/commander.d.ts +8 -8
- package/dist/commander.js +8 -8
- package/dist/completions.js +15 -4
- package/dist/dev.d.ts +47 -0
- package/dist/dev.js +132 -0
- package/dist/execute.d.ts +22 -1
- package/dist/execute.js +75 -24
- package/dist/help.d.ts +19 -7
- package/dist/help.js +50 -21
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +15 -0
- package/dist/manifest.js +12 -1
- package/dist/mcp.d.ts +14 -2
- package/dist/mcp.js +15 -2
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.js +7 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +11 -0
- package/dist/testing-helpers.d.ts +18 -1
- package/dist/testing-helpers.js +35 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +1 -1
- package/dist/unknown-option.d.ts +9 -0
- package/dist/unknown-option.js +27 -0
- package/dist/{yargs-burgee.d.ts → yargs/burgee.d.ts} +2 -2
- package/dist/{yargs-burgee.js → yargs/burgee.js} +15 -1
- package/dist/{yargs-cliui.js → yargs/cliui.js} +8 -0
- package/dist/{yargs-command.d.ts → yargs/command.d.ts} +5 -5
- package/dist/{yargs-command.js → yargs/command.js} +9 -2
- package/dist/{yargs-completion.d.ts → yargs/completion.d.ts} +3 -3
- package/dist/{yargs-completion.js → yargs/completion.js} +7 -2
- package/dist/{yargs-factory.d.ts → yargs/factory.d.ts} +8 -8
- package/dist/{yargs-factory.js → yargs/factory.js} +63 -13
- package/dist/{yargs-middleware.js → yargs/middleware.js} +6 -1
- package/dist/{yargs-shim.d.ts → yargs/shim.d.ts} +3 -3
- package/dist/{yargs-shim.js → yargs/shim.js} +24 -5
- package/dist/{yargs-usage.d.ts → yargs/usage.d.ts} +1 -1
- package/dist/{yargs-usage.js → yargs/usage.js} +9 -1
- package/dist/{yargs-utils.js → yargs/utils.js} +11 -0
- package/dist/{yargs-validation.d.ts → yargs/validation.d.ts} +1 -1
- package/dist/{yargs-validation.js → yargs/validation.js} +7 -1
- package/dist/{yargs-y18n.js → yargs/y18n.js} +6 -0
- package/dist/yargs-helpers.d.ts +1 -1
- package/dist/yargs-helpers.js +1 -1
- package/dist/yargs.d.ts +4 -4
- package/dist/yargs.js +5 -5
- package/package.json +3 -2
- /package/dist/{commander-argument.d.ts → commander/argument.d.ts} +0 -0
- /package/dist/{commander-error.d.ts → commander/error.d.ts} +0 -0
- /package/dist/{commander-suggest.d.ts → suggest.d.ts} +0 -0
- /package/dist/{commander-suggest.js → suggest.js} +0 -0
- /package/dist/{yargs-cliui.d.ts → yargs/cliui.d.ts} +0 -0
- /package/dist/{yargs-middleware.d.ts → yargs/middleware.d.ts} +0 -0
- /package/dist/{yargs-utils.d.ts → yargs/utils.d.ts} +0 -0
- /package/dist/{yargs-y18n.d.ts → yargs/y18n.d.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** commander's error classes, byte-for-byte in shape: `code`, `exitCode`, `nestedError`. */
|
|
1
2
|
export class CommanderError extends Error {
|
|
2
3
|
code;
|
|
3
4
|
exitCode;
|
|
@@ -18,3 +19,4 @@ export class InvalidArgumentError extends CommanderError {
|
|
|
18
19
|
this.name = this.constructor.name;
|
|
19
20
|
}
|
|
20
21
|
}
|
|
22
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Argument } from './
|
|
2
|
-
import type { Command } from './
|
|
3
|
-
import type { Option } from './
|
|
1
|
+
import { type Argument } from './argument.js';
|
|
2
|
+
import type { Command } from './command.js';
|
|
3
|
+
import type { Option } from './option.js';
|
|
4
4
|
export interface HelpContext {
|
|
5
5
|
error?: boolean;
|
|
6
6
|
helpWidth?: number;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { stripVTControlCharacters } from 'node:util';
|
|
2
|
-
import { humanReadableArgName } from './
|
|
2
|
+
import { humanReadableArgName } from './argument.js';
|
|
3
|
+
/** Methods are static in style so a subclass, or plain functions via `configureHelp`, can override them. */
|
|
3
4
|
export class Help {
|
|
4
5
|
helpWidth = undefined;
|
|
5
6
|
minWidthToWrap = 40;
|
|
6
7
|
sortSubcommands = false;
|
|
7
8
|
sortOptions = false;
|
|
8
9
|
showGlobalOptions = false;
|
|
10
|
+
/** Called after `configureHelp` overrides are applied and just before `formatHelp`. */
|
|
9
11
|
prepareContext(contextOptions) {
|
|
10
12
|
this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
|
|
11
13
|
}
|
|
@@ -26,6 +28,7 @@ export class Help {
|
|
|
26
28
|
const visibleOptions = cmd.options.filter((option) => !option.hidden);
|
|
27
29
|
const helpOption = cmd._getHelpOption();
|
|
28
30
|
if (helpOption && !helpOption.hidden) {
|
|
31
|
+
// Historical: hide the built-in help flags a user option already took.
|
|
29
32
|
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
|
|
30
33
|
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
|
|
31
34
|
if (!removeShort && !removeLong)
|
|
@@ -51,6 +54,7 @@ export class Help {
|
|
|
51
54
|
return globalOptions;
|
|
52
55
|
}
|
|
53
56
|
visibleArguments(cmd) {
|
|
57
|
+
// Side effect: apply the legacy descriptions before the arguments are displayed.
|
|
54
58
|
if (cmd._argsDescription) {
|
|
55
59
|
for (const argument of cmd.registeredArguments) {
|
|
56
60
|
argument.description = argument.description || cmd._argsDescription[argument.name()] || '';
|
|
@@ -106,6 +110,7 @@ export class Help {
|
|
|
106
110
|
commandDescription(cmd) {
|
|
107
111
|
return cmd.description();
|
|
108
112
|
}
|
|
113
|
+
/** Summary, falling back to description for backwards compatibility. */
|
|
109
114
|
subcommandDescription(cmd) {
|
|
110
115
|
return cmd.summary() || cmd.description();
|
|
111
116
|
}
|
|
@@ -115,6 +120,7 @@ export class Help {
|
|
|
115
120
|
extraInfo.push(`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
|
|
116
121
|
}
|
|
117
122
|
if (option.defaultValue !== undefined) {
|
|
123
|
+
// Defaults for boolean and negated are more for the programmer than the end user.
|
|
118
124
|
const showDefault = option.required || option.optional || (option.isBoolean() && typeof option.defaultValue === 'boolean');
|
|
119
125
|
if (showDefault)
|
|
120
126
|
extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
|
|
@@ -148,6 +154,7 @@ export class Help {
|
|
|
148
154
|
return [];
|
|
149
155
|
return [helper.styleTitle(heading), ...items, ''];
|
|
150
156
|
}
|
|
157
|
+
/** Groups in order of first appearance among all items; members in order of the visible items. */
|
|
151
158
|
groupItems(unsortedItems, visibleItems, getGroup) {
|
|
152
159
|
const result = new Map();
|
|
153
160
|
for (const item of unsortedItems) {
|
|
@@ -196,6 +203,7 @@ export class Help {
|
|
|
196
203
|
}
|
|
197
204
|
return output.join('\n');
|
|
198
205
|
}
|
|
206
|
+
/** Width ignoring ANSI escapes, for padding and wrapping. */
|
|
199
207
|
displayWidth(str) {
|
|
200
208
|
return stripVTControlCharacters(str).length;
|
|
201
209
|
}
|
|
@@ -203,6 +211,7 @@ export class Help {
|
|
|
203
211
|
return str;
|
|
204
212
|
}
|
|
205
213
|
styleUsage(str) {
|
|
214
|
+
// Assume the default usage shape: command subcommand [options] [command] <foo> [bar]
|
|
206
215
|
return str
|
|
207
216
|
.split(' ')
|
|
208
217
|
.map((word) => {
|
|
@@ -264,9 +273,15 @@ export class Help {
|
|
|
264
273
|
padWidth(cmd, helper) {
|
|
265
274
|
return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
|
|
266
275
|
}
|
|
276
|
+
/** Manually wrapped text: a line break followed by whitespace. */
|
|
267
277
|
preformatted(str) {
|
|
268
278
|
return /\n[^\S\r\n]/.test(str);
|
|
269
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Pad the term and wrap the description, indenting the following lines:
|
|
282
|
+
* TTT DDD DDDD
|
|
283
|
+
* DD DDD
|
|
284
|
+
*/
|
|
270
285
|
formatItem(term, termWidth, description, helper) {
|
|
271
286
|
const itemIndent = 2;
|
|
272
287
|
const itemIndentStr = ' '.repeat(itemIndent);
|
|
@@ -286,6 +301,7 @@ export class Help {
|
|
|
286
301
|
}
|
|
287
302
|
return itemIndentStr + paddedTerm + ' '.repeat(spacerWidth) + formattedDescription.replace(/\n/g, `\n${itemIndentStr}`);
|
|
288
303
|
}
|
|
304
|
+
/** Wrap at whitespace, preserving existing line breaks; skipped below `minWidthToWrap`. */
|
|
289
305
|
boxWrap(str, width) {
|
|
290
306
|
if (width < this.minWidthToWrap)
|
|
291
307
|
return str;
|
|
@@ -317,3 +333,4 @@ export class Help {
|
|
|
317
333
|
return wrappedLines.join('\n');
|
|
318
334
|
}
|
|
319
335
|
}
|
|
336
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {} from './argument.js';
|
|
2
|
+
import { InvalidArgumentError } from './error.js';
|
|
2
3
|
export class Option {
|
|
3
4
|
flags;
|
|
4
5
|
description;
|
|
@@ -24,6 +25,7 @@ export class Option {
|
|
|
24
25
|
this.description = description || '';
|
|
25
26
|
this.required = flags.includes('<');
|
|
26
27
|
this.optional = flags.includes('[');
|
|
28
|
+
// `<value,...>` describes custom splitting of one argument, not a variadic option.
|
|
27
29
|
this.variadic = /\w\.\.\.[>\]]$/.test(flags);
|
|
28
30
|
const { shortFlag, longFlag } = splitOptionFlags(flags);
|
|
29
31
|
this.short = shortFlag;
|
|
@@ -36,6 +38,7 @@ export class Option {
|
|
|
36
38
|
this.defaultValueDescription = description;
|
|
37
39
|
return this;
|
|
38
40
|
}
|
|
41
|
+
/** Value used when the option appears without an option-argument. `parseArg` still runs. */
|
|
39
42
|
preset(arg) {
|
|
40
43
|
this.presetArg = arg;
|
|
41
44
|
return this;
|
|
@@ -44,6 +47,7 @@ export class Option {
|
|
|
44
47
|
this.conflictsWith = this.conflictsWith.concat(names);
|
|
45
48
|
return this;
|
|
46
49
|
}
|
|
50
|
+
/** Values implied for other options when this one is set and they are not. `parseArg` does not run on them. */
|
|
47
51
|
implies(impliedOptionValues) {
|
|
48
52
|
const newImplied = typeof impliedOptionValues === 'string' ? { [impliedOptionValues]: true } : impliedOptionValues;
|
|
49
53
|
this.implied = Object.assign(this.implied ?? {}, newImplied);
|
|
@@ -86,6 +90,7 @@ export class Option {
|
|
|
86
90
|
return this.long.replace(/^--/, '');
|
|
87
91
|
return (this.short ?? '').replace(/^-/, '');
|
|
88
92
|
}
|
|
93
|
+
/** camelCase key used on the options object; `--no-foo` shares `foo` with `--foo`. */
|
|
89
94
|
attributeName() {
|
|
90
95
|
return camelcase(this.negate ? this.name().replace(/^no-/, '') : this.name());
|
|
91
96
|
}
|
|
@@ -96,10 +101,15 @@ export class Option {
|
|
|
96
101
|
is(arg) {
|
|
97
102
|
return this.short === arg || this.long === arg;
|
|
98
103
|
}
|
|
104
|
+
/** Options are one of boolean, negated, required-argument or optional-argument. */
|
|
99
105
|
isBoolean() {
|
|
100
106
|
return !this.required && !this.optional && !this.negate;
|
|
101
107
|
}
|
|
102
108
|
}
|
|
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
|
+
*/
|
|
103
113
|
export class DualOptions {
|
|
104
114
|
positiveOptions = new Map();
|
|
105
115
|
negativeOptions = new Map();
|
|
@@ -125,6 +135,7 @@ export class DualOptions {
|
|
|
125
135
|
function camelcase(str) {
|
|
126
136
|
return str.split('-').reduce((acc, word) => acc + (word[0] ?? '').toUpperCase() + word.slice(1));
|
|
127
137
|
}
|
|
138
|
+
/** Split `'-m,--mixed <value>'` into its short and long flags, failing noisily on anything else. */
|
|
128
139
|
export function splitOptionFlags(flags) {
|
|
129
140
|
let shortFlag;
|
|
130
141
|
let longFlag;
|
|
@@ -136,8 +147,10 @@ export function splitOptionFlags(flags) {
|
|
|
136
147
|
shortFlag = flagParts.shift();
|
|
137
148
|
if (longFlagExp.test(head()))
|
|
138
149
|
longFlag = flagParts.shift();
|
|
150
|
+
// Long then short. Rarely used but fine.
|
|
139
151
|
if (!shortFlag && shortFlagExp.test(head()))
|
|
140
152
|
shortFlag = flagParts.shift();
|
|
153
|
+
// Two long flags, like '--ws, --workspace': the supported way to have a shortish flag.
|
|
141
154
|
if (!shortFlag && longFlagExp.test(head())) {
|
|
142
155
|
shortFlag = longFlag;
|
|
143
156
|
longFlag = flagParts.shift();
|
|
@@ -162,3 +175,4 @@ export function splitOptionFlags(flags) {
|
|
|
162
175
|
}
|
|
163
176
|
return { shortFlag, longFlag };
|
|
164
177
|
}
|
|
178
|
+
//# sourceMappingURL=option.js.map
|
package/dist/commander.d.ts
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* dependency on commander itself). Graded by commander's own suite; see
|
|
4
4
|
* `.sdlc/intents/commander-compat/design.md` and `npm run compat`.
|
|
5
5
|
*/
|
|
6
|
-
import { Argument } from './commander
|
|
7
|
-
import { Command } from './commander
|
|
8
|
-
import { Option } from './commander
|
|
9
|
-
export { Argument, humanReadableArgName } from './commander
|
|
10
|
-
export { Command, useColor, type AddHelpTextContext, type AddHelpTextPosition, type BurgeeParseOptions, type CommandOptions, type ErrorOptions, type ExecutableCommandOptions, type HookEvent, type HookListener, type OutputConfiguration, type OutputContext, type ParseOptions, } from './commander
|
|
11
|
-
export { CommanderError, InvalidArgumentError, InvalidArgumentError as InvalidOptionArgumentError } from './commander
|
|
12
|
-
export { Help, type HelpContext } from './commander
|
|
13
|
-
export { DualOptions, Option } from './commander
|
|
6
|
+
import { Argument } from './commander/argument.js';
|
|
7
|
+
import { Command } from './commander/command.js';
|
|
8
|
+
import { Option } from './commander/option.js';
|
|
9
|
+
export { Argument, humanReadableArgName } from './commander/argument.js';
|
|
10
|
+
export { Command, useColor, type AddHelpTextContext, type AddHelpTextPosition, type BurgeeParseOptions, type CommandOptions, type ErrorOptions, type ExecutableCommandOptions, type HookEvent, type HookListener, type OutputConfiguration, type OutputContext, type ParseOptions, } from './commander/command.js';
|
|
11
|
+
export { CommanderError, InvalidArgumentError, InvalidArgumentError as InvalidOptionArgumentError } from './commander/error.js';
|
|
12
|
+
export { Help, type HelpContext } from './commander/help.js';
|
|
13
|
+
export { DualOptions, Option } from './commander/option.js';
|
|
14
14
|
/** The root command, for programs that never construct their own. */
|
|
15
15
|
export declare const program: Command;
|
|
16
16
|
export declare const createCommand: (name?: string) => Command;
|
package/dist/commander.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Argument } from './commander
|
|
2
|
-
import { Command } from './commander
|
|
3
|
-
import { Option } from './commander
|
|
4
|
-
export { Argument, humanReadableArgName } from './commander
|
|
5
|
-
export { Command, useColor, } from './commander
|
|
6
|
-
export { CommanderError, InvalidArgumentError, InvalidArgumentError as InvalidOptionArgumentError } from './commander
|
|
7
|
-
export { Help } from './commander
|
|
8
|
-
export { DualOptions, Option } from './commander
|
|
1
|
+
import { Argument } from './commander/argument.js';
|
|
2
|
+
import { Command } from './commander/command.js';
|
|
3
|
+
import { Option } from './commander/option.js';
|
|
4
|
+
export { Argument, humanReadableArgName } from './commander/argument.js';
|
|
5
|
+
export { Command, useColor, } from './commander/command.js';
|
|
6
|
+
export { CommanderError, InvalidArgumentError, InvalidArgumentError as InvalidOptionArgumentError } from './commander/error.js';
|
|
7
|
+
export { Help } from './commander/help.js';
|
|
8
|
+
export { DualOptions, Option } from './commander/option.js';
|
|
9
9
|
export const program = new Command();
|
|
10
10
|
export const createCommand = (name) => new Command(name);
|
|
11
11
|
export const createOption = (flags, description) => new Option(flags, description);
|
package/dist/completions.js
CHANGED
|
@@ -14,7 +14,9 @@ function children(manifest, at) {
|
|
|
14
14
|
.map((c) => nodeOf(manifest, c));
|
|
15
15
|
}
|
|
16
16
|
function nodeOf(manifest, c) {
|
|
17
|
-
const visible = Object.fromEntries(Object.entries(c.options)
|
|
17
|
+
const visible = Object.fromEntries(Object.entries(c.options)
|
|
18
|
+
.filter(([, spec]) => spec.hidden !== true)
|
|
19
|
+
.map(([name, spec]) => [name, spec.type === 'boolean' ? { ...spec, negatable: true } : spec]));
|
|
18
20
|
const isRoot = c.path.length === manifest.rootPath.length;
|
|
19
21
|
return {
|
|
20
22
|
path: c.path.slice(manifest.rootPath.length),
|
|
@@ -40,7 +42,11 @@ function walk(root) {
|
|
|
40
42
|
return out;
|
|
41
43
|
}
|
|
42
44
|
const sq = (s) => `'${s.replaceAll("'", "'\\''")}'`;
|
|
43
|
-
const flags = (name, spec) =>
|
|
45
|
+
const flags = (name, spec) => {
|
|
46
|
+
const long = `--${kebab(name)}`;
|
|
47
|
+
const spellings = spec.short === undefined ? [long] : [`-${spec.short}`, long];
|
|
48
|
+
return spec.negatable === true ? [...spellings, `--no-${kebab(name)}`] : spellings;
|
|
49
|
+
};
|
|
44
50
|
const takesValue = (spec) => spec.type !== 'boolean';
|
|
45
51
|
const fname = (program, path) => `_${[program, ...path].join('_').replaceAll(/[^A-Za-z0-9_]/g, '_')}`;
|
|
46
52
|
function bashCase(program, node) {
|
|
@@ -141,8 +147,13 @@ function fish(program, root) {
|
|
|
141
147
|
const cond = sq(fishCondition(node));
|
|
142
148
|
for (const c of node.children)
|
|
143
149
|
lines.push(`complete -c ${program} -n ${cond} -f -a ${c.path[c.path.length - 1] ?? ''} -d ${sq(c.description)}`);
|
|
144
|
-
for (const [name, spec] of Object.entries(node.options))
|
|
145
|
-
lines.push(fishOption(program, cond, name, spec));
|
|
150
|
+
for (const [name, spec] of Object.entries(node.options)) {
|
|
151
|
+
lines.push(fishOption(program, cond, kebab(name), spec));
|
|
152
|
+
if (spec.negatable === true) {
|
|
153
|
+
const { short: _short, ...unshort } = spec;
|
|
154
|
+
lines.push(fishOption(program, cond, `no-${kebab(name)}`, unshort));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
146
157
|
}
|
|
147
158
|
return `# ${program} completion for fish, generated by burgee — never runs ${program} on TAB.
|
|
148
159
|
# Install: ${program} completion fish > ~/.config/fish/completions/${program}.fish
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Manifest } from './manifest.js';
|
|
2
|
+
import { type Invoke } from './mcp.js';
|
|
3
|
+
export interface Writer {
|
|
4
|
+
write: (s: string) => unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface DevOptions {
|
|
7
|
+
/** The module that exports the program: `program` (or `default`) as a burgee manifest, a commander `Command` or a yargs instance. */
|
|
8
|
+
entry: string;
|
|
9
|
+
/** The MCP channel. */
|
|
10
|
+
input: NodeJS.ReadableStream;
|
|
11
|
+
output: Writer;
|
|
12
|
+
/** Where the reload report goes: never stdout, which carries MCP. */
|
|
13
|
+
log: Writer;
|
|
14
|
+
/** Off for tests that drive `reload()` themselves. */
|
|
15
|
+
watch?: boolean;
|
|
16
|
+
/** Files changed within this window coalesce into one reload. */
|
|
17
|
+
debounceMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface Loaded {
|
|
20
|
+
manifest: Manifest;
|
|
21
|
+
invoke: Invoke;
|
|
22
|
+
kind: 'burgee' | 'commander' | 'yargs';
|
|
23
|
+
/** Milliseconds from the trigger to the manifest being served (W6). */
|
|
24
|
+
ms: number;
|
|
25
|
+
}
|
|
26
|
+
export interface DevHandle {
|
|
27
|
+
ready: Promise<Loaded>;
|
|
28
|
+
/** Re-import the entry, print the diff and the help, swap the served manifest. */
|
|
29
|
+
reload: () => Promise<Loaded>;
|
|
30
|
+
/** Settles when the MCP input closes. */
|
|
31
|
+
done: Promise<void>;
|
|
32
|
+
close: () => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Import the entry as a fresh module graph. The query is what discards the previous
|
|
36
|
+
* graph: the same URL would hand back the cached module and its stale handlers.
|
|
37
|
+
*/
|
|
38
|
+
export declare function load(entry: string, generation: number): Promise<Loaded>;
|
|
39
|
+
/** What changed between two manifests, by command path: added, removed, or a different schema. */
|
|
40
|
+
export declare function diffManifests(before: Manifest | undefined, after: Manifest): {
|
|
41
|
+
added: string[];
|
|
42
|
+
removed: string[];
|
|
43
|
+
changed: string[];
|
|
44
|
+
};
|
|
45
|
+
/** The reload report: one save, every surface (W3). */
|
|
46
|
+
export declare function report(before: Manifest | undefined, loaded: Loaded): string;
|
|
47
|
+
export declare function dev(opts: DevOptions): DevHandle;
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { watch } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { execute } from './execute.js';
|
|
5
|
+
import { renderHelp } from './help.js';
|
|
6
|
+
import { Manifest } from './manifest.js';
|
|
7
|
+
import { startMcp, toolsOf } from './mcp.js';
|
|
8
|
+
import { commandSchemaOf } from './schema.js';
|
|
9
|
+
const DEFAULT_DEBOUNCE_MS = 50;
|
|
10
|
+
const SOURCE = /\.(m?[jt]s|c[jt]s|json)$/;
|
|
11
|
+
const isObject = (x) => x !== null && x !== undefined && typeof x === 'object' && !Array.isArray(x);
|
|
12
|
+
function isManifest(x) {
|
|
13
|
+
return x instanceof Manifest || (isObject(x) && Array.isArray(x['commands']) && Array.isArray(x['rootPath']));
|
|
14
|
+
}
|
|
15
|
+
function isYargs(x) {
|
|
16
|
+
return isObject(x) && typeof x['burgee'] === 'function' && isObject(x['manifest']);
|
|
17
|
+
}
|
|
18
|
+
function isCommander(x) {
|
|
19
|
+
return isObject(x) && typeof x['parseAsync'] === 'function' && isObject(x['manifest']);
|
|
20
|
+
}
|
|
21
|
+
function invokeOn(program, kind, entry) {
|
|
22
|
+
return async (argv) => {
|
|
23
|
+
const out = [];
|
|
24
|
+
const err = [];
|
|
25
|
+
let code = 0;
|
|
26
|
+
const seam = { stdout: { write: (s) => out.push(s) }, stderr: { write: (s) => err.push(s) }, exit: (c) => void (code = c) };
|
|
27
|
+
if (kind === 'burgee')
|
|
28
|
+
await execute(program, { argv, from: 'user', entry, ...seam });
|
|
29
|
+
else if (kind === 'yargs')
|
|
30
|
+
await program.burgee(seam).parseAsync(argv);
|
|
31
|
+
else
|
|
32
|
+
await program.parseAsync(argv, { from: 'user', ...seam });
|
|
33
|
+
return { stdout: out.join(''), stderr: err.join(''), code };
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function load(entry, generation) {
|
|
37
|
+
const started = performance.now();
|
|
38
|
+
const url = pathToFileURL(resolve(entry));
|
|
39
|
+
url.searchParams.set('burgee-dev', String(generation));
|
|
40
|
+
const mod = (await import(url.href));
|
|
41
|
+
const exported = mod['program'] ?? mod['default'];
|
|
42
|
+
if (isManifest(exported))
|
|
43
|
+
return { manifest: exported, invoke: invokeOn(exported, 'burgee', entry), kind: 'burgee', ms: performance.now() - started };
|
|
44
|
+
if (isYargs(exported))
|
|
45
|
+
return { manifest: exported.manifest, invoke: invokeOn(exported, 'yargs', entry), kind: 'yargs', ms: performance.now() - started };
|
|
46
|
+
if (isCommander(exported))
|
|
47
|
+
return { manifest: exported.manifest, invoke: invokeOn(exported, 'commander', entry), kind: 'commander', ms: performance.now() - started };
|
|
48
|
+
throw new Error(`${entry} exports no program: export a burgee manifest, a commander Command or a yargs instance as \`program\` or default`);
|
|
49
|
+
}
|
|
50
|
+
const schemasByPath = (m) => new Map(m.commands.map((c) => [c.path.join(' '), JSON.stringify(commandSchemaOf(c, m.rootPath))]));
|
|
51
|
+
export function diffManifests(before, after) {
|
|
52
|
+
const was = before === undefined ? new Map() : schemasByPath(before);
|
|
53
|
+
const now = schemasByPath(after);
|
|
54
|
+
return {
|
|
55
|
+
added: [...now.keys()].filter((k) => !was.has(k)),
|
|
56
|
+
removed: [...was.keys()].filter((k) => !now.has(k)),
|
|
57
|
+
changed: [...now.keys()].filter((k) => was.has(k) && was.get(k) !== now.get(k)),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function report(before, loaded) {
|
|
61
|
+
const { manifest } = loaded;
|
|
62
|
+
const diff = diffManifests(before, manifest);
|
|
63
|
+
const lines = [];
|
|
64
|
+
lines.push(`${before === undefined ? 'loaded' : 'reloaded'} ${manifest.rootPath.join(' ')} (${loaded.kind}) in ${loaded.ms.toFixed(0)} ms — ${manifest.commands.length} commands, ${toolsOf(manifest).length} MCP tools`);
|
|
65
|
+
for (const p of diff.added)
|
|
66
|
+
lines.push(` + ${p}`);
|
|
67
|
+
for (const p of diff.removed)
|
|
68
|
+
lines.push(` - ${p}`);
|
|
69
|
+
for (const p of diff.changed)
|
|
70
|
+
lines.push(` ~ ${p}`);
|
|
71
|
+
const root = manifest.find(manifest.rootPath);
|
|
72
|
+
if (root !== undefined)
|
|
73
|
+
lines.push('', renderHelp(manifest, root).trimEnd());
|
|
74
|
+
return `${lines.join('\n')}\n`;
|
|
75
|
+
}
|
|
76
|
+
export function dev(opts) {
|
|
77
|
+
let generation = 0;
|
|
78
|
+
let current;
|
|
79
|
+
let server;
|
|
80
|
+
let watcher;
|
|
81
|
+
let timer;
|
|
82
|
+
let chain = Promise.resolve();
|
|
83
|
+
const entry = resolve(opts.entry);
|
|
84
|
+
const reloadNow = async () => {
|
|
85
|
+
generation += 1;
|
|
86
|
+
const loaded = await load(entry, generation);
|
|
87
|
+
opts.log.write(report(current, loaded));
|
|
88
|
+
current = loaded.manifest;
|
|
89
|
+
if (server === undefined)
|
|
90
|
+
server = startMcp(loaded.manifest, { input: opts.input, output: opts.output, invoke: loaded.invoke });
|
|
91
|
+
else
|
|
92
|
+
server.swap(loaded.manifest, loaded.invoke);
|
|
93
|
+
return loaded;
|
|
94
|
+
};
|
|
95
|
+
const reload = async () => {
|
|
96
|
+
await chain;
|
|
97
|
+
const next = reloadNow();
|
|
98
|
+
chain = next.catch((err) => {
|
|
99
|
+
opts.log.write(`reload failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
100
|
+
});
|
|
101
|
+
return await next;
|
|
102
|
+
};
|
|
103
|
+
const ready = reload();
|
|
104
|
+
if (opts.watch !== false) {
|
|
105
|
+
watcher = watch(dirname(entry), { recursive: true }, (_event, filename) => {
|
|
106
|
+
const name = filename === null ? '' : String(filename);
|
|
107
|
+
if (name !== '' && (!SOURCE.test(name) || name.includes('node_modules')))
|
|
108
|
+
return;
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
timer = setTimeout(() => void reload().catch(() => undefined), opts.debounceMs ?? DEFAULT_DEBOUNCE_MS);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const untilInputCloses = async () => {
|
|
114
|
+
try {
|
|
115
|
+
await ready;
|
|
116
|
+
await server.done;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
watcher?.close();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const done = untilInputCloses();
|
|
123
|
+
return {
|
|
124
|
+
ready,
|
|
125
|
+
reload,
|
|
126
|
+
done,
|
|
127
|
+
close: () => {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
watcher?.close();
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
package/dist/execute.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ArgumentSpec, type Effects, type Example, Manifest, type OptionSpec, type Relation, type RunContext } from './manifest.js';
|
|
1
|
+
import { type ArgumentSpec, type CommandNode, type Effects, type Example, type LazyModule, Manifest, type OptionSpec, type Relation, type RunContext } from './manifest.js';
|
|
2
2
|
export interface CommandContext<O> extends Omit<RunContext, 'options'> {
|
|
3
3
|
options: O;
|
|
4
4
|
}
|
|
@@ -39,6 +39,8 @@ export interface Command<S extends OptionSpecs = OptionSpecs> {
|
|
|
39
39
|
relations?: readonly Relation[];
|
|
40
40
|
/** Absent on a group that only holds subcommands. `NoInfer`: the spec fixes S, the handler only reads it. */
|
|
41
41
|
run?: (ctx: CommandContext<InferOptions<NoInfer<S>>>) => unknown;
|
|
42
|
+
/** The handler's module, imported on dispatch only (M2); everything else about the command is declared here. */
|
|
43
|
+
load?: () => Promise<LazyModule>;
|
|
42
44
|
commands?: AnyCommand[];
|
|
43
45
|
}
|
|
44
46
|
export interface Program {
|
|
@@ -59,6 +61,13 @@ export interface Program {
|
|
|
59
61
|
commands: AnyCommand[];
|
|
60
62
|
}
|
|
61
63
|
export declare function defineCommand<const S extends OptionSpecs = OptionSpecs>(command: Command<S>): Command<S>;
|
|
64
|
+
/**
|
|
65
|
+
* Options declared once and spread into each command that takes them (M4): never global,
|
|
66
|
+
* so `--schema` stays a tree and each command's help lists them as its own, and the
|
|
67
|
+
* handler's `options` type carries them like any other. Every copy is tagged
|
|
68
|
+
* `sharedFrom` with the set's name, so the schema says where it came from.
|
|
69
|
+
*/
|
|
70
|
+
export declare function sharedOptions<const T extends OptionSpecs>(name: string, specs: T): T;
|
|
62
71
|
/** A native multi-command program. The manifest it builds is the same one the façades fill. */
|
|
63
72
|
export declare function defineProgram(program: Program): Manifest;
|
|
64
73
|
export interface RunOptions {
|
|
@@ -89,6 +98,18 @@ export declare function execute(manifest: Manifest, opts?: RunOptions & {
|
|
|
89
98
|
root?: string[];
|
|
90
99
|
from?: 'node' | 'user';
|
|
91
100
|
}): Promise<void>;
|
|
101
|
+
/** M6: which command argv names, or null — the same longest-prefix match `execute` uses. */
|
|
102
|
+
export declare function resolveCommand(manifest: Manifest, argv: readonly string[]): CommandNode | null;
|
|
103
|
+
export interface RunResult {
|
|
104
|
+
code: number;
|
|
105
|
+
stdout: string;
|
|
106
|
+
stderr: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* M6: run a command programmatically — the harness's own entry, public. The streams are
|
|
110
|
+
* captured, the exit recorded; env, cwd and the entry can be injected like `execute`'s.
|
|
111
|
+
*/
|
|
112
|
+
export declare function runCommand(manifest: Manifest, argv: readonly string[], opts?: Pick<RunOptions, 'env' | 'cwd' | 'entry' | 'stdin'>): Promise<RunResult>;
|
|
92
113
|
/**
|
|
93
114
|
* The one-file entry: a single command, or a program from `defineProgram`. Both go
|
|
94
115
|
* through `execute`, so there is exactly one code path from argv to exit.
|