burgee 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cli.d.ts +1 -90
- package/dist/cli.js +3 -151
- package/dist/commander/command.d.ts +22 -0
- package/dist/commander/command.js +41 -10
- 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 +32 -14
- 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 +71 -13
- 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 +35 -5
- package/dist/manifest.js +6 -0
- package/dist/mcp.js +88 -5
- 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/dist/manifest.d.ts
CHANGED
|
@@ -36,6 +36,13 @@ export type StandardResult<Output> = {
|
|
|
36
36
|
export interface OptionSpec {
|
|
37
37
|
/** `boolean` never consumes a value (S7); `number` rejects NaN and Infinity (S3). */
|
|
38
38
|
type: 'string' | 'boolean' | 'number';
|
|
39
|
+
/**
|
|
40
|
+
* D3 / D-119 — values computed when a person presses TAB: the generated script calls the
|
|
41
|
+
* program back (`<program> __complete <command> --<option> <partial>`) for this option and
|
|
42
|
+
* no other. Declaring it is the opt-in; an option without one completes from `choices`, or
|
|
43
|
+
* not at all, and never runs the program.
|
|
44
|
+
*/
|
|
45
|
+
complete?: (partial: string) => Iterable<string> | Promise<Iterable<string>>;
|
|
39
46
|
description?: string;
|
|
40
47
|
required?: boolean;
|
|
41
48
|
short?: string;
|
|
@@ -157,6 +164,8 @@ export interface ArgumentSpec {
|
|
|
157
164
|
required?: boolean;
|
|
158
165
|
variadic?: boolean;
|
|
159
166
|
default?: string;
|
|
167
|
+
/** `'file'`: a path, where `-` means standard input — handed to the handler as `ctx.stdin` (S4). */
|
|
168
|
+
type?: 'file';
|
|
160
169
|
}
|
|
161
170
|
/** One example: a single copy-pasteable command line, the description below it (H2). */
|
|
162
171
|
export interface Example {
|
|
@@ -180,6 +189,11 @@ export interface RunContext {
|
|
|
180
189
|
options: Record<string, unknown>;
|
|
181
190
|
positionals: string[];
|
|
182
191
|
passthrough: string[];
|
|
192
|
+
/**
|
|
193
|
+
* Standard input, present only when a `type: 'file'` argument was given `-` (S4). The
|
|
194
|
+
* positional still reads `-`, so a handler checks it the same way it checks a path.
|
|
195
|
+
*/
|
|
196
|
+
stdin?: NodeJS.ReadableStream;
|
|
183
197
|
env: Record<string, string | undefined>;
|
|
184
198
|
/** Exit with an E1 code. Unwinds cleanly: the code is honoured and nothing is printed. */
|
|
185
199
|
exit: (code: number) => never;
|
|
@@ -226,6 +240,8 @@ export interface CommandNode {
|
|
|
226
240
|
* none, so a façade's command is withheld in fact and cannot be made to say so.
|
|
227
241
|
*/
|
|
228
242
|
effects?: DeclaredEffects;
|
|
243
|
+
/** The result's top-level fields, as declared (N14); what `--json=` lists. */
|
|
244
|
+
fields?: readonly string[];
|
|
229
245
|
run?: (ctx: RunContext) => unknown;
|
|
230
246
|
/**
|
|
231
247
|
* The handler's module, imported on dispatch only (M2): the manifest — help, schema,
|
|
@@ -242,13 +258,19 @@ export declare function lazyRun(load: () => Promise<LazyModule>): (ctx: RunConte
|
|
|
242
258
|
export interface HookFilter {
|
|
243
259
|
command?: RegExp;
|
|
244
260
|
}
|
|
261
|
+
/** What a hook is handed. `argv` on `parse` only; `options` is empty on `parse` and `shutdown`. */
|
|
262
|
+
export interface HookContext {
|
|
263
|
+
command: string;
|
|
264
|
+
options: Record<string, unknown>;
|
|
265
|
+
argv?: string[];
|
|
266
|
+
}
|
|
245
267
|
export interface Hook {
|
|
246
268
|
filter?: HookFilter;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
options: Record<string, unknown>;
|
|
250
|
-
}) => void | Promise<void>;
|
|
269
|
+
/** `parse` may return the argv to use instead; every other stage's return is ignored. */
|
|
270
|
+
handler: (ctx: HookContext) => unknown;
|
|
251
271
|
}
|
|
272
|
+
/** The stages a plugin hook fires at. `parse` and `shutdown` bracket the run (D-122). */
|
|
273
|
+
export type HookStage = 'parse' | 'preRun' | 'postRun' | 'onError' | 'shutdown';
|
|
252
274
|
/** Rolldown's lesson: evaluate the filter before crossing the boundary. */
|
|
253
275
|
export declare function hookApplies(hook: Hook | undefined, command: string): hook is Hook;
|
|
254
276
|
export declare class Manifest {
|
|
@@ -277,7 +299,15 @@ export declare class Manifest {
|
|
|
277
299
|
use(plugin: Plugin): void;
|
|
278
300
|
/** `enforce: 'pre'` first, then unordered, then `'post'` — the Vite/Rolldown convention. */
|
|
279
301
|
private ordered;
|
|
280
|
-
|
|
302
|
+
/** Whether any registered plugin declares a hook at `stage` — so a run without one pays nothing. */
|
|
303
|
+
declares(stage: HookStage): boolean;
|
|
304
|
+
/**
|
|
305
|
+
* `parse` (D-122): argv in, argv out, before the command is resolved. Each plugin, in
|
|
306
|
+
* `enforce` order, is handed what the previous one returned; returning nothing keeps it.
|
|
307
|
+
* A filter is matched against the typed argv, since no command has been resolved yet.
|
|
308
|
+
*/
|
|
309
|
+
parse(argv: string[]): Promise<string[]>;
|
|
310
|
+
fire(stage: Exclude<HookStage, 'parse'>, command: string, options: Record<string, unknown>): Promise<void>;
|
|
281
311
|
find(path: string[]): CommandNode | undefined;
|
|
282
312
|
/**
|
|
283
313
|
* Longest-prefix match of argv against declared command paths. The root's own
|
package/dist/manifest.js
CHANGED
|
@@ -49,6 +49,12 @@ export class Manifest {
|
|
|
49
49
|
ordered() {
|
|
50
50
|
return [...this.plugins].sort((a, b) => (a.enforce === undefined ? 1 : ORDER[a.enforce]) - (b.enforce === undefined ? 1 : ORDER[b.enforce]));
|
|
51
51
|
}
|
|
52
|
+
declares(stage) {
|
|
53
|
+
return this.plugins.some((p) => p.hooks?.[stage] !== undefined);
|
|
54
|
+
}
|
|
55
|
+
async parse(argv) {
|
|
56
|
+
return (await import('./parse-hooks.js')).runParseHooks(this.ordered(), argv);
|
|
57
|
+
}
|
|
52
58
|
async fire(stage, command, options) {
|
|
53
59
|
for (const plugin of this.ordered()) {
|
|
54
60
|
const hook = plugin.hooks?.[stage];
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Console } from 'node:console';
|
|
1
2
|
import { createInterface } from 'node:readline';
|
|
3
|
+
import { Writable } from 'node:stream';
|
|
2
4
|
import { WITHHELD } from './definition.js';
|
|
3
5
|
import { kebab } from './names.js';
|
|
6
|
+
import { host } from './runtime.js';
|
|
4
7
|
import { inputSchemaOf, runnable, typedName } from './schema.js';
|
|
5
8
|
export const MCP_PROTOCOL_VERSION = '2025-06-18';
|
|
6
9
|
const JSON_RPC_INVALID_REQUEST = -32600;
|
|
@@ -57,6 +60,64 @@ export function argvOf(node, root, args) {
|
|
|
57
60
|
const command = typedName(node, root).split(' ').filter((s) => s !== '');
|
|
58
61
|
return [...command, ...optionArgs(node, args), '--json', ...positionalArgs(node, args)];
|
|
59
62
|
}
|
|
63
|
+
const STDOUT_CONSOLE = ['log', 'info', 'debug', 'dir', 'dirxml', 'table', 'group', 'groupCollapsed', 'groupEnd', 'count', 'countReset', 'time', 'timeLog', 'timeEnd'];
|
|
64
|
+
let framing = false;
|
|
65
|
+
let sessions = 0;
|
|
66
|
+
let release = () => undefined;
|
|
67
|
+
function holdStdout() {
|
|
68
|
+
if (sessions++ === 0) {
|
|
69
|
+
const stdout = host.stdout;
|
|
70
|
+
const write = stdout.write;
|
|
71
|
+
stdout.write = function (...args) {
|
|
72
|
+
return Reflect.apply(framing ? write : host.stderr.write, framing ? stdout : host.stderr, args);
|
|
73
|
+
};
|
|
74
|
+
release = () => void (stdout.write = write);
|
|
75
|
+
}
|
|
76
|
+
let held = true;
|
|
77
|
+
return () => {
|
|
78
|
+
if (held && --sessions === 0)
|
|
79
|
+
release();
|
|
80
|
+
held = false;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function printedBy(run) {
|
|
84
|
+
const chunks = [];
|
|
85
|
+
const decoder = new TextDecoder();
|
|
86
|
+
const take = (chunk) => void chunks.push(typeof chunk === 'string' ? chunk : decoder.decode(chunk));
|
|
87
|
+
const sink = new Writable({
|
|
88
|
+
decodeStrings: false,
|
|
89
|
+
write(chunk, _encoding, done) {
|
|
90
|
+
take(chunk);
|
|
91
|
+
done();
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const stdout = host.stdout;
|
|
95
|
+
const write = stdout.write;
|
|
96
|
+
stdout.write = function (chunk, ...rest) {
|
|
97
|
+
if (framing)
|
|
98
|
+
return Reflect.apply(write, stdout, [chunk, ...rest]);
|
|
99
|
+
take(chunk);
|
|
100
|
+
const done = rest.find((r) => typeof r === 'function');
|
|
101
|
+
if (done !== undefined)
|
|
102
|
+
queueMicrotask(done);
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
const printer = new Console({ stdout: sink, stderr: host.stderr });
|
|
106
|
+
const saved = STDOUT_CONSOLE.map((m) => [m, console[m]]);
|
|
107
|
+
for (const m of STDOUT_CONSOLE)
|
|
108
|
+
Reflect.set(console, m, printer[m].bind(printer));
|
|
109
|
+
try {
|
|
110
|
+
return { settled: { status: 'fulfilled', value: await run() }, printed: chunks.join('') };
|
|
111
|
+
}
|
|
112
|
+
catch (reason) {
|
|
113
|
+
return { settled: { status: 'rejected', reason }, printed: chunks.join('') };
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
for (const [m, fn] of saved)
|
|
117
|
+
Reflect.set(console, m, fn);
|
|
118
|
+
stdout.write = write;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
60
121
|
async function callTool(session, params) {
|
|
61
122
|
const { manifest, invoke } = session;
|
|
62
123
|
const root = manifest.rootPath;
|
|
@@ -67,9 +128,22 @@ async function callTool(session, params) {
|
|
|
67
128
|
if (node === undefined)
|
|
68
129
|
return { error: { code: JSON_RPC_INVALID_PARAMS, message: `unknown tool "${name}"` } };
|
|
69
130
|
const args = (given['arguments'] ?? {});
|
|
70
|
-
const {
|
|
71
|
-
|
|
72
|
-
|
|
131
|
+
const { settled, printed } = await printedBy(async () => await invoke(argvOf(node, root, args)));
|
|
132
|
+
let text;
|
|
133
|
+
let isError;
|
|
134
|
+
if (settled.status === 'fulfilled') {
|
|
135
|
+
const { stdout, stderr, code } = settled.value;
|
|
136
|
+
text = stdout.trim() !== '' ? stdout.trim() : stderr.trim();
|
|
137
|
+
isError = code !== 0;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
text = settled.reason instanceof Error ? settled.reason.message : String(settled.reason);
|
|
141
|
+
isError = true;
|
|
142
|
+
}
|
|
143
|
+
const content = [{ type: 'text', text }];
|
|
144
|
+
if (printed.trim() !== '')
|
|
145
|
+
content.push({ type: 'text', text: printed.trimEnd() });
|
|
146
|
+
return { result: { content, isError } };
|
|
73
147
|
}
|
|
74
148
|
async function handle(session, request) {
|
|
75
149
|
switch (request.method) {
|
|
@@ -91,14 +165,23 @@ export function startMcp(manifest, opts) {
|
|
|
91
165
|
invoke: opts.invoke,
|
|
92
166
|
serverInfo: { name: manifest.rootPath.join(' ') || 'burgee', version: manifest.version ?? '0.0.0' },
|
|
93
167
|
};
|
|
94
|
-
const reply = (body) =>
|
|
168
|
+
const reply = (body) => {
|
|
169
|
+
framing = true;
|
|
170
|
+
try {
|
|
171
|
+
opts.output.write(`${JSON.stringify({ jsonrpc: '2.0', ...body })}\n`);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
framing = false;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
95
177
|
const swap = (next, invoke) => {
|
|
96
178
|
session.manifest = next;
|
|
97
179
|
if (invoke !== undefined)
|
|
98
180
|
session.invoke = invoke;
|
|
99
181
|
reply({ method: 'notifications/tools/list_changed' });
|
|
100
182
|
};
|
|
101
|
-
const
|
|
183
|
+
const unhold = holdStdout();
|
|
184
|
+
const done = serve(session, opts.input, reply).finally(unhold);
|
|
102
185
|
return { done, swap };
|
|
103
186
|
}
|
|
104
187
|
export async function serveMcp(manifest, opts) {
|
package/dist/migrate.d.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Row } from './compat.js';
|
|
2
|
+
/** The npm package a specifier names: `@scope/name/x` → `@scope/name`, `name/x` → `name`. */
|
|
3
|
+
export declare function packageOf(specifier: string): string;
|
|
2
4
|
/**
|
|
3
|
-
* A2 — the whole mapping, as data.
|
|
5
|
+
* A2, A12 — the whole mapping, as data, and none of it typed here.
|
|
6
|
+
*
|
|
7
|
+
* Every drop-in the oracle grades **level** with its incumbent (D-137): the incumbent's own
|
|
8
|
+
* suite passes as many cases against the family's replacement as against the incumbent
|
|
9
|
+
* itself, in the same harness. That is commander and yargs, and chalk, ora, string-width,
|
|
10
|
+
* cross-spawn, signal-exit and the rest — `compat.ts` holds the list and a lock re-derives it
|
|
11
|
+
* from `compat-oracle`. A drop-in that is not level yet is reported, never rewritten.
|
|
4
12
|
*
|
|
5
13
|
* Whole specifiers only. `burgee/commander` is not a key, which is what makes a second run
|
|
6
14
|
* over an already-migrated tree a no-op and lets the command declare `effects: 'idempotent'`.
|
|
15
|
+
* `yargs/yargs` is the one key the oracle does not name: yargs documents it as an entry,
|
|
16
|
+
* and it is the same module as `yargs`.
|
|
7
17
|
*/
|
|
8
18
|
export declare const MAPPING: Readonly<Record<string, string>>;
|
|
9
|
-
/** The packages a project depends on that this command
|
|
10
|
-
export declare const HOSTS: readonly [
|
|
19
|
+
/** The packages a project depends on that this command rewrites (A1). */
|
|
20
|
+
export declare const HOSTS: readonly string[];
|
|
21
|
+
/** The leading number of a version or a range — `^3.0.7` is 3, `>=18` is 18; `undefined` for `*` or a tag. */
|
|
22
|
+
export declare function majorOf(version: string): number | undefined;
|
|
11
23
|
/**
|
|
12
24
|
* Every name each target exports — values and types alike — so a rewrite that moves
|
|
13
25
|
* `import { Argv } from 'yargs'` can first ask whether `burgee/yargs` has an `Argv`.
|
|
@@ -24,8 +36,24 @@ export declare const FACADE_EXPORTS: Readonly<Record<string, readonly string[]>>
|
|
|
24
36
|
*
|
|
25
37
|
* `unknown-export` is a named import the target does not export — rewriting it would turn a
|
|
26
38
|
* working import into TS2305 or a `SyntaxError` at load, so the file stays as it was.
|
|
39
|
+
*
|
|
40
|
+
* `require-of-default` is a `require()` whose two sides hand back different kinds of value.
|
|
41
|
+
* `require()` of an ES module returns its namespace, so `require('cross-spawn')` — a function —
|
|
42
|
+
* rewritten to a target with a default and no `'module.exports'` would make `spawn(...)` throw.
|
|
43
|
+
* A `require('chalk')` of chalk 6, which is ESM only, already returns a namespace, and moves to
|
|
44
|
+
* a target that does too ({@link REQUIRE_NAMESPACE}, A29). Where the shapes differ, the file
|
|
45
|
+
* stays on the incumbent, where it works.
|
|
27
46
|
*/
|
|
28
|
-
export type RefusalReason = 'deep-import' | 'non-literal-specifier' | 'unknown-export';
|
|
47
|
+
export type RefusalReason = 'deep-import' | 'non-literal-specifier' | 'unknown-export' | 'require-of-default';
|
|
48
|
+
/**
|
|
49
|
+
* Incumbents whose own `require()` already returns an ES namespace — they ship ESM only and
|
|
50
|
+
* export no `'module.exports'`, so `require('chalk').default` is how a CommonJS caller of
|
|
51
|
+
* chalk 6 reaches it. Moving that line to a target that also hands back its namespace is
|
|
52
|
+
* exact (A29). Derived, not typed: `migrate-require.test.ts` holds this list equal to what
|
|
53
|
+
* Node's own `require()` returns for every installed incumbent in `MAPPING`, so a name here
|
|
54
|
+
* is a measurement and an incumbent that is not installed is left out, and refused as before.
|
|
55
|
+
*/
|
|
56
|
+
export declare const REQUIRE_NAMESPACE: readonly string[];
|
|
29
57
|
export interface Refusal {
|
|
30
58
|
/** Relative to the directory being migrated, with forward slashes on every platform. */
|
|
31
59
|
file: string;
|
|
@@ -64,6 +92,18 @@ export interface Detection {
|
|
|
64
92
|
/** Hosts a source file actually imports — used. These two disagree more often than not. */
|
|
65
93
|
imported: string[];
|
|
66
94
|
}
|
|
95
|
+
/** An incumbent the project has on a different major from the one graded — left alone (A12). */
|
|
96
|
+
export interface OffMajor {
|
|
97
|
+
from: string;
|
|
98
|
+
/** The installed version, or the declared range when nothing is installed here. */
|
|
99
|
+
found: string;
|
|
100
|
+
graded: string;
|
|
101
|
+
}
|
|
102
|
+
/** A declared incumbent with a graded drop-in that is not level yet (A12). */
|
|
103
|
+
export interface NotLevel extends Row {
|
|
104
|
+
from: string;
|
|
105
|
+
to: string;
|
|
106
|
+
}
|
|
67
107
|
export interface MigrationReport {
|
|
68
108
|
files: number;
|
|
69
109
|
imports: number;
|
|
@@ -72,14 +112,22 @@ export interface MigrationReport {
|
|
|
72
112
|
/** Type-only imports left pointing at the incumbent, each with the note that says why. */
|
|
73
113
|
kept: Kept[];
|
|
74
114
|
detected: Detection;
|
|
115
|
+
/** `add`: the family packages the rewritten imports now name, which the project must depend on. */
|
|
75
116
|
dependencies: {
|
|
76
117
|
before: string[];
|
|
77
118
|
removable: string[];
|
|
78
119
|
after: number;
|
|
120
|
+
add: string[];
|
|
79
121
|
};
|
|
80
|
-
graded: (
|
|
122
|
+
graded: (Row & {
|
|
81
123
|
host: string;
|
|
82
124
|
})[];
|
|
125
|
+
/** Declared incumbents whose drop-in is graded but not level — left alone, with the grade that says why. */
|
|
126
|
+
partial: NotLevel[];
|
|
127
|
+
/** Incumbents on a major the oracle did not grade — left alone, never rewritten onto an API they do not use. */
|
|
128
|
+
offMajor: OffMajor[];
|
|
129
|
+
/** The install and uninstall to run next, for the package manager the lockfile names; `''` when there is none. */
|
|
130
|
+
next: string;
|
|
83
131
|
dryRun: boolean;
|
|
84
132
|
/** N7 — an idempotent command says whether it changed anything; silence is what an agent misreads. */
|
|
85
133
|
changed: boolean;
|
|
@@ -97,6 +145,8 @@ interface Site {
|
|
|
97
145
|
* — the import clause, which is all `bindingsOf` needs. Absent for the other three positions.
|
|
98
146
|
*/
|
|
99
147
|
clause?: string[];
|
|
148
|
+
/** `require('x')`: CommonJS receives the target's whole namespace, not its default export. */
|
|
149
|
+
require?: true;
|
|
100
150
|
}
|
|
101
151
|
interface Scan {
|
|
102
152
|
sites: Site[];
|
|
@@ -154,7 +204,7 @@ export declare function bindingsOf(clause: readonly string[]): {
|
|
|
154
204
|
* source is returned unchanged the moment there is a refusal in it: the unit of success is
|
|
155
205
|
* the file, so the worst case is *nothing changed here, and here is why* (D-051).
|
|
156
206
|
*/
|
|
157
|
-
export declare function rewriteSource(source: string): Rewrite;
|
|
207
|
+
export declare function rewriteSource(source: string, skip?: ReadonlySet<string>): Rewrite;
|
|
158
208
|
/** Every source file under `dir`, relative and slash-separated so a report reads the same everywhere. */
|
|
159
209
|
export declare function sourceFiles(dir: string, at?: string, found?: string[]): string[];
|
|
160
210
|
/**
|