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/index.d.ts
CHANGED
|
@@ -4,15 +4,40 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The public entry, and only that. The execution core lives in execute.ts so a
|
|
6
6
|
* façade can import it without pulling this barrel.
|
|
7
|
+
*
|
|
8
|
+
* ## What is not here, and why (D-093, reversed on a measurement)
|
|
9
|
+
*
|
|
10
|
+
* This barrel used to re-export the value half of `help.ts`, `mcp.ts`, `schema.ts`,
|
|
11
|
+
* `plugin.ts`, `manifest.ts` and `seniority/precedence` as a convenience. A re-export is
|
|
12
|
+
* not free: it makes those modules live for every consumer of `burgee`, whether or not
|
|
13
|
+
* anything reads them. `execute.ts` loads each one behind an `await import()`, so the only
|
|
14
|
+
* thing keeping them on the startup path was this file.
|
|
15
|
+
*
|
|
16
|
+
* Measured 2026-09-21 with `--splitting --outdir` over the transitive closure of `import`
|
|
17
|
+
* statements — the initial load a consumer actually pays:
|
|
18
|
+
*
|
|
19
|
+
* - `import { run } from 'burgee'` — **44,663 bytes**
|
|
20
|
+
* - the same program against `execute.ts` directly — **31,047 bytes**
|
|
21
|
+
* - cold start, `burgee ÷ cac` — **2.113 → 1.717**
|
|
22
|
+
*
|
|
23
|
+
* D-093 declined this split on a cold-start argument it did not have the number for. The
|
|
24
|
+
* number says 13,616 bytes and 19% of startup, so the split lands: every value moved here
|
|
25
|
+
* has a subpath of its own (`burgee/help`, `burgee/mcp`, `burgee/schema`, `burgee/plugin`,
|
|
26
|
+
* `burgee/config`), which is where a program that wants it should say so.
|
|
27
|
+
*
|
|
28
|
+
* **Every `type` stays.** A type re-export is erased and costs a consumer nothing, so the
|
|
29
|
+
* whole type surface is still importable from `burgee` and no typed program has to move.
|
|
7
30
|
*/
|
|
8
31
|
export { ExitCode, isExitCode, type ExitCode as ExitCodeValue } from './exit-code.js';
|
|
9
32
|
export { defineCommand, defineProgram, execute, resolveCommand, run, runCommand, sharedOptions, type AnyCommand, type Command, type CommandContext, type InferOptions, type OptionSpecs, type Program, type RunOptions, type RunResult, } from './execute.js';
|
|
10
33
|
export { checkCommand, checkDefinition } from './definition.js';
|
|
11
|
-
export { camel, kebab, UsageError } from './validate.js';
|
|
34
|
+
export { AuthError, camel, kebab, UsageError } from './validate.js';
|
|
12
35
|
export { AGENT_PROBES, detectAgent, type AgentProbe, type Detection } from './agent.js';
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export
|
|
36
|
+
export type { HelpOptions, HelpTheme, HelpToken } from './help.js';
|
|
37
|
+
export type { Invoke, ServeOptions, Tool, ToolAnnotations } from './mcp.js';
|
|
38
|
+
export type { Candidate, Layers, Provenance, Resolution, Source } from 'seniority/precedence';
|
|
39
|
+
export type { CommandSchema, JsonSchema, ProgramSchema, SchemaSummary } from './schema.js';
|
|
40
|
+
export type { PluginErrorCode } from './plugin.js';
|
|
41
|
+
export type {
|
|
42
|
+
/** The class itself lives at `burgee/schema`; the *type* stays here so `defineProgram`'s return type is nameable. */
|
|
43
|
+
Manifest, ArgumentSpec, CommandNode, Effects, Example, Hook, LazyModule, OptionSpec, ActionRequiredSpec, Plugin, Relation, RunContext, StandardResult, StandardSchemaV1, } from './manifest.js';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
export { ExitCode, isExitCode } from './exit-code.js';
|
|
2
2
|
export { defineCommand, defineProgram, execute, resolveCommand, run, runCommand, sharedOptions, } from './execute.js';
|
|
3
3
|
export { checkCommand, checkDefinition } from './definition.js';
|
|
4
|
-
export { camel, kebab, UsageError } from './validate.js';
|
|
4
|
+
export { AuthError, camel, kebab, UsageError } from './validate.js';
|
|
5
5
|
export { AGENT_PROBES, detectAgent } from './agent.js';
|
|
6
|
-
export { renderHelp } from './help.js';
|
|
7
|
-
export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf } from './mcp.js';
|
|
8
|
-
export { ConfigError, envName, explain, resolve, screaming } from 'seniority/precedence';
|
|
9
|
-
export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf } from './schema.js';
|
|
10
|
-
export { CONTRACT, definePlugin, PluginError } from './plugin.js';
|
|
11
|
-
export { Manifest, } from './manifest.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf } from './mcp.js';
|
package/dist/mcp.d.ts
CHANGED
|
@@ -2,9 +2,15 @@ import { type CommandNode, type Effects, type Manifest } from './manifest.js';
|
|
|
2
2
|
import { type JsonSchema } from './schema.js';
|
|
3
3
|
export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
4
4
|
export interface ToolAnnotations {
|
|
5
|
-
readOnlyHint
|
|
6
|
-
idempotentHint
|
|
7
|
-
destructiveHint
|
|
5
|
+
readOnlyHint?: boolean;
|
|
6
|
+
idempotentHint?: boolean;
|
|
7
|
+
destructiveHint?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* `'undeclared'`, and only ever that (G1). It appears on a command whose author said
|
|
10
|
+
* nothing — every commander and yargs command that did not call `.effects()` — and never
|
|
11
|
+
* beside a hint, because a hint is what a declaration produces.
|
|
12
|
+
*/
|
|
13
|
+
effects?: 'undeclared';
|
|
8
14
|
}
|
|
9
15
|
export interface Tool {
|
|
10
16
|
name: string;
|
|
@@ -18,20 +24,34 @@ export type Invoke = (argv: string[]) => Promise<{
|
|
|
18
24
|
stderr: string;
|
|
19
25
|
code: number;
|
|
20
26
|
}>;
|
|
21
|
-
/**
|
|
22
|
-
|
|
27
|
+
/**
|
|
28
|
+
* MCP's hints, from the declared effects. `destructiveHint` is only ever false by declaration.
|
|
29
|
+
*
|
|
30
|
+
* No declaration returns no hints (G1). That is not a gap: MCP defines a default for each of
|
|
31
|
+
* the three — `readOnlyHint: false`, `destructiveHint: true`, `idempotentHint: false` — so an
|
|
32
|
+
* absent hint already reads as *assume the worst*, in the client's own vocabulary and without
|
|
33
|
+
* burgee inventing a value it has no basis for. `effects: 'undeclared'` is the positive half:
|
|
34
|
+
* this command is not a `read_only` one, and it is not a `withheld` one either — nobody said.
|
|
35
|
+
*/
|
|
36
|
+
export declare function annotationsOf(effects?: Effects): ToolAnnotations;
|
|
23
37
|
/** `config get` → `config_get`: MCP tool names are `[a-zA-Z0-9_-]`. */
|
|
24
38
|
export declare const toolName: (node: CommandNode, root: string[]) => string;
|
|
25
39
|
/**
|
|
26
|
-
* The tool list: every runnable, visible command
|
|
40
|
+
* The tool list: every runnable, visible command an author has not withheld.
|
|
41
|
+
*
|
|
42
|
+
* One word is absent and one is not, and until 2026-09-21 they were the same thing. An
|
|
43
|
+
* author who wrote `'withheld'` thought about it and said no; that is what the word is for
|
|
44
|
+
* and it still means absent. An author who wrote nothing — which on burgee's own API
|
|
45
|
+
* `checkCommand` refuses, and which **every** command built through the commander or yargs
|
|
46
|
+
* façade is, because neither incumbent has a notion of effects and neither can be made to
|
|
47
|
+
* acquire one without breaking the suites that grade the façades — was treated the same way,
|
|
48
|
+
* so a migrated user's whole program was silently not a tool.
|
|
27
49
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* without breaking the suites that grade the façades. The filter treats both as *not a
|
|
34
|
-
* tool*, which is the same conservative reading it always had.
|
|
50
|
+
* Reading silence as refusal was conservative and it was also the thing standing between the
|
|
51
|
+
* product and its own pitch. Absent-from-the-list is strictly worse for the caller than
|
|
52
|
+
* present-with-honest-annotations: an agent that cannot see a command cannot decide about it,
|
|
53
|
+
* and cannot ask. So an undeclared command is listed and says so — see {@link annotationsOf}
|
|
54
|
+
* for why it carries no hints rather than a reassuring default.
|
|
35
55
|
*/
|
|
36
56
|
export declare function toolsOf(manifest: Manifest): Tool[];
|
|
37
57
|
/** A tool call's arguments back into argv: the command, its options, `--json`, then positionals in declared order. */
|
package/dist/mcp.js
CHANGED
|
@@ -6,6 +6,8 @@ const JSON_RPC_INVALID_REQUEST = -32600;
|
|
|
6
6
|
const JSON_RPC_METHOD_NOT_FOUND = -32601;
|
|
7
7
|
const JSON_RPC_INVALID_PARAMS = -32602;
|
|
8
8
|
export function annotationsOf(effects) {
|
|
9
|
+
if (effects === undefined)
|
|
10
|
+
return { effects: 'undeclared' };
|
|
9
11
|
return {
|
|
10
12
|
readOnlyHint: effects === 'read_only',
|
|
11
13
|
idempotentHint: effects !== 'non_idempotent',
|
|
@@ -21,7 +23,7 @@ function describe(node) {
|
|
|
21
23
|
}
|
|
22
24
|
export function toolsOf(manifest) {
|
|
23
25
|
return runnable(manifest)
|
|
24
|
-
.filter((c) => c.effects !==
|
|
26
|
+
.filter((c) => c.effects !== WITHHELD)
|
|
25
27
|
.map((c) => ({ name: toolName(c, manifest.rootPath), description: describe(c), inputSchema: inputSchemaOf(c), annotations: annotationsOf(c.effects) }));
|
|
26
28
|
}
|
|
27
29
|
function optionArgs(node, args) {
|
|
@@ -59,8 +61,7 @@ async function callTool(session, params) {
|
|
|
59
61
|
const given = params ?? {};
|
|
60
62
|
const raw = given['name'];
|
|
61
63
|
const name = typeof raw === 'string' ? raw : '';
|
|
62
|
-
const
|
|
63
|
-
const node = exposed.has(name) ? runnable(manifest).find((c) => toolName(c, root) === name) : undefined;
|
|
64
|
+
const node = runnable(manifest).find((c) => c.effects !== WITHHELD && toolName(c, root) === name);
|
|
64
65
|
if (node === undefined)
|
|
65
66
|
return { error: { code: JSON_RPC_INVALID_PARAMS, message: `unknown tool "${name}"` } };
|
|
66
67
|
const args = (given['arguments'] ?? {});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AnyFlag, type Options } from './types.js';
|
|
2
|
+
/** Where a command run's parent arguments end and the child's begin. */
|
|
3
|
+
export interface Split {
|
|
4
|
+
parent: string[];
|
|
5
|
+
input: string[];
|
|
6
|
+
command?: string;
|
|
7
|
+
unknownCommand?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Every flag name a caller may write, and the canonical name each maps to. */
|
|
10
|
+
export declare function aliasMap(flags: Record<string, AnyFlag>): Record<string, string[]>;
|
|
11
|
+
export declare function typeofDefault(value: unknown): 'string' | 'boolean' | 'number' | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Where the parent's arguments end and the command's begin.
|
|
14
|
+
*
|
|
15
|
+
* The first token the parser reads as positional is the command word; everything after it in
|
|
16
|
+
* the *raw* argv is the child's, unparsed. `--` is not a fence here — the suite states that
|
|
17
|
+
* `-- --unknown run` reports `Unknown command: --unknown`, so a post-separator word is a
|
|
18
|
+
* candidate command like any other.
|
|
19
|
+
*/
|
|
20
|
+
export declare function splitAtCommand(argv: string[], parserOptions: Record<string, unknown>, opts: Options): Split;
|
|
21
|
+
/** stderr and exit 2 — how meow ends a run the flags do not support. */
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import parser, { decamelize } from '../yargs-parser.js';
|
|
2
|
+
export function aliasMap(flags) {
|
|
3
|
+
const out = {};
|
|
4
|
+
for (const [name, spec] of Object.entries(flags)) {
|
|
5
|
+
const names = [];
|
|
6
|
+
if (typeof spec.shortFlag === 'string')
|
|
7
|
+
names.push(spec.shortFlag);
|
|
8
|
+
if (typeof spec.alias === 'string')
|
|
9
|
+
names.push(spec.alias);
|
|
10
|
+
for (const extra of spec.aliases ?? [])
|
|
11
|
+
names.push(extra);
|
|
12
|
+
const decamelized = decamelize(name, '-');
|
|
13
|
+
if (decamelized !== name)
|
|
14
|
+
names.push(decamelized);
|
|
15
|
+
if (names.length > 0)
|
|
16
|
+
Object.defineProperty(out, name, { value: names, writable: true, enumerable: true, configurable: true });
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
export function typeofDefault(value) {
|
|
21
|
+
if (typeof value === 'boolean')
|
|
22
|
+
return 'boolean';
|
|
23
|
+
if (typeof value === 'number')
|
|
24
|
+
return 'number';
|
|
25
|
+
if (typeof value === 'string')
|
|
26
|
+
return 'string';
|
|
27
|
+
if (Array.isArray(value))
|
|
28
|
+
return typeofDefault(value[0]);
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
export function splitAtCommand(argv, parserOptions, opts) {
|
|
32
|
+
const probe = parser.detailed(argv, parserOptions);
|
|
33
|
+
const first = probe.argv['_'][0];
|
|
34
|
+
if (first === undefined)
|
|
35
|
+
return { parent: argv, input: [] };
|
|
36
|
+
const word = String(first);
|
|
37
|
+
const at = argv.indexOf(word);
|
|
38
|
+
const parent = at === -1 ? argv : argv.slice(0, at);
|
|
39
|
+
const input = at === -1 ? [] : argv.slice(at + 1);
|
|
40
|
+
if (!(opts.commands ?? []).includes(word))
|
|
41
|
+
return { parent, input, unknownCommand: word };
|
|
42
|
+
return { parent, input, command: word };
|
|
43
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Options } from './types.js';
|
|
2
|
+
/** The nearest `package.json` above the caller's module, which is what `importMeta` is for. */
|
|
3
|
+
/** The nearest `package.json` above the caller's module, which is what `importMeta` is for. */
|
|
4
|
+
export declare function readPackageUp(importMeta: ImportMeta | undefined): Record<string, unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* meow's help block.
|
|
7
|
+
*
|
|
8
|
+
* Indented by `helpIndent` (2 by default) — but only when there is more than one line to
|
|
9
|
+
* indent, which is why `{description: false, help: 'single line'}` comes back flush. The
|
|
10
|
+
* suite states both shapes and they disagree about the indent, not about the text.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* meow's help block.
|
|
14
|
+
*
|
|
15
|
+
* Indented by `helpIndent` (2 by default) — but only when there is more than one line to
|
|
16
|
+
* indent, which is why `{description: false, help: 'single line'}` comes back flush. The
|
|
17
|
+
* suite states both shapes and they disagree about the indent, not about the text.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildHelp(options: Options, pkg: Record<string, unknown>): string;
|
|
20
|
+
/** `common-tags`' `stripIndent`, for the template literals meow's callers write help in. */
|
|
21
|
+
/** `common-tags`' `stripIndent`, for the template literals meow's callers write help in. */
|
|
22
|
+
export declare function stripIndent(text: string): string;
|
|
23
|
+
/** Every flag name a caller may write, and the canonical name each maps to. */
|
|
24
|
+
/**
|
|
25
|
+
* The half of `normalize-package-data` meow's callers can see.
|
|
26
|
+
*
|
|
27
|
+
* `bin` as a string becomes `{ [name]: path }` — the suite reads `cli.pkg.bin['browser-sync']`
|
|
28
|
+
* — and an absent `version` becomes `''`. A copy, because `pkg normalization is lazy` asserts
|
|
29
|
+
* the object the caller passed in is untouched.
|
|
30
|
+
*/
|
|
31
|
+
export declare function normalizePackage(pkg: Record<string, unknown>): Record<string, unknown>;
|
|
32
|
+
/** meow renames the process after the binary it is, which `ps` and a crash report both read. */
|
|
33
|
+
/** meow renames the process after the binary it is, which `ps` and a crash report both read. */
|
|
34
|
+
export declare function setProcessTitle(pkg: Record<string, unknown>): void;
|
|
35
|
+
/** An own property under a key the caller chose — never the prototype setter. */
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { host } from '../runtime.js';
|
|
5
|
+
const indent = (text, spaces) => text.replace(/^(?!\s*$)/gmu, ' '.repeat(spaces));
|
|
6
|
+
export function readPackageUp(importMeta) {
|
|
7
|
+
if (importMeta?.url === undefined)
|
|
8
|
+
return {};
|
|
9
|
+
let dir = dirname(fileURLToPath(importMeta.url));
|
|
10
|
+
for (let depth = 0; depth < 64; depth += 1) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
const up = dirname(dir);
|
|
16
|
+
if (up === dir)
|
|
17
|
+
break;
|
|
18
|
+
dir = up;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
export function buildHelp(options, pkg) {
|
|
24
|
+
const description = options.description === false ? '' : (options.description ?? pkg['description'] ?? '');
|
|
25
|
+
const body = options.help === false ? '' : stripIndent(options.help ?? '');
|
|
26
|
+
const parts = [description, body].filter((p) => p !== '');
|
|
27
|
+
if (parts.length === 0)
|
|
28
|
+
return '';
|
|
29
|
+
const text = parts.join('\n\n');
|
|
30
|
+
const width = options.helpIndent ?? 2;
|
|
31
|
+
return `\n${text.includes('\n') ? indent(text, width) : text}\n`;
|
|
32
|
+
}
|
|
33
|
+
export function stripIndent(text) {
|
|
34
|
+
const lines = text.split('\n');
|
|
35
|
+
const widths = lines.filter((l) => l.trim() !== '').map((l) => (/^(\s*)/u.exec(l)?.[1] ?? '').length);
|
|
36
|
+
const smallest = widths.length === 0 ? 0 : Math.min(...widths);
|
|
37
|
+
return lines
|
|
38
|
+
.map((l) => l.slice(smallest))
|
|
39
|
+
.join('\n')
|
|
40
|
+
.trim();
|
|
41
|
+
}
|
|
42
|
+
export function normalizePackage(pkg) {
|
|
43
|
+
const out = { ...pkg };
|
|
44
|
+
const name = typeof out['name'] === 'string' ? out['name'].replace(/^@[^/]+\//u, '') : undefined;
|
|
45
|
+
if (typeof out['bin'] === 'string' && name !== undefined)
|
|
46
|
+
out['bin'] = Object.fromEntries([[name, out['bin']]]);
|
|
47
|
+
if (out['version'] === undefined)
|
|
48
|
+
out['version'] = '';
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
export function setProcessTitle(pkg) {
|
|
52
|
+
const bin = pkg['bin'];
|
|
53
|
+
const first = typeof bin === 'object' && bin !== null ? Object.keys(bin)[0] : undefined;
|
|
54
|
+
const name = typeof pkg['name'] === 'string' ? pkg['name'].replace(/^@[^/]+\//u, '') : undefined;
|
|
55
|
+
const title = first ?? name;
|
|
56
|
+
if (title !== undefined && title !== '')
|
|
57
|
+
host.setTitle(title);
|
|
58
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `burgee/meow` — the option and result shapes meow's callers write against.
|
|
3
|
+
*
|
|
4
|
+
* Kept beside the implementation rather than inside it because three files read them and a
|
|
5
|
+
* façade's contract is the part most worth seeing on its own.
|
|
6
|
+
*/
|
|
7
|
+
export interface AnyFlag {
|
|
8
|
+
type?: 'string' | 'boolean' | 'number';
|
|
9
|
+
alias?: string;
|
|
10
|
+
aliases?: string[];
|
|
11
|
+
shortFlag?: string;
|
|
12
|
+
default?: unknown;
|
|
13
|
+
isRequired?: boolean | ((flags: Record<string, unknown>, input: string[]) => boolean);
|
|
14
|
+
isMultiple?: boolean;
|
|
15
|
+
choices?: unknown[];
|
|
16
|
+
}
|
|
17
|
+
export interface Options {
|
|
18
|
+
importMeta?: ImportMeta;
|
|
19
|
+
argv?: readonly string[];
|
|
20
|
+
description?: string | false;
|
|
21
|
+
help?: string | false;
|
|
22
|
+
version?: string | false;
|
|
23
|
+
autoHelp?: boolean;
|
|
24
|
+
autoVersion?: boolean;
|
|
25
|
+
helpIndent?: number;
|
|
26
|
+
flags?: Record<string, AnyFlag>;
|
|
27
|
+
pkg?: Record<string, unknown>;
|
|
28
|
+
input?: unknown;
|
|
29
|
+
inferType?: boolean;
|
|
30
|
+
booleanDefault?: boolean | null | undefined;
|
|
31
|
+
hardRejection?: boolean;
|
|
32
|
+
allowUnknownFlags?: boolean;
|
|
33
|
+
commands?: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface Result {
|
|
36
|
+
input: string[];
|
|
37
|
+
flags: Record<string, unknown>;
|
|
38
|
+
unnormalizedFlags: Record<string, unknown>;
|
|
39
|
+
pkg: Record<string, unknown>;
|
|
40
|
+
help: string;
|
|
41
|
+
version: string;
|
|
42
|
+
command?: string;
|
|
43
|
+
showHelp: (code?: number) => never;
|
|
44
|
+
showVersion: () => void;
|
|
45
|
+
}
|
|
46
|
+
/** An own property under a key the caller chose — never the prototype setter. */
|
|
47
|
+
export declare function own(target: Record<string, unknown>, key: string, value: unknown): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type AnyFlag, type Options } from './types.js';
|
|
2
|
+
export declare function validateFlags(flags: Record<string, AnyFlag>): void;
|
|
3
|
+
/** meow refuses to guess where the caller's `package.json` is. */
|
|
4
|
+
/** meow refuses to guess where the caller's `package.json` is. */
|
|
5
|
+
export declare function requireImportMeta(importMeta: ImportMeta | undefined): void;
|
|
6
|
+
/** meow, over burgee's parser. */
|
|
7
|
+
/** `commands` is an array of bare words, and meow says so in three different sentences. */
|
|
8
|
+
export declare function validateCommands(commands: string[] | undefined): void;
|
|
9
|
+
/**
|
|
10
|
+
* Where the parent's arguments end and the command's begin.
|
|
11
|
+
*
|
|
12
|
+
* The first token the parser reads as positional is the command word; everything after it in
|
|
13
|
+
* the *raw* argv is the child's, unparsed. `--` is not a fence here — the suite states that
|
|
14
|
+
* `-- --unknown run` reports `Unknown command: --unknown`, so a post-separator word is a
|
|
15
|
+
* candidate command like any other.
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkChoices(specs: Record<string, AnyFlag>, flags: Record<string, unknown>): void;
|
|
18
|
+
export declare function checkRequired(specs: Record<string, AnyFlag>, flags: Record<string, unknown>, input: string[]): void;
|
|
19
|
+
export declare function checkUnknown(specs: Record<string, AnyFlag>, parsedFlags: Record<string, unknown>, argv: string[], opts: Options): void;
|
|
20
|
+
/** `commands` is an array of bare words, and meow says so in three different sentences. */
|
|
21
|
+
/** meow's `input` option may demand positionals, as a boolean or as a predicate. */
|
|
22
|
+
export declare function checkInput(opts: Options, input: string[], flags: Record<string, unknown>): void;
|
|
23
|
+
/** A flag declared once may be given once — the parser collects repeats into an array. */
|
|
24
|
+
/** A flag declared once may be given once — the parser collects repeats into an array. */
|
|
25
|
+
export declare function checkSetOnce(specs: Record<string, AnyFlag>, parsed: Record<string, unknown>): void;
|
|
26
|
+
/**
|
|
27
|
+
* The half of `normalize-package-data` meow's callers can see.
|
|
28
|
+
*
|
|
29
|
+
* `bin` as a string becomes `{ [name]: path }` — the suite reads `cli.pkg.bin['browser-sync']`
|
|
30
|
+
* — and an absent `version` becomes `''`. A copy, because `pkg normalization is lazy` asserts
|
|
31
|
+
* the object the caller passed in is untouched.
|
|
32
|
+
*/
|
|
33
|
+
/** stderr and exit 2 — how meow ends a run the flags do not support. */
|
|
34
|
+
export declare function reportAndExit(message: string): never;
|
|
35
|
+
/** meow's `input` option may demand positionals, as a boolean or as a predicate. */
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { ExitCode } from '../exit-code.js';
|
|
3
|
+
import { host } from '../runtime.js';
|
|
4
|
+
import { camelCase, decamelize } from '../yargs-parser.js';
|
|
5
|
+
export function validateFlags(flags) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
const kebab = Object.keys(flags).filter((name) => name.includes('-') && name !== '--');
|
|
8
|
+
if (kebab.length > 0)
|
|
9
|
+
errors.push(`Flag keys may not contain '-'. Invalid flags: ${kebab.map((k) => `\`${k}\``).join(', ')}`);
|
|
10
|
+
const renamed = Object.entries(flags).filter(([, spec]) => typeof spec.alias === 'string');
|
|
11
|
+
if (renamed.length > 0) {
|
|
12
|
+
errors.push(`The option \`alias\` has been renamed to \`shortFlag\`. The following flags need to be updated: ${renamed.map(([n]) => `\`--${n}\``).join(', ')}`);
|
|
13
|
+
}
|
|
14
|
+
const badChoices = Object.entries(flags).filter(([, spec]) => spec.choices !== undefined && !Array.isArray(spec.choices));
|
|
15
|
+
if (badChoices.length > 0) {
|
|
16
|
+
errors.push(`The option \`choices\` must be an array. Invalid flags: ${badChoices.map(([n]) => `\`--${n}\``).join(', ')}`);
|
|
17
|
+
}
|
|
18
|
+
for (const [name, spec] of Object.entries(flags)) {
|
|
19
|
+
if (spec.default === undefined || spec.type === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
const values = Array.isArray(spec.default) ? spec.default : [spec.default];
|
|
22
|
+
const wrong = values.find((v) => typeof v !== spec.type);
|
|
23
|
+
if (wrong !== undefined)
|
|
24
|
+
errors.push(`Expected "${name}" default value to be of type "${String(spec.type)}", got "${typeof wrong}"`);
|
|
25
|
+
}
|
|
26
|
+
if (errors.length > 0)
|
|
27
|
+
throw new Error(`${errors.join('\n')}`);
|
|
28
|
+
}
|
|
29
|
+
export function requireImportMeta(importMeta) {
|
|
30
|
+
if (importMeta === undefined || typeof importMeta !== 'object' || typeof importMeta.url !== 'string') {
|
|
31
|
+
throw new TypeError('The `importMeta` option is required. Its value must be `import.meta`.');
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
fileURLToPath(importMeta.url);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new TypeError('The `importMeta` option is required. Its value must be `import.meta`.');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function validateCommands(commands) {
|
|
41
|
+
if (commands === undefined)
|
|
42
|
+
return;
|
|
43
|
+
if (!Array.isArray(commands))
|
|
44
|
+
throw new TypeError('The `commands` option must be an array of strings.');
|
|
45
|
+
if (commands.length === 0)
|
|
46
|
+
throw new TypeError('The `commands` option must contain at least one command.');
|
|
47
|
+
const bad = commands.some((c) => typeof c !== 'string' || c === '' || /\s/u.test(c) || c.startsWith('-'));
|
|
48
|
+
if (bad)
|
|
49
|
+
throw new TypeError('The `commands` option must be an array of non-empty strings without whitespace that do not start with `-`.');
|
|
50
|
+
}
|
|
51
|
+
export function checkChoices(specs, flags) {
|
|
52
|
+
const errors = [];
|
|
53
|
+
const badDefaults = Object.entries(specs).filter(([, spec]) => {
|
|
54
|
+
if (spec.default === undefined || !Array.isArray(spec.choices))
|
|
55
|
+
return false;
|
|
56
|
+
return (Array.isArray(spec.default) ? spec.default : [spec.default]).some((d) => !spec.choices?.includes(d));
|
|
57
|
+
});
|
|
58
|
+
if (badDefaults.length > 0) {
|
|
59
|
+
throw new Error(`Each value of the option \`default\` must exist within the option \`choices\`. Invalid flags: ${badDefaults.map(([n]) => `\`--${n}\``).join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
62
|
+
if (spec.choices === undefined || !Array.isArray(spec.choices))
|
|
63
|
+
continue;
|
|
64
|
+
const label = `--${decamelize(name, '-')}`;
|
|
65
|
+
const allowed = `[${spec.choices.map((c) => `\`${String(c)}\``).join(', ')}]`;
|
|
66
|
+
const value = flags[name];
|
|
67
|
+
const required = typeof spec.isRequired === 'function' ? true : spec.isRequired === true;
|
|
68
|
+
if (value === undefined || value === '') {
|
|
69
|
+
if (required)
|
|
70
|
+
errors.push(`Flag \`${label}\` has no value. Value must be one of: ${allowed}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const values = Array.isArray(value) ? value : [value];
|
|
74
|
+
const bad = values.filter((v) => !spec.choices?.includes(v));
|
|
75
|
+
if (bad.length > 0) {
|
|
76
|
+
errors.push(`Unknown value${bad.length > 1 ? 's' : ''} for flag \`${label}\`: ${bad.map((b) => `\`${String(b)}\``).join(', ')}. Value must be one of: ${allowed}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (errors.length > 0)
|
|
80
|
+
throw new Error(`${errors.join('\n')}`);
|
|
81
|
+
}
|
|
82
|
+
export function checkRequired(specs, flags, input) {
|
|
83
|
+
const missing = [];
|
|
84
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
85
|
+
let required = spec.isRequired;
|
|
86
|
+
if (typeof spec.isRequired === 'function') {
|
|
87
|
+
required = spec.isRequired(flags, input);
|
|
88
|
+
if (typeof required !== 'boolean') {
|
|
89
|
+
throw new TypeError(`Return value for isRequired callback should be of type boolean, but ${typeof required} was returned.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (required !== true)
|
|
93
|
+
continue;
|
|
94
|
+
const value = flags[name];
|
|
95
|
+
if (value !== undefined && value !== '' && !(Array.isArray(value) && value.length === 0))
|
|
96
|
+
continue;
|
|
97
|
+
const short = typeof spec.shortFlag === 'string' ? `, -${spec.shortFlag}` : '';
|
|
98
|
+
missing.push(`--${decamelize(name, '-')}${short}`);
|
|
99
|
+
}
|
|
100
|
+
if (missing.length > 0) {
|
|
101
|
+
reportAndExit(`Missing required flag${missing.length > 1 ? 's' : ''}\n${missing.map((m) => `\t${m}`).join('\n')}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function checkUnknown(specs, parsedFlags, argv, opts) {
|
|
105
|
+
const known = new Set(['--']);
|
|
106
|
+
if (opts.autoHelp !== false)
|
|
107
|
+
known.add('help');
|
|
108
|
+
if (opts.autoVersion !== false)
|
|
109
|
+
known.add('version');
|
|
110
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
111
|
+
known.add(name);
|
|
112
|
+
known.add(decamelize(name, '-'));
|
|
113
|
+
if (typeof spec.shortFlag === 'string')
|
|
114
|
+
known.add(spec.shortFlag);
|
|
115
|
+
if (typeof spec.alias === 'string')
|
|
116
|
+
known.add(spec.alias);
|
|
117
|
+
}
|
|
118
|
+
const unknown = Object.keys(parsedFlags).filter((k) => !known.has(k) && !known.has(camelCase(k)) && !known.has(decamelize(k, '-')));
|
|
119
|
+
if (unknown.length > 0) {
|
|
120
|
+
const names = unknown.map((u) => (u.length === 1 ? `-${u}` : `--${decamelize(u, '-')}`));
|
|
121
|
+
reportAndExit(`Unknown flag${names.length > 1 ? 's' : ''}\n${names.join('\n')}`);
|
|
122
|
+
}
|
|
123
|
+
void argv;
|
|
124
|
+
}
|
|
125
|
+
export function checkInput(opts, input, flags) {
|
|
126
|
+
if (typeof opts.input !== 'object' || opts.input === null)
|
|
127
|
+
return;
|
|
128
|
+
const spec = opts.input;
|
|
129
|
+
const required = typeof spec.isRequired === 'function' ? spec.isRequired(flags, input) : spec.isRequired === true;
|
|
130
|
+
if (required && input.length === 0)
|
|
131
|
+
reportAndExit('Missing required input');
|
|
132
|
+
}
|
|
133
|
+
export function checkSetOnce(specs, parsed) {
|
|
134
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
135
|
+
if (spec.isMultiple === true || name === '--')
|
|
136
|
+
continue;
|
|
137
|
+
if (Array.isArray(parsed[name]))
|
|
138
|
+
throw new Error(`The flag --${decamelize(name, '-')} can only be set once.`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export function reportAndExit(message) {
|
|
142
|
+
host.stderr.write(`${message}\n`);
|
|
143
|
+
return host.exit(ExitCode.USAGE);
|
|
144
|
+
}
|
package/dist/meow.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Options, type Result } from './meow/types.js';
|
|
2
|
+
export { type AnyFlag, type Options, type Result } from './meow/types.js';
|
|
3
|
+
/** meow, over burgee's parser. */
|
|
4
|
+
declare function meow(helpText: string | Options, options?: Options): Result;
|
|
5
|
+
declare const meowFacade: typeof meow;
|
|
6
|
+
export default meowFacade;
|