Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const config: ReturnType<typeof defineConfig> = defineConfig(
'./bench/**',
'./playground/**',
'./packages/docs/**',
'./packages/**/examples/**',
'./**/test/**',
'./**/src/**/*.test.ts',
'./**/src/**/*.test-d.ts'
Expand Down
3 changes: 3 additions & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default {
'packages/gunshi': {
entry: ['src/constants.ts']
},
'packages/plugin-completion': {
ignore: ['examples/**/*.ts']
},
'packages/docs': {
entry: ['src/.vitepress/config.ts', 'src/.vitepress/theme/index.ts']
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"build:docs": "pnpm -r build:docs",
"build:gunshi": "pnpm -F gunshi build",
"build:plugin": "pnpm -F @gunshi/bone build",
"build:plugin:completion": "pnpm -F @gunshi/plugin-completion build",
"build:plugin:global": "pnpm -F @gunshi/plugin-global build",
"build:plugin:i18n": "pnpm -F @gunshi/plugin-i18n build",
"build:plugin:renderer": "pnpm -F @gunshi/plugin-renderer build",
Expand Down
26 changes: 22 additions & 4 deletions packages/gunshi/src/__snapshots__/cli.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ OPTIONS:

exports[`auto generate usage > loosely sub commands > command2 1`] = `
"USAGE:
COMMAND command2 <OPTIONS>
my-cli command2 <OPTIONS>

OPTIONS:
-h, --help Display this help message
Expand All @@ -68,7 +68,16 @@ OPTIONS:

exports[`auto generate usage > loosely sub commands > main 1`] = `
"USAGE:
COMMAND <OPTIONS>
my-cli [command1] <OPTIONS>
my-cli <COMMANDS>

COMMANDS:
command2
command1

For more info, run any command with the \`--help\` flag:
my-cli command2 --help
my-cli command1 --help

OPTIONS:
-h, --help Display this help message
Expand All @@ -79,15 +88,24 @@ OPTIONS:

exports[`auto generate usage > loosely sub commands 1`] = `
"USAGE:
COMMAND <OPTIONS>
my-cli [command1] <OPTIONS>
my-cli <COMMANDS>

COMMANDS:
command2
command1

For more info, run any command with the \`--help\` flag:
my-cli command2 --help
my-cli command1 --help

OPTIONS:
-h, --help Display this help message
-v, --version Display this version
-f, --foo <foo>

USAGE:
COMMAND command2 <OPTIONS>
my-cli command2 <OPTIONS>

OPTIONS:
-h, --help Display this help message
Expand Down
11 changes: 9 additions & 2 deletions packages/gunshi/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ describe('auto generate usage', () => {
test('loosely sub commands', async () => {
const utils = await import('./utils.ts')
const log = defineMockLog(utils)
const meta = {
name: 'my-cli',
renderHeader: null // no header
}

const entryArgs = {
foo: {
Expand All @@ -398,6 +402,7 @@ describe('auto generate usage', () => {
}
} satisfies Args
const entry = {
name: 'command1',
args: entryArgs,
run: vi.fn()
} satisfies Command<GunshiParams<{ args: typeof entryArgs }>>
Expand All @@ -417,8 +422,10 @@ describe('auto generate usage', () => {
const subCommands = new Map()
subCommands.set('command2', command2)

expect(await cli(['-h'], entry, { subCommands })).toMatchSnapshot('main')
expect(await cli(['command2', '-h'], entry, { subCommands })).toMatchSnapshot('command2')
expect(await cli(['-h'], entry, { ...meta, subCommands })).toMatchSnapshot('main')
expect(await cli(['command2', '-h'], entry, { ...meta, subCommands })).toMatchSnapshot(
'command2'
)

const message = log()
expect(message).toMatchSnapshot()
Expand Down
28 changes: 16 additions & 12 deletions packages/gunshi/src/cli/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { parseArgs, resolveArgs } from 'args-tokens'
import { ANONYMOUS_COMMAND_NAME, COMMAND_OPTIONS_DEFAULT, NOOP } from '../constants.ts'
import { ANONYMOUS_COMMAND_NAME, CLI_OPTIONS_DEFAULT, NOOP } from '../constants.ts'
import { createCommandContext } from '../context.ts'
import { createDecorators } from '../decorators.ts'
import { createPluginContext } from '../plugin/context.ts'
Expand Down Expand Up @@ -131,17 +131,17 @@ function resolveArguments<G extends GunshiParamsConstraint>(

function createInitialSubCommands<G extends GunshiParamsConstraint>(
options: CliOptions<G>,
entry: Command<G> | CommandRunner<G> | LazyCommand<G>
entryCmd: Command<G> | CommandRunner<G> | LazyCommand<G>
): Map<string, Command<G> | LazyCommand<G>> {
const subCommands = new Map(options.subCommands)

// add entry command to sub commands if there are sub commands
if (options.subCommands || subCommands.size > 0) {
if (isLazyCommand(entry)) {
if (entry.commandName) subCommands.set(entry.commandName, entry as LazyCommand<G>)
} else if (typeof entry === 'object' && entry.name) {
subCommands.set(entry.name, entry as Command<G>)
}
if (
(options.subCommands || subCommands.size > 0) &&
(isLazyCommand(entryCmd) || typeof entryCmd === 'object')
) {
entryCmd.entry = true
subCommands.set(resolveEntryName(entryCmd as LazyCommand<G> | Command<G>), entryCmd)
}

return subCommands
Expand All @@ -155,7 +155,7 @@ function normalizeCliOptions<G extends GunshiParamsConstraint>(
// get the latest sub commands from plugin context (already includes entry command)
const subCommands = new Map(pluginContext.subCommands)

const resolvedOptions = Object.assign(create<CliOptions<G>>(), COMMAND_OPTIONS_DEFAULT, options, {
const resolvedOptions = Object.assign(create<CliOptions<G>>(), CLI_OPTIONS_DEFAULT, options, {
subCommands
}) as CliOptions<G>

Expand Down Expand Up @@ -209,7 +209,7 @@ async function resolveCommand<G extends GunshiParamsConstraint>(
} else {
// inline command (command runner)
return {
command: { run: entry as CommandRunner<G> } as Command<G>,
command: { run: entry as CommandRunner<G>, entry: true } as Command<G>,
callMode: 'entry'
}
}
Expand Down Expand Up @@ -251,8 +251,12 @@ async function resolveCommand<G extends GunshiParamsConstraint>(
}
}

function resolveEntryName<G extends GunshiParamsConstraint>(entry: Command<G>): string {
return entry.name || ANONYMOUS_COMMAND_NAME
function resolveEntryName<G extends GunshiParamsConstraint>(
entry: Command<G> | LazyCommand<G>
): string {
return isLazyCommand<G>(entry)
? entry.commandName || ANONYMOUS_COMMAND_NAME
: entry.name || ANONYMOUS_COMMAND_NAME
}

function getPluginExtensions(plugins: Plugin[]): Record<string, CommandContextExtension> {
Expand Down
2 changes: 1 addition & 1 deletion packages/gunshi/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const ANONYMOUS_COMMAND_NAME = '(anonymous)'

export const NOOP: () => void = () => {}

export const COMMAND_OPTIONS_DEFAULT: CliOptions<DefaultGunshiParams> = {
export const CLI_OPTIONS_DEFAULT: CliOptions<DefaultGunshiParams> = {
name: undefined,
description: undefined,
version: undefined,
Expand Down
4 changes: 2 additions & 2 deletions packages/gunshi/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @license MIT
*/

import { ANONYMOUS_COMMAND_NAME, COMMAND_OPTIONS_DEFAULT, NOOP } from './constants.ts'
import { ANONYMOUS_COMMAND_NAME, CLI_OPTIONS_DEFAULT, NOOP } from './constants.ts'
import { create, deepFreeze, isLazyCommand, log } from './utils.ts'

import type {
Expand Down Expand Up @@ -148,7 +148,7 @@ export async function createCommandContext<
* setup the environment
*/

const env = Object.assign(create<CommandEnvironment<G>>(), COMMAND_OPTIONS_DEFAULT, cliOptions)
const env = Object.assign(create<CommandEnvironment<G>>(), CLI_OPTIONS_DEFAULT, cliOptions)

/**
* create the command context
Expand Down
1 change: 1 addition & 0 deletions packages/gunshi/src/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export function lazy<G extends GunshiParamsConstraint = DefaultGunshiParams>(
lazyCommand.args = definition.args
lazyCommand.examples = definition.examples
lazyCommand.internal = definition.internal
lazyCommand.entry = definition.entry
// @ts-ignore - resource property is now provided by plugin-i18n
if ('resource' in definition) {
// @ts-ignore
Expand Down
2 changes: 2 additions & 0 deletions packages/gunshi/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* @license MIT
*/

export { CLI_OPTIONS_DEFAULT } from './constants.ts'
export { createCommandContext } from './context.ts'
export { plugin } from './plugin/core.ts'

export type { PluginContext } from './plugin/context.ts'
Expand Down
6 changes: 6 additions & 0 deletions packages/gunshi/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ export interface Command<G extends GunshiParamsConstraint = DefaultGunshiParams>
* @since v0.27.0
*/
internal?: boolean
/**
* Whether this command is an entry command.
* @default undefined
* @since v0.27.0
*/
entry?: boolean
}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/gunshi/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export async function resolveLazyCommand<G extends GunshiParamsConstraint = Defa
description: cmd.description,
args: cmd.args,
examples: cmd.examples,
internal: cmd.internal
internal: cmd.internal,
entry: cmd.entry
}
if ('resource' in cmd && cmd.resource) {
baseCommand.resource = cmd.resource
Expand All @@ -52,6 +53,7 @@ export async function resolveLazyCommand<G extends GunshiParamsConstraint = Defa
command.args = loaded.args
command.examples = loaded.examples
command.internal = loaded.internal
command.entry = loaded.entry
if ('resource' in loaded && loaded.resource) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(command as any).resource = loaded.resource
Expand Down
126 changes: 126 additions & 0 deletions packages/plugin-completion/examples/demo.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { cli, define } from 'gunshi'
import completion from '../src/index.ts'

const entry = define({
// name: 'root',
args: {
config: {
type: 'string',
description: 'Use specified config file',
short: 'c'
},
mode: {
type: 'string',
description: 'Set env mode',
short: 'm'
},
logLevel: {
type: 'string',
description: 'info | warn | error | silent',
short: 'l'
}
},
run: _ctx => {}
})

const dev = define({
name: 'dev',
description: 'Start dev server',
args: {
host: {
type: 'string',
description: 'Specify hostname',
short: 'H'
},
port: {
type: 'number',
description: 'Specify port',
short: 'p'
}
},
run: () => {}
})

const build = define({
name: 'build',
description: 'Build project',
run: () => {}
})

const lint = define({
name: 'lint',
description: 'Lint project',
args: {
files: {
type: 'positional',
description: 'Files to lint'
}
},
run: () => {}
})

const subCommands = new Map<string, ReturnType<typeof define>>()
subCommands.set('dev', dev)
subCommands.set('build', build)
subCommands.set('lint', lint)

await cli(process.argv.slice(2), entry, {
name: 'vite',
version: '0.0.0',
description: 'Vite CLI',
subCommands,
plugins: [
completion({
config: {
entry: {
args: {
config: {
handler: () => [
{ value: 'vite.config.ts', description: 'Vite config file' },
{ value: 'vite.config.js', description: 'Vite config file' }
]
},
mode: {
handler: () => [
{ value: 'development', description: 'Development mode' },
{ value: 'production', description: 'Production mode' }
]
},
logLevel: {
handler: () => [
{ value: 'info', description: 'Info level' },
{ value: 'warn', description: 'Warn level' },
{ value: 'error', description: 'Error level' },
{ value: 'silent', description: 'Silent level' }
]
}
}
},
subCommands: {
lint: {
handler: () => [
{ value: 'main.ts', description: 'Main file' },
{ value: 'index.ts', description: 'Index file' }
]
},
dev: {
args: {
port: {
handler: () => [
{ value: '3000', description: 'Development server port' },
{ value: '8080', description: 'Alternative port' }
]
},
host: {
handler: () => [
{ value: 'localhost', description: 'Localhost' },
{ value: '0.0.0.0', description: 'All interfaces' }
]
}
}
}
}
}
})
]
})
5 changes: 4 additions & 1 deletion packages/plugin-completion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@
"build": "tsdown",
"lint:jsr": "jsr publish --dry-run --allow-dirty",
"prepack": "pnpm build",
"typecheck:deno": "deno check ./src"
"typecheck:deno": "deno check --import-map=../../importmap.json ./src"
},
"dependencies": {
"@bombsh/tab": "https://pkg.pr.new/bombshell-dev/tab@main",
"@gunshi/plugin": "workspace:*"
},
"devDependencies": {
"@gunshi/shared": "workspace:*",
"@types/node": "catalog:",
"deno": "catalog:",
"jsr": "catalog:",
"jsr-exports-lint": "catalog:",
Expand Down
Loading