burgee 0.0.0 → 0.3.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 +28 -1
- package/dist/agent.d.ts +19 -0
- package/dist/agent.js +25 -0
- package/dist/brand.d.ts +186 -0
- package/dist/brand.js +232 -0
- package/dist/cli.d.ts +62 -0
- package/dist/cli.js +121 -0
- package/dist/commander-argument.d.ts +22 -0
- package/dist/commander-argument.js +72 -0
- package/dist/commander-command.d.ts +355 -0
- package/dist/commander-command.js +1621 -0
- package/dist/commander-error.d.ts +10 -0
- package/dist/commander-error.js +20 -0
- package/dist/commander-help.d.ts +67 -0
- package/dist/commander-help.js +319 -0
- package/dist/commander-option.d.ts +58 -0
- package/dist/commander-option.js +164 -0
- package/dist/commander-suggest.d.ts +2 -0
- package/dist/commander-suggest.js +59 -0
- package/dist/commander.d.ts +18 -0
- package/dist/commander.js +12 -0
- package/dist/completions.d.ts +39 -0
- package/dist/completions.js +224 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +98 -0
- package/dist/contrast.d.ts +70 -0
- package/dist/contrast.js +93 -0
- package/dist/dev.d.ts +47 -0
- package/dist/dev.js +132 -0
- package/dist/execute.d.ts +118 -0
- package/dist/execute.js +469 -0
- package/dist/exit-code.d.ts +18 -0
- package/dist/exit-code.js +12 -0
- package/dist/help.d.ts +34 -0
- package/dist/help.js +187 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +9 -2
- package/dist/manifest.d.ts +207 -0
- package/dist/manifest.js +66 -0
- package/dist/mcp.d.ts +52 -0
- package/dist/mcp.js +124 -0
- package/dist/names.d.ts +5 -0
- package/dist/names.js +6 -0
- package/dist/pkg.d.ts +5 -0
- package/dist/pkg.js +21 -0
- package/dist/precedence.d.ts +55 -0
- package/dist/precedence.js +100 -0
- package/dist/runtime.d.ts +39 -0
- package/dist/runtime.js +24 -0
- package/dist/schema.d.ts +79 -0
- package/dist/schema.js +116 -0
- package/dist/testing-helpers.d.ts +79 -0
- package/dist/testing-helpers.js +145 -0
- package/dist/testing.d.ts +10 -0
- package/dist/testing.js +3 -0
- package/dist/validate.d.ts +27 -0
- package/dist/validate.js +133 -0
- package/dist/yargs-burgee.d.ts +50 -0
- package/dist/yargs-burgee.js +104 -0
- package/dist/yargs-cliui.d.ts +56 -0
- package/dist/yargs-cliui.js +421 -0
- package/dist/yargs-command.d.ts +82 -0
- package/dist/yargs-command.js +414 -0
- package/dist/yargs-completion.d.ts +41 -0
- package/dist/yargs-completion.js +271 -0
- package/dist/yargs-factory.d.ts +193 -0
- package/dist/yargs-factory.js +1606 -0
- package/dist/yargs-helpers.d.ts +6 -0
- package/dist/yargs-helpers.js +2 -0
- package/dist/yargs-middleware.d.ts +32 -0
- package/dist/yargs-middleware.js +81 -0
- package/dist/yargs-parser.d.ts +41 -0
- package/dist/yargs-parser.js +929 -0
- package/dist/yargs-shim.d.ts +54 -0
- package/dist/yargs-shim.js +84 -0
- package/dist/yargs-usage.d.ts +42 -0
- package/dist/yargs-usage.js +479 -0
- package/dist/yargs-utils.d.ts +33 -0
- package/dist/yargs-utils.js +209 -0
- package/dist/yargs-validation.d.ts +26 -0
- package/dist/yargs-validation.js +261 -0
- package/dist/yargs-y18n.d.ts +21 -0
- package/dist/yargs-y18n.js +117 -0
- package/dist/yargs.d.ts +6 -0
- package/dist/yargs.js +8 -0
- package/locales/be.json +46 -0
- package/locales/cs.json +51 -0
- package/locales/de.json +46 -0
- package/locales/en.json +55 -0
- package/locales/es.json +46 -0
- package/locales/fi.json +49 -0
- package/locales/fr.json +53 -0
- package/locales/he.json +55 -0
- package/locales/hi.json +49 -0
- package/locales/hu.json +46 -0
- package/locales/id.json +50 -0
- package/locales/it.json +46 -0
- package/locales/ja.json +51 -0
- package/locales/ka.json +55 -0
- package/locales/ko.json +49 -0
- package/locales/nb.json +44 -0
- package/locales/nl.json +49 -0
- package/locales/nn.json +44 -0
- package/locales/pirate.json +13 -0
- package/locales/pl.json +49 -0
- package/locales/pt.json +45 -0
- package/locales/pt_BR.json +48 -0
- package/locales/ru.json +51 -0
- package/locales/th.json +46 -0
- package/locales/tr.json +48 -0
- package/locales/uk_UA.json +51 -0
- package/locales/uz.json +52 -0
- package/locales/zh_CN.json +48 -0
- package/locales/zh_TW.json +51 -0
- package/package.json +62 -9
- package/dist/index.js.map +0 -1
package/dist/execute.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { detectAgent } from './agent.js';
|
|
4
|
+
import { ExitCode, isExitCode } from './exit-code.js';
|
|
5
|
+
import { renderHelp } from './help.js';
|
|
6
|
+
import { Manifest } from './manifest.js';
|
|
7
|
+
import { serveMcp } from './mcp.js';
|
|
8
|
+
import { camel, kebab } from './names.js';
|
|
9
|
+
import { nearestPackage } from './pkg.js';
|
|
10
|
+
import { ConfigError, explain, resolve as resolveLayers } from './precedence.js';
|
|
11
|
+
import { commandSchemaOf, schemaOf, summaryOf } from './schema.js';
|
|
12
|
+
import { checkDefinition, checkRelations, coerce, UsageError } from './validate.js';
|
|
13
|
+
function helpFields(c) {
|
|
14
|
+
const node = {};
|
|
15
|
+
if (c.description !== undefined)
|
|
16
|
+
node.description = c.description;
|
|
17
|
+
if (c.summary !== undefined)
|
|
18
|
+
node.summary = c.summary;
|
|
19
|
+
if (c.arguments !== undefined)
|
|
20
|
+
node.arguments = c.arguments;
|
|
21
|
+
if (c.examples !== undefined)
|
|
22
|
+
node.examples = c.examples;
|
|
23
|
+
if (c.group !== undefined)
|
|
24
|
+
node.group = c.group;
|
|
25
|
+
if (c.epilogue !== undefined)
|
|
26
|
+
node.epilogue = c.epilogue;
|
|
27
|
+
if (c.hidden !== undefined)
|
|
28
|
+
node.hidden = c.hidden;
|
|
29
|
+
if (c.deprecated !== undefined)
|
|
30
|
+
node.deprecated = c.deprecated;
|
|
31
|
+
if (c.effects !== undefined)
|
|
32
|
+
node.effects = c.effects;
|
|
33
|
+
if (c.relations !== undefined)
|
|
34
|
+
node.relations = c.relations;
|
|
35
|
+
return node;
|
|
36
|
+
}
|
|
37
|
+
const RESERVED = new Set(['json', 'help', 'schema', 'mcp', 'version', 'explain']);
|
|
38
|
+
export function defineCommand(command) {
|
|
39
|
+
for (const name of Object.keys(command.options ?? {})) {
|
|
40
|
+
if (RESERVED.has(name) || RESERVED.has(kebab(name)))
|
|
41
|
+
throw new Error(`burgee: option "${name}" is reserved and cannot be redefined`);
|
|
42
|
+
}
|
|
43
|
+
checkDefinition(command.name, command.options ?? {});
|
|
44
|
+
return command;
|
|
45
|
+
}
|
|
46
|
+
function addTree(manifest, parent, commands) {
|
|
47
|
+
for (const c of commands) {
|
|
48
|
+
const path = [...parent, c.name];
|
|
49
|
+
manifest.add({
|
|
50
|
+
path,
|
|
51
|
+
...helpFields(c),
|
|
52
|
+
options: c.options ?? {},
|
|
53
|
+
...(c.run === undefined ? {} : { run: c.run }),
|
|
54
|
+
...(c.load === undefined ? {} : { load: c.load }),
|
|
55
|
+
});
|
|
56
|
+
if (c.commands !== undefined)
|
|
57
|
+
addTree(manifest, path, c.commands);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function sharedOptions(name, specs) {
|
|
61
|
+
return Object.fromEntries(Object.entries(specs).map(([key, spec]) => [key, { ...spec, sharedFrom: name }]));
|
|
62
|
+
}
|
|
63
|
+
export function defineProgram(program) {
|
|
64
|
+
const manifest = new Manifest();
|
|
65
|
+
manifest.rootPath = [program.name];
|
|
66
|
+
if (program.version !== undefined)
|
|
67
|
+
manifest.version = program.version;
|
|
68
|
+
if (program.envPrefix !== undefined)
|
|
69
|
+
manifest.envPrefix = program.envPrefix;
|
|
70
|
+
if (program.schemaBudget !== undefined)
|
|
71
|
+
manifest.schemaBudget = program.schemaBudget;
|
|
72
|
+
if (program.config === true)
|
|
73
|
+
manifest.config = { name: program.name };
|
|
74
|
+
else if (typeof program.config === 'object' && program.config !== null)
|
|
75
|
+
manifest.config = program.config;
|
|
76
|
+
manifest.add({ path: [program.name], ...(program.description === undefined ? {} : { description: program.description }), options: {} });
|
|
77
|
+
addTree(manifest, [program.name], program.commands);
|
|
78
|
+
return manifest;
|
|
79
|
+
}
|
|
80
|
+
class ActionRequired extends Error {
|
|
81
|
+
spec;
|
|
82
|
+
constructor(spec) {
|
|
83
|
+
super(spec.message);
|
|
84
|
+
this.spec = spec;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const actionRequired = (spec) => {
|
|
88
|
+
throw new ActionRequired(spec);
|
|
89
|
+
};
|
|
90
|
+
class ExitSignal extends Error {
|
|
91
|
+
code;
|
|
92
|
+
constructor(code) {
|
|
93
|
+
super(`exit ${code}`);
|
|
94
|
+
this.code = code;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function isPlainObject(value) {
|
|
98
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
99
|
+
}
|
|
100
|
+
function leaf(value) {
|
|
101
|
+
if (value === undefined || value === null)
|
|
102
|
+
return '';
|
|
103
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
104
|
+
}
|
|
105
|
+
function render(value) {
|
|
106
|
+
if (value === undefined || value === null)
|
|
107
|
+
return '';
|
|
108
|
+
if (typeof value === 'string')
|
|
109
|
+
return value;
|
|
110
|
+
if (Array.isArray(value))
|
|
111
|
+
return value.map((v) => leaf(v)).join('\n');
|
|
112
|
+
if (isPlainObject(value)) {
|
|
113
|
+
return Object.entries(value)
|
|
114
|
+
.map(([k, v]) => `${k}: ${leaf(v)}`)
|
|
115
|
+
.join('\n');
|
|
116
|
+
}
|
|
117
|
+
return String(value);
|
|
118
|
+
}
|
|
119
|
+
function toParseConfig(specs, withConfig) {
|
|
120
|
+
const config = { json: { type: 'boolean' }, help: { type: 'boolean' }, version: { type: 'boolean' }, explain: { type: 'string' } };
|
|
121
|
+
if (withConfig) {
|
|
122
|
+
config['config'] = { type: 'string' };
|
|
123
|
+
config['no-config'] = { type: 'boolean' };
|
|
124
|
+
}
|
|
125
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
126
|
+
config[kebab(name)] = {
|
|
127
|
+
type: spec.type === 'boolean' ? 'boolean' : 'string',
|
|
128
|
+
...(spec.short === undefined ? {} : { short: spec.short }),
|
|
129
|
+
...(spec.multiple === true ? { multiple: true } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return config;
|
|
133
|
+
}
|
|
134
|
+
function canonical(values) {
|
|
135
|
+
return Object.fromEntries(Object.entries(values).map(([k, v]) => [camel(k), v]));
|
|
136
|
+
}
|
|
137
|
+
function packageLayer(pkg, name) {
|
|
138
|
+
if (pkg === undefined || name === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const field = pkg.data[name];
|
|
141
|
+
return typeof field === 'object' && field !== null && !Array.isArray(field) ? { path: pkg.path, data: field } : undefined;
|
|
142
|
+
}
|
|
143
|
+
async function configLayers(name, values, io) {
|
|
144
|
+
const { discover } = await import('./config.js');
|
|
145
|
+
const explicit = values['config'];
|
|
146
|
+
const disabled = values['noConfig'] === true;
|
|
147
|
+
const loaded = await discover({ name, cwd: io.cwd, env: io.env, ...(typeof explicit === 'string' ? { explicit } : {}), disabled });
|
|
148
|
+
const out = {};
|
|
149
|
+
if (loaded !== undefined)
|
|
150
|
+
out.config = { path: loaded.chain.join(' ← '), data: loaded.data };
|
|
151
|
+
const pkg = disabled ? undefined : packageLayer(io.pkg, name);
|
|
152
|
+
if (pkg !== undefined)
|
|
153
|
+
out.pkg = pkg;
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
async function resolveValues(manifest, specs, values, io) {
|
|
157
|
+
const layers = { flags: values, env: io.env };
|
|
158
|
+
if (manifest.envPrefix !== undefined)
|
|
159
|
+
layers.envPrefix = manifest.envPrefix;
|
|
160
|
+
if (manifest.config !== undefined)
|
|
161
|
+
Object.assign(layers, await configLayers(manifest.config.name, values, io));
|
|
162
|
+
const resolution = resolveLayers(specs, layers);
|
|
163
|
+
const out = { values: resolution.values, provenance: resolution.provenance };
|
|
164
|
+
const asked = values['explain'];
|
|
165
|
+
if (typeof asked === 'string')
|
|
166
|
+
out.explainText = explain(asked, resolution);
|
|
167
|
+
for (const [name, spec] of Object.entries(specs)) {
|
|
168
|
+
if (out.values[name] === undefined && spec.required === true && out.explainText === undefined) {
|
|
169
|
+
throw new UsageError(`missing required option --${kebab(name)}`, `pass --${kebab(name)} <value>`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
function splitPositionals(tokens) {
|
|
175
|
+
const positionals = [];
|
|
176
|
+
const passthrough = [];
|
|
177
|
+
let after = false;
|
|
178
|
+
for (const token of tokens) {
|
|
179
|
+
const { kind } = token;
|
|
180
|
+
const value = 'value' in token ? token.value : undefined;
|
|
181
|
+
if (kind === 'option-terminator')
|
|
182
|
+
after = true;
|
|
183
|
+
else if (kind === 'positional' && typeof value === 'string')
|
|
184
|
+
(after ? passthrough : positionals).push(value);
|
|
185
|
+
}
|
|
186
|
+
return { positionals, passthrough };
|
|
187
|
+
}
|
|
188
|
+
function isParseArgsFailure(cause) {
|
|
189
|
+
if (!(cause instanceof Error))
|
|
190
|
+
return false;
|
|
191
|
+
const { code } = cause;
|
|
192
|
+
return typeof code === 'string' && code.startsWith('ERR_PARSE_ARGS_');
|
|
193
|
+
}
|
|
194
|
+
function exitSignal(cause) {
|
|
195
|
+
const code = cause?.code;
|
|
196
|
+
return typeof code === 'number' && isExitCode(code) ? code : undefined;
|
|
197
|
+
}
|
|
198
|
+
const SINGLE_DASH_WORD = /^-([a-zA-Z][\w-]+)(?:=.*)?$/;
|
|
199
|
+
function singleDashHint(argv) {
|
|
200
|
+
for (const token of argv) {
|
|
201
|
+
if (token === '--')
|
|
202
|
+
return undefined;
|
|
203
|
+
const found = SINGLE_DASH_WORD.exec(token);
|
|
204
|
+
if (found?.[1] !== undefined)
|
|
205
|
+
return `did you mean --${found[1]}? a single dash introduces one-letter options`;
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
function describeFailure(cause, argv) {
|
|
210
|
+
const signal = exitSignal(cause);
|
|
211
|
+
if (signal !== undefined)
|
|
212
|
+
return { code: signal, message: '', silent: true };
|
|
213
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
214
|
+
if (cause instanceof ActionRequired)
|
|
215
|
+
return { code: ExitCode.CANCELLED, message, action: cause.spec, ...(cause.spec.hint === undefined ? {} : { hint: cause.spec.hint }) };
|
|
216
|
+
if (cause instanceof UsageError) {
|
|
217
|
+
return { code: ExitCode.USAGE, message, ...(cause.hint === undefined ? {} : { hint: cause.hint }) };
|
|
218
|
+
}
|
|
219
|
+
if (cause instanceof ConfigError) {
|
|
220
|
+
return { code: ExitCode.CONFIG, message, ...(cause.hint === undefined ? {} : { hint: cause.hint }) };
|
|
221
|
+
}
|
|
222
|
+
if (isParseArgsFailure(cause)) {
|
|
223
|
+
return { code: ExitCode.USAGE, message, hint: singleDashHint(argv) ?? 'run --help to see the available options' };
|
|
224
|
+
}
|
|
225
|
+
return { code: ExitCode.RUNTIME, message };
|
|
226
|
+
}
|
|
227
|
+
function textFailure(failure) {
|
|
228
|
+
const hint = failure.hint === undefined ? '' : `hint: ${failure.hint}\n`;
|
|
229
|
+
if (failure.action !== undefined) {
|
|
230
|
+
const next = (failure.action.next ?? []).map((n) => ` ${n.command} ${n.when}\n`).join('');
|
|
231
|
+
return `action required (${failure.action.reason}): ${failure.message}\n${next === '' ? '' : `next:\n${next}`}${hint}`;
|
|
232
|
+
}
|
|
233
|
+
return `error: ${failure.message}\n${hint}`;
|
|
234
|
+
}
|
|
235
|
+
function runnableNext(manifest, spec, json) {
|
|
236
|
+
const program = manifest.rootPath.join(' ');
|
|
237
|
+
return (spec.next ?? []).map((n) => ({ command: `${program} ${n.command}${json && !n.command.includes('--json') ? ' --json' : ''}`, when: n.when }));
|
|
238
|
+
}
|
|
239
|
+
const HELP_FLAGS = new Set(['--help', '-h']);
|
|
240
|
+
const HELP_WIDTH = 100;
|
|
241
|
+
const processExit = (code) => process.exit(code);
|
|
242
|
+
export function beforeTerminator(argv) {
|
|
243
|
+
const at = argv.indexOf('--');
|
|
244
|
+
return at === -1 ? argv : argv.slice(0, at);
|
|
245
|
+
}
|
|
246
|
+
function rootNode(manifest, root) {
|
|
247
|
+
return manifest.find(root) ?? { path: root, options: {} };
|
|
248
|
+
}
|
|
249
|
+
function unresolved({ manifest, root, io: { width } }, argv, at) {
|
|
250
|
+
const node = at ?? rootNode(manifest, root);
|
|
251
|
+
const typed = argv.slice(node.path.length - root.length);
|
|
252
|
+
if (typed.length > 0 && HELP_FLAGS.has(typed[0] ?? ''))
|
|
253
|
+
return { text: renderHelp(manifest, node, { width }), code: ExitCode.OK };
|
|
254
|
+
if (typed.length === 0)
|
|
255
|
+
return { text: renderHelp(manifest, node, { width }), code: ExitCode.USAGE };
|
|
256
|
+
throw new UsageError(`unknown command "${typed[0] ?? ''}"`, 'run --help to see the available commands');
|
|
257
|
+
}
|
|
258
|
+
async function completion(manifest, argv, io) {
|
|
259
|
+
if (argv[0] !== 'completion' || manifest.find([...manifest.rootPath, 'completion']) !== undefined)
|
|
260
|
+
return false;
|
|
261
|
+
const { renderCompletion, renderFigSpec, SHELLS } = await import('./completions.js');
|
|
262
|
+
const shell = argv[1] ?? '';
|
|
263
|
+
if (shell === 'fig') {
|
|
264
|
+
io.out.write(`${JSON.stringify(renderFigSpec(manifest), null, 2)}\n`);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
const known = SHELLS.find((s) => s === shell);
|
|
268
|
+
if (known === undefined)
|
|
269
|
+
throw new UsageError(`unknown shell "${shell}"`, `completion ${SHELLS.join('|')}|fig`);
|
|
270
|
+
io.out.write(renderCompletion(manifest, known));
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
async function surface(manifest, argv, io) {
|
|
274
|
+
const head = beforeTerminator(argv);
|
|
275
|
+
if (await completion(manifest, argv, io))
|
|
276
|
+
return true;
|
|
277
|
+
if (argv[0] === 'help') {
|
|
278
|
+
io.out.write(helpCommand(manifest, argv.slice(1), manifest.rootPath, io.width));
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if (head.includes('--schema')) {
|
|
282
|
+
io.out.write(`${JSON.stringify(schemaSurface(manifest, argv), null, 2)}\n`);
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
if (head[0] === '--mcp') {
|
|
286
|
+
const invoke = async (args) => {
|
|
287
|
+
const out = [];
|
|
288
|
+
const err = [];
|
|
289
|
+
let code = 0;
|
|
290
|
+
await execute(manifest, {
|
|
291
|
+
argv: args,
|
|
292
|
+
env: io.env,
|
|
293
|
+
stdout: { write: (s) => out.push(s) },
|
|
294
|
+
stderr: { write: (s) => err.push(s) },
|
|
295
|
+
exit: (c) => {
|
|
296
|
+
code = c;
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
return { stdout: out.join(''), stderr: err.join(''), code };
|
|
300
|
+
};
|
|
301
|
+
await serveMcp(manifest, { input: io.stdin, output: io.out, invoke });
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
const SCHEMA_BUDGET = 48_000;
|
|
307
|
+
function schemaSurface(manifest, argv) {
|
|
308
|
+
const { node } = manifest.resolve(beforeTerminator(argv).filter((a) => a !== '--schema'), manifest.rootPath);
|
|
309
|
+
if (node?.run !== undefined)
|
|
310
|
+
return commandSchemaOf(node, manifest.rootPath);
|
|
311
|
+
const full = schemaOf(manifest);
|
|
312
|
+
const budget = manifest.schemaBudget ?? SCHEMA_BUDGET;
|
|
313
|
+
return JSON.stringify(full).length <= budget ? full : summaryOf(manifest, budget);
|
|
314
|
+
}
|
|
315
|
+
function helpCommand(manifest, argv, root, width) {
|
|
316
|
+
const { node } = manifest.resolve(argv, root);
|
|
317
|
+
return renderHelp(manifest, node ?? rootNode(manifest, root), { width });
|
|
318
|
+
}
|
|
319
|
+
function changedOf(node, data) {
|
|
320
|
+
const value = isPlainObject(data) ? data['changed'] : undefined;
|
|
321
|
+
if (typeof value === 'boolean')
|
|
322
|
+
return value;
|
|
323
|
+
if (node.effects === 'idempotent') {
|
|
324
|
+
throw new Error(`"${node.path.slice(1).join(' ')}" is idempotent and must report changed: true | false in its result (N7)`);
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
function versionOf(manifest, io) {
|
|
329
|
+
const declared = manifest.version ?? (typeof io.pkg?.data['version'] === 'string' ? io.pkg.data['version'] : undefined);
|
|
330
|
+
if (declared === undefined)
|
|
331
|
+
throw new ConfigError('no version declared', 'pass version to defineProgram, or set "version" in the owning package.json');
|
|
332
|
+
return declared;
|
|
333
|
+
}
|
|
334
|
+
async function dispatch(manifest, { node, rest, name }, io) {
|
|
335
|
+
const parsed = parseArgs({ args: rest, options: toParseConfig(node.options, manifest.config !== undefined), allowPositionals: true, strict: true, tokens: true });
|
|
336
|
+
const flags = canonical(parsed.values);
|
|
337
|
+
const json = flags.json === true;
|
|
338
|
+
if (flags.help === true)
|
|
339
|
+
return { json, text: renderHelp(manifest, node, { width: io.width }) };
|
|
340
|
+
if (flags.version === true)
|
|
341
|
+
return { json, text: `${versionOf(manifest, io)}\n` };
|
|
342
|
+
const resolved = await resolveValues(manifest, node.options, flags, io);
|
|
343
|
+
if (resolved.explainText !== undefined)
|
|
344
|
+
return { json, text: resolved.explainText };
|
|
345
|
+
const { provenance } = resolved;
|
|
346
|
+
checkRelations(node.relations, resolved.values, provenance);
|
|
347
|
+
const values = await coerce(node.options, resolved.values);
|
|
348
|
+
const { positionals, passthrough } = splitPositionals(parsed.tokens);
|
|
349
|
+
requirePositionals(node, positionals);
|
|
350
|
+
warnDeprecated(node, io);
|
|
351
|
+
await manifest.fire('preRun', name, values);
|
|
352
|
+
const exit = (code) => {
|
|
353
|
+
io.exit(code);
|
|
354
|
+
throw new ExitSignal(code);
|
|
355
|
+
};
|
|
356
|
+
const detection = detectAgent(io.env, io.tty);
|
|
357
|
+
const data = await node.run({ options: values, positionals, passthrough, env: io.env, exit, actionRequired, ...detection });
|
|
358
|
+
await manifest.fire('postRun', name, values);
|
|
359
|
+
const changed = changedOf(node, data);
|
|
360
|
+
return { json, data, provenance, ...(changed === undefined ? {} : { changed }) };
|
|
361
|
+
}
|
|
362
|
+
function requirePositionals(node, positionals) {
|
|
363
|
+
const required = (node.arguments ?? []).filter((a) => a.required !== false && a.variadic !== true);
|
|
364
|
+
const missing = required[positionals.length];
|
|
365
|
+
if (positionals.length < required.length && missing !== undefined) {
|
|
366
|
+
throw new UsageError(`missing required argument "${missing.name}"`, `run --help to see what "${node.path.slice(1).join(' ')}" takes`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const warned = new Set();
|
|
370
|
+
function warnDeprecated(node, io) {
|
|
371
|
+
if (node.deprecated === undefined || node.deprecated === false)
|
|
372
|
+
return;
|
|
373
|
+
const key = node.path.join(' ');
|
|
374
|
+
if (warned.has(key))
|
|
375
|
+
return;
|
|
376
|
+
warned.add(key);
|
|
377
|
+
const typed = node.path.slice(1).join(' ') || key;
|
|
378
|
+
const use = typeof node.deprecated === 'string' ? `, use '${node.deprecated}'` : '';
|
|
379
|
+
io.err.write(`warning: '${typed}' is deprecated${use}\n`);
|
|
380
|
+
}
|
|
381
|
+
function emit(io, outcome) {
|
|
382
|
+
if (outcome.text !== undefined) {
|
|
383
|
+
io.out.write(outcome.text);
|
|
384
|
+
return io.exit(ExitCode.OK);
|
|
385
|
+
}
|
|
386
|
+
const meta = { provenance: outcome.provenance ?? {}, ...(outcome.changed === undefined ? {} : { changed: outcome.changed }) };
|
|
387
|
+
const envelope = { ok: true, data: outcome.data, meta };
|
|
388
|
+
io.out.write(outcome.json ? `${JSON.stringify(envelope)}\n` : `${render(outcome.data)}\n`);
|
|
389
|
+
return io.exit(ExitCode.OK);
|
|
390
|
+
}
|
|
391
|
+
async function report(cause, { manifest, io, argv, json, name }) {
|
|
392
|
+
const failure = describeFailure(cause, argv);
|
|
393
|
+
if (failure.silent === true)
|
|
394
|
+
return io.exit(failure.code);
|
|
395
|
+
await manifest.fire('onError', name, {});
|
|
396
|
+
if (failure.action !== undefined) {
|
|
397
|
+
const next = runnableNext(manifest, failure.action, json);
|
|
398
|
+
const rendered = { ...failure, action: { ...failure.action, next } };
|
|
399
|
+
const body = { ok: false, status: 'action_required', reason: failure.action.reason, message: failure.message, next, hint: failure.hint, error: { code: failure.code, message: failure.message } };
|
|
400
|
+
io.err.write(json ? `${JSON.stringify(body)}\n` : textFailure(rendered));
|
|
401
|
+
return io.exit(failure.code);
|
|
402
|
+
}
|
|
403
|
+
const body = { code: failure.code, message: failure.message, hint: failure.hint };
|
|
404
|
+
io.err.write(json ? `${JSON.stringify({ ok: false, error: body })}\n` : textFailure(failure));
|
|
405
|
+
return io.exit(failure.code);
|
|
406
|
+
}
|
|
407
|
+
function ioOf(opts) {
|
|
408
|
+
const out = opts.stdout ?? process.stdout;
|
|
409
|
+
return {
|
|
410
|
+
out,
|
|
411
|
+
err: opts.stderr ?? process.stderr,
|
|
412
|
+
env: opts.env ?? process.env,
|
|
413
|
+
exit: opts.exit ?? processExit,
|
|
414
|
+
width: out.columns ?? HELP_WIDTH,
|
|
415
|
+
stdin: opts.stdin ?? process.stdin,
|
|
416
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
417
|
+
pkg: nearestPackage(dirname(opts.entry ?? process.argv[1] ?? process.cwd())),
|
|
418
|
+
tty: out.isTTY === true,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
export async function execute(manifest, opts = {}) {
|
|
422
|
+
const io = ioOf(opts);
|
|
423
|
+
const raw = opts.argv ?? process.argv;
|
|
424
|
+
const argv = opts.argv === undefined || opts.from === 'node' ? raw.slice(2) : raw;
|
|
425
|
+
const root = opts.root ?? manifest.rootPath;
|
|
426
|
+
let json = beforeTerminator(argv).includes('--json');
|
|
427
|
+
let name = '';
|
|
428
|
+
try {
|
|
429
|
+
if (await surface(manifest, argv, io))
|
|
430
|
+
return io.exit(ExitCode.OK);
|
|
431
|
+
const { node, rest } = manifest.resolve(argv, root);
|
|
432
|
+
if (node?.run === undefined) {
|
|
433
|
+
const { text, code } = unresolved({ manifest, root, io }, argv, node);
|
|
434
|
+
(code === ExitCode.OK ? io.out : io.err).write(text);
|
|
435
|
+
return io.exit(code);
|
|
436
|
+
}
|
|
437
|
+
name = node.path.slice(root.length).join(' ');
|
|
438
|
+
const outcome = await dispatch(manifest, { node: node, rest, name }, io);
|
|
439
|
+
json = outcome.json;
|
|
440
|
+
return emit(io, outcome);
|
|
441
|
+
}
|
|
442
|
+
catch (cause) {
|
|
443
|
+
return await report(cause, { manifest, io, argv, json, name });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
export function resolveCommand(manifest, argv) {
|
|
447
|
+
const { node } = manifest.resolve(beforeTerminator(argv), manifest.rootPath);
|
|
448
|
+
return node ?? null;
|
|
449
|
+
}
|
|
450
|
+
export async function runCommand(manifest, argv, opts = {}) {
|
|
451
|
+
const out = [];
|
|
452
|
+
const err = [];
|
|
453
|
+
let code = ExitCode.OK;
|
|
454
|
+
await execute(manifest, { ...opts, argv: [...argv], from: 'user', stdout: { write: (s) => out.push(s) }, stderr: { write: (s) => err.push(s) }, exit: (c) => void (code = c) });
|
|
455
|
+
return { code, stdout: out.join(''), stderr: err.join('') };
|
|
456
|
+
}
|
|
457
|
+
export async function run(target, opts = {}) {
|
|
458
|
+
if (target instanceof Manifest)
|
|
459
|
+
return await execute(target, opts);
|
|
460
|
+
const manifest = new Manifest();
|
|
461
|
+
manifest.rootPath = [target.name];
|
|
462
|
+
manifest.add({
|
|
463
|
+
path: [target.name],
|
|
464
|
+
...(target.description === undefined ? {} : { description: target.description }),
|
|
465
|
+
options: target.options ?? {},
|
|
466
|
+
...(target.run === undefined ? {} : { run: target.run }),
|
|
467
|
+
});
|
|
468
|
+
return await execute(manifest, opts);
|
|
469
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** E1 — exit codes are a contract. No other literal may reach `process.exitCode`. */
|
|
2
|
+
export declare const ExitCode: {
|
|
3
|
+
/** Command completed. */
|
|
4
|
+
readonly OK: 0;
|
|
5
|
+
/** The command ran and failed. Never accompanied by help text (E2). */
|
|
6
|
+
readonly RUNTIME: 1;
|
|
7
|
+
/** Bad arguments, unknown command, missing flag, prompt needed in a non-TTY (P2). */
|
|
8
|
+
readonly USAGE: 2;
|
|
9
|
+
/** Config file or environment could not be loaded or validated (V1). */
|
|
10
|
+
readonly CONFIG: 3;
|
|
11
|
+
/** The user or caller cancelled. */
|
|
12
|
+
readonly CANCELLED: 4;
|
|
13
|
+
/** SIGINT after the terminal was restored (E5). */
|
|
14
|
+
readonly SIGINT: 130;
|
|
15
|
+
};
|
|
16
|
+
export type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];
|
|
17
|
+
/** True for the six codes in the contract and nothing else. */
|
|
18
|
+
export declare function isExitCode(n: unknown): n is ExitCode;
|
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CommandNode, Manifest } from './manifest.js';
|
|
2
|
+
/** The token names of `roundel`'s R3, typed structurally: burgee never imports them (U13). */
|
|
3
|
+
export type HelpToken = 'error' | 'warn' | 'ok' | 'hint' | 'muted' | 'command' | 'flag' | 'value' | 'heading';
|
|
4
|
+
/**
|
|
5
|
+
* Per-token styling for help (R7). A user who has `roundel` passes its tokens; a user
|
|
6
|
+
* who does not gets the defaults. Help reads `heading`, `command`, `flag` and `value`;
|
|
7
|
+
* the others are accepted so one theme object serves the whole output stack.
|
|
8
|
+
*
|
|
9
|
+
* What the theme does not touch: the command name after `Usage:` and every `$ example`
|
|
10
|
+
* line are rendered plain, whatever the theme says. Styling wraps a finished cell, so a
|
|
11
|
+
* name is measured and padded plain and never coloured in the manifest (yargs #1699).
|
|
12
|
+
*/
|
|
13
|
+
export type HelpTheme = Partial<Record<HelpToken, (s: string) => string>>;
|
|
14
|
+
export interface HelpOptions {
|
|
15
|
+
/** Columns available; 100 when unknown, never `process.stdout` directly (H3). */
|
|
16
|
+
width?: number;
|
|
17
|
+
/** Show type hints such as `[string]`; off by default (H6). */
|
|
18
|
+
verbose?: boolean;
|
|
19
|
+
/** Apply colour (R7). Off by default: the renderer is pure, so the TTY and NO_COLOR decision stays with the caller. */
|
|
20
|
+
color?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Replaces the default styling token by token; read only when `color` is on. See
|
|
23
|
+
* `HelpTheme` for the lines it leaves plain: the `Usage:` command name and `$ example`.
|
|
24
|
+
*/
|
|
25
|
+
theme?: HelpTheme;
|
|
26
|
+
}
|
|
27
|
+
/** Word-wrap one paragraph; lines the author indented are kept verbatim (yargs #2120). */
|
|
28
|
+
export declare function wrap(text: string, width: number): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Render help for one node — a runnable command, a group, or both — as text.
|
|
31
|
+
* Deterministic for a given node and width; a snapshot suite pins it. With `color`
|
|
32
|
+
* off — the default — a theme changes nothing; with it on, only ANSI is added.
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderHelp(manifest: Manifest, node: CommandNode, opts?: HelpOptions): string;
|