burgee 0.8.0 → 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.
@@ -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,3 @@
1
+ export function own(target, key, value) {
2
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
3
+ }
@@ -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;
package/dist/meow.js ADDED
@@ -0,0 +1,146 @@
1
+ import { ExitCode } from './exit-code.js';
2
+ import { aliasMap, splitAtCommand, typeofDefault } from './meow/parse.js';
3
+ import { buildHelp, normalizePackage, readPackageUp, setProcessTitle } from './meow/present.js';
4
+ import { own } from './meow/types.js';
5
+ import { checkChoices, checkInput, checkRequired, checkSetOnce, checkUnknown, requireImportMeta, validateCommands, validateFlags } from './meow/validate.js';
6
+ import { host } from './runtime.js';
7
+ import parser, { camelCase } from './yargs-parser.js';
8
+ function meow(helpText, options = {}) {
9
+ const opts = typeof helpText === 'string' ? { help: helpText, ...options } : helpText;
10
+ if (typeof helpText !== 'string' && Object.keys(options).length > 0)
11
+ Object.assign(opts, options);
12
+ if (opts.input !== undefined && typeof opts.input !== 'string' && !Array.isArray(opts.input) && typeof opts.input !== 'object') {
13
+ throw new TypeError('The `input` option must be a string or an object.');
14
+ }
15
+ const flagSpecs = (opts.flags ?? {});
16
+ if (typeof flagSpecs !== 'object' || flagSpecs === null || Array.isArray(flagSpecs)) {
17
+ throw new TypeError('The `flags` option must be an object.');
18
+ }
19
+ if (typeof opts.input === 'object' && opts.input !== null) {
20
+ const required = opts.input.isRequired;
21
+ if (required !== undefined && typeof required !== 'boolean' && typeof required !== 'function') {
22
+ throw new TypeError('The `input.isRequired` option must be a boolean or a function.');
23
+ }
24
+ }
25
+ validateFlags(flagSpecs);
26
+ if (opts.pkg === undefined)
27
+ requireImportMeta(opts.importMeta);
28
+ const pkg = normalizePackage(opts.pkg ?? readPackageUp(opts.importMeta));
29
+ validateCommands(opts.commands);
30
+ const rawArgv = [...(opts.argv ?? host.argv.slice(2))];
31
+ const booleans = [];
32
+ const strings = [];
33
+ const numbers = [];
34
+ const defaults = {};
35
+ const arrays = [];
36
+ for (const [name, spec] of Object.entries(flagSpecs)) {
37
+ if (name === '--')
38
+ continue;
39
+ const type = spec.type ?? (spec.default === undefined ? undefined : typeofDefault(spec.default));
40
+ if (type === 'boolean')
41
+ booleans.push(name);
42
+ else if (type === 'number')
43
+ numbers.push(name);
44
+ else if (type === 'string')
45
+ strings.push(name);
46
+ if (spec.isMultiple === true)
47
+ arrays.push(name);
48
+ if (spec.default !== undefined)
49
+ own(defaults, name, spec.default);
50
+ else if (type === 'boolean' && 'booleanDefault' in opts) {
51
+ if (opts.booleanDefault !== undefined)
52
+ own(defaults, name, opts.booleanDefault);
53
+ }
54
+ else if (type === 'boolean')
55
+ own(defaults, name, false);
56
+ if (spec.isMultiple === true && spec.default === undefined) {
57
+ own(defaults, name, type === 'boolean' && !('booleanDefault' in opts) ? [false] : []);
58
+ }
59
+ }
60
+ const parserOptions = {
61
+ alias: aliasMap(flagSpecs),
62
+ array: arrays,
63
+ boolean: booleans,
64
+ string: strings,
65
+ number: numbers,
66
+ default: defaults,
67
+ configuration: {
68
+ 'camel-case-expansion': true,
69
+ 'greedy-arrays': false,
70
+ 'parse-numbers': opts.inferType === true,
71
+ 'parse-positional-numbers': opts.inferType === true,
72
+ 'populate--': flagSpecs['--'] !== undefined,
73
+ 'strip-aliased': false,
74
+ 'strip-dashed': true,
75
+ 'unknown-options-as-args': false,
76
+ },
77
+ };
78
+ const split = opts.commands === undefined ? undefined : splitAtCommand(rawArgv, parserOptions, opts);
79
+ const argv = split?.parent ?? rawArgv;
80
+ const parsed = parser.detailed(argv, parserOptions);
81
+ const { _: positional, ...rest } = parsed.argv;
82
+ const inputType = typeof opts.input === 'object' && opts.input !== null ? opts.input.type : opts.input;
83
+ const coerce = (v) => {
84
+ if (inputType === 'number')
85
+ return Number(v);
86
+ if (inputType === 'string')
87
+ return String(v);
88
+ return opts.inferType === true ? v : String(v);
89
+ };
90
+ const input = (split === undefined ? positional.map(coerce) : split.input);
91
+ const command = split?.command;
92
+ const dropped = new Set();
93
+ for (const [name, spec] of Object.entries(flagSpecs)) {
94
+ if (typeof spec.shortFlag === 'string')
95
+ dropped.add(spec.shortFlag);
96
+ if (typeof spec.alias === 'string')
97
+ dropped.add(spec.alias);
98
+ for (const extra of spec.aliases ?? [])
99
+ dropped.add(extra);
100
+ void name;
101
+ }
102
+ const flags = {};
103
+ const unnormalizedFlags = { ...rest };
104
+ for (const [key, value] of Object.entries(rest)) {
105
+ if (key === '--' || dropped.has(key))
106
+ continue;
107
+ Object.defineProperty(flags, camelCase(key), { value, writable: true, enumerable: true, configurable: true });
108
+ }
109
+ if (flagSpecs['--'] !== undefined)
110
+ flags['--'] = rest['--'] ?? [];
111
+ const help = buildHelp(opts, pkg);
112
+ const declared = opts.version === undefined ? undefined : String(opts.version);
113
+ const fromPkg = typeof pkg['version'] === 'string' && pkg['version'] !== '' ? pkg['version'] : undefined;
114
+ const version = declared ?? fromPkg ?? 'No version found';
115
+ const showHelp = (code = ExitCode.USAGE) => {
116
+ host.stdout.write(help === '' ? '\n' : `${help}\n`);
117
+ return host.exit(code);
118
+ };
119
+ const showVersion = () => {
120
+ host.stdout.write(`${version}\n`);
121
+ host.exit(ExitCode.OK);
122
+ };
123
+ setProcessTitle(pkg);
124
+ const result = { input, flags, unnormalizedFlags, pkg, help, version, showHelp, showVersion };
125
+ if (opts.commands !== undefined && command !== undefined)
126
+ result.command = command;
127
+ if (split?.unknownCommand !== undefined) {
128
+ host.stderr.write(`Unknown command: ${split.unknownCommand}\nAvailable commands: ${opts.commands?.join(', ') ?? ''}\n${help}\n`);
129
+ host.exit(ExitCode.USAGE);
130
+ }
131
+ if (input.length === 0) {
132
+ if (opts.autoHelp !== false && flagSpecs['help'] === undefined && flags['help'] === true)
133
+ showHelp(ExitCode.OK);
134
+ if (opts.autoVersion !== false && flagSpecs['version'] === undefined && flags['version'] === true)
135
+ showVersion();
136
+ }
137
+ checkSetOnce(flagSpecs, rest);
138
+ checkInput(opts, input, flags);
139
+ checkChoices(flagSpecs, flags);
140
+ checkRequired(flagSpecs, flags, input);
141
+ if (opts.allowUnknownFlags === false)
142
+ checkUnknown(flagSpecs, rest, argv, opts);
143
+ return result;
144
+ }
145
+ const meowFacade = meow;
146
+ export default meowFacade;
package/dist/plugin.d.ts CHANGED
@@ -19,7 +19,7 @@ export interface Plugin {
19
19
  };
20
20
  enforce?: 'pre' | 'post';
21
21
  }
22
- export type PluginErrorCode = 'E_PLUGIN_SCHEMA' | 'E_PLUGIN_CONTRACT';
22
+ export type PluginErrorCode = 'E_PLUGIN_SCHEMA' | 'E_PLUGIN_CONTRACT' | 'E_NO_CONTRIBUTION';
23
23
  /** A refused plugin says what is wrong and what to do about it — the family's one vocabulary. */
24
24
  export declare class PluginError extends Error {
25
25
  readonly code: PluginErrorCode;
package/dist/plugin.js CHANGED
@@ -68,7 +68,7 @@ function checkCommands(commands, name, taken) {
68
68
  }
69
69
  seen.add(path);
70
70
  try {
71
- checkCommand(path, (node['options'] ?? {}), node['effects'], node['run'] !== undefined || node['load'] !== undefined);
71
+ checkCommand(path, node);
72
72
  }
73
73
  catch (error) {
74
74
  throw schema(`${at}: ${error.message}`, 'a plugin command is declared exactly as a first-party one');
package/dist/runtime.d.ts CHANGED
@@ -66,6 +66,8 @@ export declare const host: {
66
66
  readonly stdin: NodeJS.ReadStream;
67
67
  readonly stdout: NodeJS.WriteStream;
68
68
  readonly stderr: NodeJS.WriteStream;
69
+ /** What `ps` shows. meow renames the process after the binary it is; nothing else sets it. */
70
+ setTitle(value: string): void;
69
71
  /**
70
72
  * The terminal's width, or undefined when there is no terminal to ask. Guarded on
71
73
  * `process` itself because cliui's upstream is guarded there: `getWindowWidth` is reached
package/dist/runtime.js CHANGED
@@ -18,6 +18,9 @@ export const host = {
18
18
  get stderr() {
19
19
  return process.stderr;
20
20
  },
21
+ setTitle(value) {
22
+ process.title = value;
23
+ },
21
24
  get columns() {
22
25
  if (typeof process === 'undefined')
23
26
  return undefined;
@@ -0,0 +1,3 @@
1
+ /** `burgee/schema` — the schema surface and the manifest it reads, by itself. See `index.ts`. */
2
+ export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf, type CommandSchema, type JsonSchema, type ProgramSchema, type SchemaSummary } from './schema.js';
3
+ export { Manifest } from './manifest.js';
@@ -0,0 +1,2 @@
1
+ export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf } from './schema.js';
2
+ export { Manifest } from './manifest.js';
package/dist/schema.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/ofri-peretz/burgee/blob/main/packages/flagstaff/src/schema.json","title":"flagstaff plugin","description":"A plugin is one plain object. Everything in it is data that can be read without running it; the only functions allowed are a component's `static` (required) and `frame` (optional). A spinner or component without a static projection is refused at register().","type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"type":"string","minLength":1,"description":"The plugin's name; also the prefix a host may use when two plugins contribute the same key."},"contract":{"type":"integer","minimum":1,"description":"The plugin contract this object follows. A host refuses a newer contract than it knows."},"tokens":{"type":"object","description":"A roundel theme: semantic token name to a hex colour, contrast-checked when flown.","additionalProperties":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"propertyNames":{"enum":["error","warn","ok","hint","muted","command","flag","value","heading","ground"],"description":"roundel's ten semantic tokens, and nothing else — `roundel`'s own validate() refuses any other name."}},"glyphs":{"type":"object","description":"Symbols by meaning: `ok`, `fail`, `warn`, `info`, `running`. A plugin that ships glyphs changes every built-in that draws one.","additionalProperties":{"type":"string","minLength":1}},"spinners":{"type":"object","description":"Spinner styles by name, in cli-spinners' shape plus the static projection.","additionalProperties":{"$ref":"#/$defs/spinner"}},"borders":{"type":"object","description":"Border styles a box can be drawn with, by name.","additionalProperties":{"$ref":"#/$defs/border"}},"components":{"type":"object","description":"Components by name: `static(state)` returns the text a pipe, an agent or a screen reader gets; `frame(t, state)` is the optional animated form.","additionalProperties":{"$ref":"#/$defs/component"}},"capabilities":{"$ref":"#/$defs/capabilities"}},"$defs":{"spinner":{"type":"object","required":["frames","interval","static"],"properties":{"frames":{"type":"array","items":{"type":"string"},"minItems":1},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between frames on a terminal."},"static":{"type":"string","description":"What a pipe prints instead of the animation."}}},"component":{"type":"object","required":["static"],"properties":{"static":{"description":"(state) => string. Required: the projection every non-terminal mode prints."},"frame":{"description":"(t, state) => string. Optional: the frame at t milliseconds since hoisting."},"sample":{"type":"object","required":["running","done"],"description":"Two states to *show* this component with: `flagstaff check` and the docs gallery render `running` then `done`. Omitted, they assume `{ phase: 'running' }` and `{ phase: 'done' }` and say so in the output. The loop never reads it — a running program's state comes from the program.","properties":{"running":{"description":"The state to open with."},"done":{"description":"The state to close with."}}},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between repaints when `frame` is given; 80 when omitted."}}},"border":{"type":"object","required":["topLeft","top","topRight","left","right","bottomLeft","bottom","bottomRight"],"description":"cli-boxes' shape exactly, so that corpus imports unchanged.","properties":{"topLeft":{"type":"string"},"top":{"type":"string"},"topRight":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"bottomLeft":{"type":"string"},"bottom":{"type":"string"},"bottomRight":{"type":"string"}}},"capability":{"type":"object","required":["name","osc","when","encode","fallback"],"additionalProperties":false,"description":"One paratext capability: what it says to the terminal, when the terminal is believed to understand it, and what prints when it does not. No functions, so it travels through JSON. `encode` and `fallback` are templates: `{field}` is the field's value, `{field|base64}` is it base64-encoded, and `[ … ]` is emitted only when every field inside it has a value.","properties":{"name":{"type":"string","minLength":1,"description":"How callers name it. Registering an existing name replaces it — how a caller corrects a guess we got wrong."},"osc":{"description":"The OSC code this speaks, or 'BEL' for the bell and for protocols that are not OSC at all, such as Kitty's.","oneOf":[{"type":"integer","minimum":0},{"const":"BEL"}]},"when":{"type":"object","description":"When the terminal is believed to understand it. Every clause must hold; `termProgram` and `envAny` are ORs within themselves. Guesses — no terminal answers 'do you do OSC 1337' — and therefore data a caller can replace.","additionalProperties":false,"properties":{"tty":{"type":"boolean","description":"Refuse a pipe. Almost always true: a file that receives OSC gets control bytes in it."},"termProgram":{"type":"array","items":{"type":"string"},"description":"Any one of these TERM_PROGRAM values."},"envAny":{"type":"array","items":{"type":"string"},"description":"Any one of these environment variables merely being set, as VTE announces itself."},"term":{"type":"string","description":"An exact TERM — Kitty is xterm-kitty."}}},"encode":{"type":"string","minLength":1,"description":"The bytes, as a template, for a terminal that does understand."},"fallback":{"type":"string","description":"What to print when it does not — PRINCIPLES rule 6. '' is a legitimate answer, a window title having nothing to say in a log; absence is not, which is why this is required and may be ''."}},"examples":[{"name":"kitty-image","osc":"BEL","when":{"tty":true,"term":"xterm-kitty"},"encode":"\u001b_Ga=T,f=100;{base64}\u001b\\","fallback":"{caption}"}]},"capabilities":{"type":"object","description":"paratext capabilities by name — the OSC section of a plugin, read the way flagstaff reads `spinners`.","additionalProperties":{"$ref":"#/$defs/capability"}},"capabilityDocument":{"description":"What paratext's `check()` accepts. Two shapes, and only one of them survives 1.0.","oneOf":[{"description":"The family shape: a plugin carrying its capabilities under `capabilities`.","type":"object","required":["name","capabilities"],"properties":{"name":{"type":"string","minLength":1},"contract":{"type":"integer","minimum":1},"capabilities":{"$ref":"#/$defs/capabilities"}}},{"deprecated":true,"description":"Deprecated: one capability as the whole document, the shape paratext's schema had before this one absorbed it. Accepted for one minor release, removed at 1.0 — `check()` validates it and says so.","$ref":"#/$defs/capability"}]}}}
1
+ {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/ofri-peretz/burgee/blob/main/packages/flagstaff/src/schema.json","title":"flagstaff plugin","description":"A plugin is one plain object. Everything in it is data that can be read without running it; the only functions allowed are a component's `static` (required) and `frame` (optional). A spinner or component without a static projection is refused at register().","type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"type":"string","minLength":1,"description":"The plugin's name; also the prefix a host may use when two plugins contribute the same key."},"contract":{"type":"integer","minimum":1,"description":"The plugin contract this object follows. A host refuses a newer contract than it knows."},"tokens":{"type":"object","description":"A roundel theme: semantic token name to a hex colour, contrast-checked when flown.","additionalProperties":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"propertyNames":{"enum":["error","warn","ok","hint","muted","command","flag","value","heading","ground"],"description":"roundel's ten semantic tokens, and nothing else — `roundel`'s own validate() refuses any other name."}},"glyphs":{"type":"object","description":"Symbols by meaning: `ok`, `fail`, `warn`, `info`, `running`. A plugin that ships glyphs changes every built-in that draws one.","additionalProperties":{"type":"string","minLength":1}},"spinners":{"type":"object","description":"Spinner styles by name, in cli-spinners' shape plus the static projection.","additionalProperties":{"$ref":"#/$defs/spinner"}},"borders":{"type":"object","description":"Border styles a box can be drawn with, by name.","additionalProperties":{"$ref":"#/$defs/border"}},"components":{"type":"object","description":"Components by name: `static(state)` returns the text a pipe, an agent or a screen reader gets; `frame(t, state)` is the optional animated form.","additionalProperties":{"$ref":"#/$defs/component"}},"capabilities":{"$ref":"#/$defs/capabilities"},"widths":{"$ref":"#/$defs/widths"}},"$defs":{"spinner":{"type":"object","required":["frames","interval","static"],"properties":{"frames":{"type":"array","items":{"type":"string"},"minItems":1},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between frames on a terminal."},"static":{"type":"string","description":"What a pipe prints instead of the animation."}}},"component":{"type":"object","required":["static"],"properties":{"static":{"description":"(state) => string. Required: the projection every non-terminal mode prints."},"frame":{"description":"(t, state) => string. Optional: the frame at t milliseconds since hoisting."},"sample":{"type":"object","required":["running","done"],"description":"Two states to *show* this component with: `flagstaff check` and the docs gallery render `running` then `done`. Omitted, they assume `{ phase: 'running' }` and `{ phase: 'done' }` and say so in the output. The loop never reads it — a running program's state comes from the program.","properties":{"running":{"description":"The state to open with."},"done":{"description":"The state to close with."}}},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between repaints when `frame` is given; 80 when omitted."}}},"border":{"type":"object","required":["topLeft","top","topRight","left","right","bottomLeft","bottom","bottomRight"],"description":"cli-boxes' shape exactly, so that corpus imports unchanged.","properties":{"topLeft":{"type":"string"},"top":{"type":"string"},"topRight":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"bottomLeft":{"type":"string"},"bottom":{"type":"string"},"bottomRight":{"type":"string"}}},"capability":{"type":"object","required":["name","osc","when","encode","fallback"],"additionalProperties":false,"description":"One paratext capability: what it says to the terminal, when the terminal is believed to understand it, and what prints when it does not. No functions, so it travels through JSON. `encode` and `fallback` are templates: `{field}` is the field's value, `{field|base64}` is it base64-encoded, and `[ … ]` is emitted only when every field inside it has a value.","properties":{"name":{"type":"string","minLength":1,"description":"How callers name it. Registering an existing name replaces it — how a caller corrects a guess we got wrong."},"osc":{"description":"The OSC code this speaks, or 'BEL' for the bell and for protocols that are not OSC at all, such as Kitty's.","oneOf":[{"type":"integer","minimum":0},{"const":"BEL"}]},"when":{"type":"object","description":"When the terminal is believed to understand it. Every clause must hold; `termProgram` and `envAny` are ORs within themselves. Guesses — no terminal answers 'do you do OSC 1337' — and therefore data a caller can replace.","additionalProperties":false,"properties":{"tty":{"type":"boolean","description":"Refuse a pipe. Almost always true: a file that receives OSC gets control bytes in it."},"termProgram":{"type":"array","items":{"type":"string"},"description":"Any one of these TERM_PROGRAM values."},"envAny":{"type":"array","items":{"type":"string"},"description":"Any one of these environment variables merely being set, as VTE announces itself."},"term":{"type":"string","description":"An exact TERM — Kitty is xterm-kitty."}}},"encode":{"type":"string","minLength":1,"description":"The bytes, as a template, for a terminal that does understand."},"fallback":{"type":"string","description":"What to print when it does not — PRINCIPLES rule 6. '' is a legitimate answer, a window title having nothing to say in a log; absence is not, which is why this is required and may be ''."}},"examples":[{"name":"kitty-image","osc":"BEL","when":{"tty":true,"term":"xterm-kitty"},"encode":"\u001b_Ga=T,f=100;{base64}\u001b\\","fallback":"{caption}"}]},"capabilities":{"type":"object","description":"paratext capabilities by name — the OSC section of a plugin, read the way flagstaff reads `spinners`.","additionalProperties":{"$ref":"#/$defs/capability"}},"capabilityDocument":{"description":"What paratext's `check()` accepts. Two shapes, and only one of them survives 1.0.","oneOf":[{"description":"The family shape: a plugin carrying its capabilities under `capabilities`.","type":"object","required":["name","capabilities"],"properties":{"name":{"type":"string","minLength":1},"contract":{"type":"integer","minimum":1},"capabilities":{"$ref":"#/$defs/capabilities"}}},{"deprecated":true,"description":"Deprecated: one capability as the whole document, the shape paratext's schema had before this one absorbed it. Accepted for one minor release, removed at 1.0 — `check()` validates it and says so.","$ref":"#/$defs/capability"}]},"widthRange":{"type":"array","description":"One inclusive code-point range, as [low, high]. A single code point is written [n, n].","items":{"type":"integer","minimum":0,"maximum":1114111},"minItems":2,"maxItems":2},"widthOverride":{"type":"object","description":"A linegauge width override: the column count a named set of code-point ranges occupies, for a terminal that disagrees with the Unicode tables. Data only — no function, so it can be written in a config file, diffed, and printed by `linegauge check` without running anyone code.","required":["ranges","columns","why"],"additionalProperties":false,"properties":{"ranges":{"type":"array","description":"The code points this override applies to.","items":{"$ref":"#/$defs/widthRange"},"minItems":1},"columns":{"type":"integer","description":"Columns each cluster in those ranges occupies: 0 for a zero-width mark, 1 narrow, 2 wide.","minimum":0,"maximum":2},"why":{"type":"string","description":"The terminal or the reason. Required, because a width table with no provenance is one nobody can audit when it turns out to be wrong — which is the normal outcome for ambiguous width.","minLength":1}}},"widths":{"type":"object","description":"linegauge width overrides by name — the measurement section of a plugin, read the way flagstaff reads `spinners`.","additionalProperties":{"$ref":"#/$defs/widthOverride"}}}}
@@ -133,7 +133,9 @@ export async function runBurgee(program, opts) {
133
133
  await execute(program, {
134
134
  argv: rt.argv,
135
135
  env: rt.env,
136
- stdout: rt.stdout,
136
+ cwd: rt.cwd,
137
+ stdin: rt.stdin,
138
+ stdout: { write: rt.stdout.write, isTTY: rt.isTTY.stdout },
137
139
  stderr: rt.stderr,
138
140
  exit: (c) => rt.exit(c),
139
141
  root: program.rootPath,
@@ -11,6 +11,21 @@ export declare class UsageError extends Error {
11
11
  readonly hint?: string | undefined;
12
12
  constructor(message: string, hint?: string | undefined);
13
13
  }
14
+ /**
15
+ * E6 — the far side said no. Throw this and the run leaves with `ExitCode.AUTH`.
16
+ *
17
+ * The one error class whose *response* is unambiguous: not "read the message and decide" but
18
+ * "get a credential and run it again". A handler that throws a bare `Error` for a 401 gets
19
+ * `RUNTIME`, which is the code for everything, and a caller retrying on it retries forever.
20
+ *
21
+ * `fix` is the exact command that gets the credential, where the program knows it — `hint` is
22
+ * prose a person reads and `fix` is a line a caller runs, which is the turn the field saves.
23
+ */
24
+ export declare class AuthError extends Error {
25
+ readonly hint?: string | undefined;
26
+ readonly fix?: string | undefined;
27
+ constructor(message: string, hint?: string | undefined, fix?: string | undefined);
28
+ }
14
29
  type Sources = Record<string, {
15
30
  source: string;
16
31
  }>;
package/dist/validate.js CHANGED
@@ -6,6 +6,16 @@ export class UsageError extends Error {
6
6
  this.hint = hint;
7
7
  }
8
8
  }
9
+ export class AuthError extends Error {
10
+ hint;
11
+ fix;
12
+ constructor(message, hint, fix) {
13
+ super(message);
14
+ this.hint = hint;
15
+ this.fix = fix;
16
+ this.name = 'AuthError';
17
+ }
18
+ }
9
19
  const isSet = (values, key, sources) => values[key] !== undefined && sources[key]?.source !== 'default';
10
20
  const flagList = (keys) => flagsOf(keys).join(', ');
11
21
  function exactlyOne(keys, on) {
@@ -1,7 +1,6 @@
1
1
  var _a;
2
2
  import { ExitCode } from '../exit-code.js';
3
3
  import { Manifest } from '../manifest.js';
4
- import { serveMcp } from '../mcp.js';
5
4
  import { machineJson, schemaOf } from '../schema.js';
6
5
  import { tokenizeArgString } from '../yargs-parser.js';
7
6
  import { projectManifest, render } from './burgee.js';
@@ -1288,7 +1287,9 @@ export class YargsInstance {
1288
1287
  return { stdout: out.join(''), stderr: err.join(''), code };
1289
1288
  };
1290
1289
  const write = this.#burgee?.stdout ?? this.#shim.process.stdout();
1291
- return serveMcp(this.manifest, { input: this.#shim.process.stdin(), output: { write: (s) => void write.write(s) }, invoke }).then(() => {
1290
+ return import('../mcp.js')
1291
+ .then(async ({ serveMcp }) => serveMcp(this.manifest, { input: this.#shim.process.stdin(), output: { write: (s) => void write.write(s) }, invoke }))
1292
+ .then(() => {
1292
1293
  this.exit(0);
1293
1294
  return true;
1294
1295
  });
@@ -1315,6 +1316,10 @@ export class YargsInstance {
1315
1316
  .then(async (value) => {
1316
1317
  await manifest.fire('postRun', name, options);
1317
1318
  return settle(value);
1319
+ })
1320
+ .catch(async (cause) => {
1321
+ await manifest.fire('onError', name, options);
1322
+ throw cause;
1318
1323
  });
1319
1324
  }
1320
1325
  #settle(value, argv) {