diff --git a/.config/typedoc.js b/.config/typedoc.js index f2a503d..8daedcb 100644 --- a/.config/typedoc.js +++ b/.config/typedoc.js @@ -33,6 +33,14 @@ export default { favicon: '../site/media/favicon.svg', // @ts-expect-error from extras plugin footerLastModified: true, + kindSortOrder: [ + 'Reference', + 'Project', + 'Module', + 'Namespace', + 'Function', + 'TypeAlias', + ], lightHighlightTheme: 'vitesse-light', markdownLinkExternal: true, name: 'BARGS', diff --git a/README.md b/README.md index aaed53c..6fb46c8 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,7 @@ A CLI with an optional command and a couple options: ```typescript import { bargs, opt, pos } from '@boneskull/bargs'; -await bargs - .create('greet', { version: '1.0.0' }) +await bargs('greet', { version: '1.0.0' }) .globals( opt.options({ name: opt.string({ default: 'world' }), @@ -112,11 +111,10 @@ const parser = pos.positionals(pos.variadic('string', { name: 'text' }))( }), ); -const { values, positionals } = await bargs - .create('echo', { - description: 'Echo text to stdout', - version: '1.0.0', - }) +const { values, positionals } = await bargs('echo', { + description: 'Echo text to stdout', + version: '1.0.0', +}) .globals(parser) .parseAsync(); @@ -132,11 +130,10 @@ For a CLI with multiple subcommands: ```typescript import { bargs, merge, opt, pos } from '@boneskull/bargs'; -await bargs - .create('tasks', { - description: 'A task manager', - version: '1.0.0', - }) +await bargs('tasks', { + description: 'A task manager', + version: '1.0.0', +}) .globals( opt.options({ verbose: opt.boolean({ aliases: ['v'], default: false }), @@ -189,8 +186,7 @@ Commands can be nested to arbitrary depth by passing a `CliBuilder` as the secon import { bargs, opt, pos } from '@boneskull/bargs'; // Define subcommands as a separate builder -const remoteCommands = bargs - .create('remote') +const remoteCommands = bargs('remote') .command( 'add', pos.positionals( @@ -208,8 +204,7 @@ const remoteCommands = bargs .defaultCommand('add'); // Nest under parent CLI -await bargs - .create('git') +await bargs('git') .globals(opt.options({ verbose: opt.boolean({ aliases: ['v'] }) })) .command('remote', remoteCommands, 'Manage remotes') // ← CliBuilder .command('commit', commitParser, commitHandler) // ← Regular command @@ -227,7 +222,7 @@ Parent globals automatically flow to nested command handlers. You can nest as de ## API -### bargs.create(name, options?) +### bargs(name, options?) Create a CLI builder. @@ -243,7 +238,7 @@ Create a CLI builder. Set global options and transforms that apply to all commands. ```typescript -bargs.create('my-cli').globals(opt.options({ verbose: opt.boolean() })); +bargs('my-cli').globals(opt.options({ verbose: opt.boolean() })); // ... ``` @@ -268,9 +263,9 @@ Register a command. The handler receives merged global + command types. Register a nested command group. The `cliBuilder` is another `CliBuilder` whose commands become subcommands. Parent globals are passed down to nested handlers. ```typescript -const subCommands = bargs.create('sub').command('foo', ...).command('bar', ...); +const subCommands = bargs('sub').command('foo', ...).command('bar', ...); -bargs.create('main') +bargs('main') .command('nested', subCommands, 'Nested commands') // nested group .parseAsync(); @@ -304,11 +299,11 @@ Parse arguments and execute handlers. ```typescript // Async (supports async transforms/handlers) -const result = await bargs.create('my-cli').globals(...).parseAsync(); +const result = await bargs('my-cli').globals(...).parseAsync(); console.log(result.values, result.positionals, result.command); // Sync (no async transforms/handlers) -const result = bargs.create('my-cli').globals(...).parse(); +const result = bargs('my-cli').globals(...).parse(); ``` ## Option Helpers @@ -469,8 +464,7 @@ const globals = map( }), ); -await bargs - .create('my-cli') +await bargs('my-cli') .globals(globals) .command( 'info', @@ -513,8 +507,7 @@ If you prefer camelCase property names instead of kebab-case, use the `camelCase ```typescript import { bargs, map, opt, camelCaseValues } from '@boneskull/bargs'; -const { values } = await bargs - .create('my-cli') +const { values } = await bargs('my-cli') .globals( map( opt.options({ @@ -542,12 +535,12 @@ By default, **bargs** displays your package's homepage and repository URLs (from ```typescript // Custom epilog -bargs.create('my-cli', { +bargs('my-cli', { epilog: 'For more info, visit https://example.com', }); // Disable epilog entirely -bargs.create('my-cli', { epilog: false }); +bargs('my-cli', { epilog: false }); ``` ## Theming @@ -556,10 +549,10 @@ Customize help output colors with built-in themes or your own: ```typescript // Use a built-in theme: 'default', 'mono', 'ocean', 'warm' -bargs.create('my-cli', { theme: 'ocean' }); +bargs('my-cli', { theme: 'ocean' }); // Disable colors entirely -bargs.create('my-cli', { theme: 'mono' }); +bargs('my-cli', { theme: 'mono' }); ``` The `ansi` export provides common ANSI escape codes for styled terminal output: @@ -567,7 +560,7 @@ The `ansi` export provides common ANSI escape codes for styled terminal output: ```typescript import { ansi } from '@boneskull/bargs'; -bargs.create('my-cli', { +bargs('my-cli', { theme: { command: ansi.bold, flag: ansi.brightCyan, @@ -613,7 +606,7 @@ import { } from '@boneskull/bargs'; try { - await bargs.create('my-cli').parseAsync(); + await bargs('my-cli').parseAsync(); } catch (error) { if (error instanceof ValidationError) { // Config validation failed (e.g., invalid schema) diff --git a/examples/greeter.ts b/examples/greeter.ts index fa8d48f..f6c102c 100644 --- a/examples/greeter.ts +++ b/examples/greeter.ts @@ -45,11 +45,10 @@ const greetPositionals = pos.positionals( // Build and run the CLI // Using the (Parser, handler) form for full type inference of merged globals -await bargs - .create('greeter', { - description: 'A friendly greeter CLI', - version: '1.0.0', - }) +await bargs('greeter', { + description: 'A friendly greeter CLI', + version: '1.0.0', +}) .globals(globalOptions) // The (Parser, handler, description) form gives us merged global + command types! .command( diff --git a/examples/nested-commands.ts b/examples/nested-commands.ts index 0694da9..4286ac0 100644 --- a/examples/nested-commands.ts +++ b/examples/nested-commands.ts @@ -35,8 +35,7 @@ const config: Map = new Map([ // ═══════════════════════════════════════════════════════════════════════════════ // "remote" command group with subcommands: add, remove, list -const remoteCommands = bargs - .create('remote') +const remoteCommands = bargs('remote') .command( 'add', pos.positionals( @@ -96,8 +95,7 @@ const remoteCommands = bargs .defaultCommand('list'); // "config" command group with subcommands: get, set -const configCommands = bargs - .create('config') +const configCommands = bargs('config') .command( 'get', pos.positionals(pos.string({ name: 'key', required: true })), @@ -137,11 +135,10 @@ const globals = opt.options({ verbose: opt.boolean({ aliases: ['v'], default: false }), }); -await bargs - .create('git-like', { - description: 'A git-like CLI demonstrating nested commands', - version: '1.0.0', - }) +await bargs('git-like', { + description: 'A git-like CLI demonstrating nested commands', + version: '1.0.0', +}) .globals(globals) // Register nested command groups .command('remote', remoteCommands, 'Manage remotes') diff --git a/examples/tasks.ts b/examples/tasks.ts index e18dc38..76fb595 100644 --- a/examples/tasks.ts +++ b/examples/tasks.ts @@ -71,11 +71,10 @@ const doneParser = pos.positionals(pos.string({ name: 'id', required: true })); // Using (Parser, handler, description) form for full type inference! // ═══════════════════════════════════════════════════════════════════════════════ -await bargs - .create('tasks', { - description: 'A simple task manager', - version: '1.0.0', - }) +await bargs('tasks', { + description: 'A simple task manager', + version: '1.0.0', +}) .globals(globalOptions) // The handler receives merged global + command types .command( diff --git a/examples/transforms.ts b/examples/transforms.ts index 223d1a8..3d693a0 100644 --- a/examples/transforms.ts +++ b/examples/transforms.ts @@ -97,11 +97,10 @@ const infoParser = opt.options({}); // Using (Parser, handler, description) form for full type inference! // ═══════════════════════════════════════════════════════════════════════════════ -await bargs - .create('transforms-demo', { - description: 'Demonstrates transforms with commands', - version: '1.0.0', - }) +await bargs('transforms-demo', { + description: 'Demonstrates transforms with commands', + version: '1.0.0', +}) .globals(globals) // The handler receives merged global + command types .command( diff --git a/src/bargs.ts b/src/bargs.ts index c793d1b..60860ed 100644 --- a/src/bargs.ts +++ b/src/bargs.ts @@ -1,8 +1,8 @@ /** * Core bargs API using parser combinator pattern. * - * Provides `bargs.create()` for building CLIs with a fluent API, plus - * combinator functions like `pipe()`, `map()`, and `handle()`. + * Provides `bargs()` for building CLIs with a fluent API, plus combinator + * functions like `pipe()`, `map()`, and `handle()`. * * @packageDocumentation */ @@ -362,8 +362,7 @@ const kebabToCamel = (s: string): string => * ```typescript * import { bargs, opt, map, camelCaseValues } from '@boneskull/bargs'; * - * const { values } = await bargs - * .create('my-cli') + * const { values } = await bargs('my-cli') * .globals( * map(opt.options({ 'output-dir': opt.string() }), camelCaseValues), * ) @@ -396,8 +395,7 @@ export const camelCaseValues = ( * @example * * ```typescript - * const cli = await bargs - * .create('my-app', { version: '1.0.0' }) + * const cli = await bargs('my-app', { version: '1.0.0' }) * .globals( * map(opt.options({ verbose: opt.boolean() }), ({ values }) => ({ * values: { ...values, ts: Date.now() }, @@ -414,7 +412,7 @@ export const camelCaseValues = ( * * @function */ -const create = ( +export const bargs = ( name: string, options: CreateOptions = {}, ): CliBuilder, readonly []> => { @@ -1110,8 +1108,7 @@ const runWithCommands = ( }; /** - * Main bargs namespace. + * @ignore + * @deprecated */ -export const bargs = { - create, -}; +bargs.create = bargs; diff --git a/src/index.ts b/src/index.ts index 71cc95c..7471f65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,8 +8,7 @@ * ```typescript * import { bargs, opt, pos } from '@boneskull/bargs'; * - * await bargs - * .create('my-app', { version: '1.0.0' }) + * await bargs('my-app', { version: '1.0.0' }) * .globals(opt.options({ verbose: opt.boolean({ aliases: ['v'] }) })) * .command( * 'greet', diff --git a/src/theme.ts b/src/theme.ts index 7e13857..685c1ce 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -13,6 +13,8 @@ import { stripVTControlCharacters } from 'node:util'; /** * Strip all ANSI escape codes from a string. + * + * @function */ export const stripAnsi = stripVTControlCharacters; diff --git a/src/types.ts b/src/types.ts index 165a324..a15488d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -204,7 +204,7 @@ export interface CountOption extends OptionBase { } /** - * Options for bargs.create(). + * Options for bargs(). */ export interface CreateOptions { /** Description shown in help */ diff --git a/test/bargs.test.ts b/test/bargs.test.ts index 6a49d89..82502ad 100644 --- a/test/bargs.test.ts +++ b/test/bargs.test.ts @@ -9,9 +9,9 @@ import type { StringOption } from '../src/types.js'; import { bargs, handle, map } from '../src/bargs.js'; import { opt, pos } from '../src/opt.js'; -describe('bargs.create()', () => { +describe('bargs()', () => { it('creates a CLI builder', () => { - const cli = bargs.create('test-cli'); + const cli = bargs('test-cli'); expect(cli.globals, 'to be a', 'function'); expect(cli.command, 'to be a', 'function'); @@ -21,7 +21,7 @@ describe('bargs.create()', () => { }); it('accepts name and options', () => { - const cli = bargs.create('test-cli', { + const cli = bargs('test-cli', { description: 'A test CLI', version: '1.0.0', }); @@ -32,15 +32,15 @@ describe('bargs.create()', () => { describe('.globals()', () => { it('accepts a parser for global options', () => { - const cli = bargs - .create('test-cli') - .globals(opt.options({ verbose: opt.boolean() })); + const cli = bargs('test-cli').globals( + opt.options({ verbose: opt.boolean() }), + ); expect(cli.command, 'to be a', 'function'); }); it('returns a new builder (immutable)', () => { - const cli1 = bargs.create('test-cli'); + const cli1 = bargs('test-cli'); const cli2 = cli1.globals(opt.options({ verbose: opt.boolean() })); // Should be different objects @@ -50,7 +50,7 @@ describe('.globals()', () => { describe('.command()', () => { it('registers a command', () => { - const cli = bargs.create('test-cli').command( + const cli = bargs('test-cli').command( 'greet', handle(opt.options({ name: opt.string({ default: 'world' }) }), () => {}), ); @@ -59,7 +59,7 @@ describe('.command()', () => { }); it('accepts description as third argument', () => { - const cli = bargs.create('test-cli').command( + const cli = bargs('test-cli').command( 'greet', handle(opt.options({}), () => {}), 'Greet someone', @@ -69,8 +69,7 @@ describe('.command()', () => { }); it('is chainable', () => { - const cli = bargs - .create('test-cli') + const cli = bargs('test-cli') .command( 'cmd1', handle(opt.options({}), () => {}), @@ -86,8 +85,7 @@ describe('.command()', () => { describe('.defaultCommand()', () => { it('sets the default command', () => { - const cli = bargs - .create('test-cli') + const cli = bargs('test-cli') .command( 'greet', handle(opt.options({}), () => {}), @@ -100,7 +98,7 @@ describe('.defaultCommand()', () => { describe('.parseAsync()', () => { it('parses arguments with global options', async () => { - const cli = bargs.create('test-cli').globals( + const cli = bargs('test-cli').globals( opt.options({ name: opt.string({ default: 'world' }), verbose: opt.boolean({ default: false }), @@ -114,7 +112,7 @@ describe('.parseAsync()', () => { }); it('applies defaults when no args provided', async () => { - const cli = bargs.create('test-cli').globals( + const cli = bargs('test-cli').globals( opt.options({ name: opt.string({ default: 'world' }), verbose: opt.boolean({ default: false }), @@ -131,7 +129,7 @@ describe('.parseAsync()', () => { let handlerCalled = false; let handlerResult: unknown; - const cli = bargs.create('test-cli').command( + const cli = bargs('test-cli').command( 'greet', opt.options({ name: opt.string({ default: 'world' }) }), ({ values }) => { @@ -150,8 +148,7 @@ describe('.parseAsync()', () => { it('merges global and command options', async () => { let handlerResult: unknown; - const cli = bargs - .create('test-cli') + const cli = bargs('test-cli') .globals(opt.options({ verbose: opt.boolean({ default: false }) })) .command( 'greet', @@ -172,8 +169,7 @@ describe('.parseAsync()', () => { it('uses default command when no command specified', async () => { let handlerCalled = false; - const cli = bargs - .create('test-cli') + const cli = bargs('test-cli') .command( 'greet', handle(opt.options({}), () => { @@ -188,7 +184,7 @@ describe('.parseAsync()', () => { }); it('throws on unknown command', async () => { - const cli = bargs.create('test-cli').command( + const cli = bargs('test-cli').command( 'greet', handle(opt.options({}), () => {}), ); @@ -201,13 +197,11 @@ describe('.parseAsync()', () => { }); it('returns parsed result with command name', async () => { - const cli = bargs - .create('test-cli') - .command( - 'greet', - opt.options({ name: opt.string({ default: 'world' }) }), - () => {}, - ); + const cli = bargs('test-cli').command( + 'greet', + opt.options({ name: opt.string({ default: 'world' }) }), + () => {}, + ); const result = await cli.parseAsync(['greet', '--name', 'Test']); @@ -220,8 +214,7 @@ describe('transforms via map()', () => { it('applies global transforms', async () => { let handlerResult: unknown; - const cli = bargs - .create('test-cli') + const cli = bargs('test-cli') .globals( map( opt.options({ name: opt.string({ default: 'world' }) }), @@ -273,8 +266,7 @@ describe('transforms via map()', () => { return { ...parser, __brand: 'Parser' as const, __transform: transform }; }; - const cli = bargs - .create('test-cli') + const cli = bargs('test-cli') .globals( asyncTransform( opt.options({ name: opt.string({ default: 'world' }) }), @@ -298,7 +290,7 @@ describe('transforms via map()', () => { describe('.parse() (sync)', () => { it('parses synchronously when no async transforms/handlers', () => { - const cli = bargs.create('test-cli').globals( + const cli = bargs('test-cli').globals( opt.options({ name: opt.string({ default: 'world' }), }), @@ -318,13 +310,13 @@ describe('.parse() (sync)', () => { }, ); - const cli = bargs.create('test-cli').globals(asyncParser); + const cli = bargs('test-cli').globals(asyncParser); expect(() => cli.parse([]), 'to throw', /Async.*transform.*Use parseAsync/); }); it('throws on async handler', () => { - const cli = bargs.create('test-cli').command( + const cli = bargs('test-cli').command( 'greet', handle(opt.options({}), async () => { await Promise.resolve(); @@ -343,15 +335,13 @@ describe('positionals', () => { it('parses positional arguments', async () => { let handlerResult: unknown; - const cli = bargs - .create('test-cli') - .command( - 'echo', - pos.positionals(pos.string({ name: 'message', required: true })), - ({ positionals }) => { - handlerResult = positionals; - }, - ); + const cli = bargs('test-cli').command( + 'echo', + pos.positionals(pos.string({ name: 'message', required: true })), + ({ positionals }) => { + handlerResult = positionals; + }, + ); await cli.parseAsync(['echo', 'Hello, world!']); @@ -361,18 +351,16 @@ describe('positionals', () => { it('handles multiple positionals', async () => { let handlerResult: unknown; - const cli = bargs - .create('test-cli') - .command( - 'copy', - pos.positionals( - pos.string({ name: 'source', required: true }), - pos.string({ name: 'dest', required: true }), - ), - ({ positionals }) => { - handlerResult = positionals; - }, - ); + const cli = bargs('test-cli').command( + 'copy', + pos.positionals( + pos.string({ name: 'source', required: true }), + pos.string({ name: 'dest', required: true }), + ), + ({ positionals }) => { + handlerResult = positionals; + }, + ); await cli.parseAsync(['copy', 'src.txt', 'dst.txt']); @@ -382,15 +370,13 @@ describe('positionals', () => { it('handles variadic positionals', async () => { let handlerResult: unknown; - const cli = bargs - .create('test-cli') - .command( - 'concat', - pos.positionals(pos.variadic('string', { name: 'files' })), - ({ positionals }) => { - handlerResult = positionals; - }, - ); + const cli = bargs('test-cli').command( + 'concat', + pos.positionals(pos.variadic('string', { name: 'files' })), + ({ positionals }) => { + handlerResult = positionals; + }, + ); await cli.parseAsync(['concat', 'a.txt', 'b.txt', 'c.txt']); @@ -402,7 +388,7 @@ describe('nested commands (subcommands)', () => { it('supports nested commands via CliBuilder', async () => { let result: unknown; - const remoteCommands = bargs.create('remote').command( + const remoteCommands = bargs('remote').command( 'add', pos.positionals( pos.string({ name: 'name', required: true }), @@ -414,9 +400,11 @@ describe('nested commands (subcommands)', () => { 'Add a remote', ); - const cli = bargs - .create('git') - .command('remote', remoteCommands, 'Manage remotes'); + const cli = bargs('git').command( + 'remote', + remoteCommands, + 'Manage remotes', + ); await cli.parseAsync(['remote', 'add', 'origin', 'https://github.com/...']); @@ -429,25 +417,27 @@ describe('nested commands (subcommands)', () => { it('supports deeply nested commands', async () => { let result: unknown; - const setCommands = bargs - .create('set') - .command( - 'url', - pos.positionals(pos.string({ name: 'url', required: true })), - ({ positionals }) => { - result = { command: 'remote origin set url', positionals }; - }, - ); + const setCommands = bargs('set').command( + 'url', + pos.positionals(pos.string({ name: 'url', required: true })), + ({ positionals }) => { + result = { command: 'remote origin set url', positionals }; + }, + ); - const originCommands = bargs - .create('origin') - .command('set', setCommands, 'Set properties'); + const originCommands = bargs('origin').command( + 'set', + setCommands, + 'Set properties', + ); - const remoteCommands = bargs - .create('remote') - .command('origin', originCommands, 'Manage origin'); + const remoteCommands = bargs('remote').command( + 'origin', + originCommands, + 'Manage origin', + ); - const cli = bargs.create('git').command('remote', remoteCommands); + const cli = bargs('git').command('remote', remoteCommands); await cli.parseAsync(['remote', 'origin', 'set', 'url', 'https://new.url']); @@ -460,18 +450,15 @@ describe('nested commands (subcommands)', () => { it('passes parent globals to nested command handlers', async () => { let result: unknown; - const remoteCommands = bargs - .create('remote') - .command( - 'add', - pos.positionals(pos.string({ name: 'name', required: true })), - ({ positionals, values }) => { - result = { positionals, values }; - }, - ); + const remoteCommands = bargs('remote').command( + 'add', + pos.positionals(pos.string({ name: 'name', required: true })), + ({ positionals, values }) => { + result = { positionals, values }; + }, + ); - const cli = bargs - .create('git') + const cli = bargs('git') .globals(opt.options({ verbose: opt.boolean({ aliases: ['v'] }) })) .command('remote', remoteCommands); @@ -486,8 +473,7 @@ describe('nested commands (subcommands)', () => { it('runs default subcommand when no subcommand specified', async () => { let result: unknown; - const remoteCommands = bargs - .create('remote') + const remoteCommands = bargs('remote') .command( 'list', opt.options({}), @@ -501,7 +487,7 @@ describe('nested commands (subcommands)', () => { }) .defaultCommand('list'); - const cli = bargs.create('git').command('remote', remoteCommands); + const cli = bargs('git').command('remote', remoteCommands); await cli.parseAsync(['remote']); @@ -509,14 +495,15 @@ describe('nested commands (subcommands)', () => { }); it('generates help listing nested command', () => { - const remoteCommands = bargs - .create('remote') + const remoteCommands = bargs('remote') .command('add', opt.options({}), () => {}, 'Add a remote') .command('remove', opt.options({}), () => {}, 'Remove a remote'); - const cli = bargs - .create('git') - .command('remote', remoteCommands, 'Manage remotes'); + const cli = bargs('git').command( + 'remote', + remoteCommands, + 'Manage remotes', + ); // The parent CLI should list 'remote' as a command with its description // Note: The actual help generation for nested commands is handled by the