From f8069559883ee81a8dfa8559698c98b256768fa7 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Fri, 2 May 2025 18:11:03 +0330 Subject: [PATCH 01/11] init --- bin/cli.ts | 82 ++++++++++++++++++++++++++++++++++++++ bin/completion-handlers.ts | 56 ++++++++++++++++++++++++++ package.json | 9 +++-- tsdown.config.ts | 9 ++++- 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 bin/cli.ts create mode 100644 bin/completion-handlers.ts diff --git a/bin/cli.ts b/bin/cli.ts new file mode 100644 index 0000000..c42ea59 --- /dev/null +++ b/bin/cli.ts @@ -0,0 +1,82 @@ +#!/usr/bin/env node + +import cac from 'cac'; +import { script, Completion } from '../src/index.js'; +import tab from '../src/cac.js'; + +import { + setupCompletionForPackageManager +} from './completion-handlers'; + +const packageManagers = ['npm', 'pnpm', 'yarn', 'bun']; +const shells = ['zsh', 'bash', 'fish', 'powershell']; + +const cli = cac('tab'); + +cli.command(' complete', 'Process completion requests from shell') + .allowUnknownOptions() + .action(async (packageManager) => { + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error(`Supported package managers: ${packageManagers.join(', ')}`); + process.exit(1); + } + + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error(`Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete`); + process.exit(1); + } + }); + +cli.command(' ', 'Generate shell completion script for a package manager') + .action(async (packageManager, shell) => { + if (shell === 'complete') { + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error(`Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete`); + process.exit(1); + } + return; + } + + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error(`Supported package managers: ${packageManagers.join(', ')}`); + process.exit(1); + } + + if (!shells.includes(shell)) { + console.error(`Error: Unsupported shell "${shell}"`); + console.error(`Supported shells: ${shells.join(', ')}`); + process.exit(1); + } + + generateCompletionScript(packageManager, shell); + }); + +const completion = tab(cli); + +cli.parse(); + +function generateCompletionScript(packageManager: string, shell: string) { + const name = packageManager; + const executable = process.env.npm_execpath ? + `${packageManager} exec @bombsh/tab ${packageManager}` : + `node ${process.argv[1]} ${packageManager}`; + script(shell as any, name, executable); +} \ No newline at end of file diff --git a/bin/completion-handlers.ts b/bin/completion-handlers.ts new file mode 100644 index 0000000..8f3b109 --- /dev/null +++ b/bin/completion-handlers.ts @@ -0,0 +1,56 @@ +import { Completion } from '../src/index.js'; + +export function setupCompletionForPackageManager(packageManager: string, completion: Completion) { + if (packageManager === 'pnpm') { + setupPnpmCompletions(completion); + } else if (packageManager === 'npm') { + setupNpmCompletions(completion); + } else if (packageManager === 'yarn') { + setupYarnCompletions(completion); + } else if (packageManager === 'bun') { + setupBunCompletions(completion); + } +} + +export function setupPnpmCompletions(completion: Completion) { + completion.addCommand('add', 'Install a package', [], async () => []); + completion.addCommand('remove', 'Remove a package', [], async () => []); + completion.addCommand('install', 'Install all dependencies', [], async () => []); + completion.addCommand('update', 'Update packages', [], async () => []); + completion.addCommand('exec', 'Execute a command', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('publish', 'Publish package', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); +} + +export function setupNpmCompletions(completion: Completion) { + completion.addCommand('install', 'Install a package', [], async () => []); + completion.addCommand('uninstall', 'Uninstall a package', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('publish', 'Publish package', [], async () => []); + completion.addCommand('update', 'Update packages', [], async () => []); + completion.addCommand('start', 'Start the application', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); +} + +export function setupYarnCompletions(completion: Completion) { + completion.addCommand('add', 'Add a package', [], async () => []); + completion.addCommand('remove', 'Remove a package', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('publish', 'Publish package', [], async () => []); + completion.addCommand('install', 'Install dependencies', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); +} + +export function setupBunCompletions(completion: Completion) { + completion.addCommand('add', 'Add a package', [], async () => []); + completion.addCommand('remove', 'Remove a package', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('install', 'Install dependencies', [], async () => []); + completion.addCommand('update', 'Update packages', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); +} \ No newline at end of file diff --git a/package.json b/package.json index ea3ff32..1ab841b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { - "name": "tab", + "name": "@bombsh/tab", "version": "0.0.0", - "description": "", "main": "./dist/index.js", "types": "./dist/index.d.ts", "type": "module", + "bin": { + "tab": "./dist/bin/cli.js" + }, "scripts": { "test": "vitest", "type-check": "tsc --noEmit", @@ -12,7 +14,8 @@ "format:check": "prettier --check .", "build": "tsdown", "prepare": "pnpm build", - "lint": "eslint src \"./*.ts\"" + "lint": "eslint src \"./*.ts\"", + "test-cli": "tsx bin/cli.ts" }, "files": [ "dist" diff --git a/tsdown.config.ts b/tsdown.config.ts index c4141b9..460913e 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -1,7 +1,14 @@ import { defineConfig } from 'tsdown'; export default defineConfig({ - entry: ['src/index.ts', 'src/citty.ts', 'src/cac.ts', 'src/commander.ts'], + entry: [ + 'src/index.ts', + 'src/citty.ts', + 'src/cac.ts', + 'src/commander.ts', + 'bin/cli.ts', + 'bin/completion-handlers.ts' + ], format: ['esm'], dts: true, clean: true, From 6d7ba3f22600ac3546097bffde7b23d829fb8180 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Fri, 2 May 2025 18:12:53 +0330 Subject: [PATCH 02/11] prettier --- bin/cli.ts | 132 ++++++++++++++++++++----------------- bin/completion-handlers.ts | 92 ++++++++++++++------------ tsdown.config.ts | 2 +- 3 files changed, 124 insertions(+), 102 deletions(-) diff --git a/bin/cli.ts b/bin/cli.ts index c42ea59..1e1002e 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -4,79 +4,93 @@ import cac from 'cac'; import { script, Completion } from '../src/index.js'; import tab from '../src/cac.js'; -import { - setupCompletionForPackageManager -} from './completion-handlers'; +import { setupCompletionForPackageManager } from './completion-handlers'; const packageManagers = ['npm', 'pnpm', 'yarn', 'bun']; const shells = ['zsh', 'bash', 'fish', 'powershell']; const cli = cac('tab'); -cli.command(' complete', 'Process completion requests from shell') - .allowUnknownOptions() - .action(async (packageManager) => { - if (!packageManagers.includes(packageManager)) { - console.error(`Error: Unsupported package manager "${packageManager}"`); - console.error(`Supported package managers: ${packageManagers.join(', ')}`); - process.exit(1); - } +cli + .command( + ' complete', + 'Process completion requests from shell' + ) + .allowUnknownOptions() + .action(async (packageManager) => { + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error( + `Supported package managers: ${packageManagers.join(', ')}` + ); + process.exit(1); + } - const dashIndex = process.argv.indexOf('--'); - if (dashIndex !== -1) { - const completion = new Completion(); - setupCompletionForPackageManager(packageManager, completion); - const toComplete = process.argv.slice(dashIndex + 1); - await completion.parse(toComplete); - process.exit(0); - } else { - console.error(`Error: Expected '--' followed by command to complete`); - console.error(`Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete`); - process.exit(1); - } - }); + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error( + `Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete` + ); + process.exit(1); + } + }); -cli.command(' ', 'Generate shell completion script for a package manager') - .action(async (packageManager, shell) => { - if (shell === 'complete') { - const dashIndex = process.argv.indexOf('--'); - if (dashIndex !== -1) { - const completion = new Completion(); - setupCompletionForPackageManager(packageManager, completion); - const toComplete = process.argv.slice(dashIndex + 1); - await completion.parse(toComplete); - process.exit(0); - } else { - console.error(`Error: Expected '--' followed by command to complete`); - console.error(`Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete`); - process.exit(1); - } - return; - } +cli + .command( + ' ', + 'Generate shell completion script for a package manager' + ) + .action(async (packageManager, shell) => { + if (shell === 'complete') { + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error( + `Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete` + ); + process.exit(1); + } + return; + } - if (!packageManagers.includes(packageManager)) { - console.error(`Error: Unsupported package manager "${packageManager}"`); - console.error(`Supported package managers: ${packageManagers.join(', ')}`); - process.exit(1); - } + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error( + `Supported package managers: ${packageManagers.join(', ')}` + ); + process.exit(1); + } - if (!shells.includes(shell)) { - console.error(`Error: Unsupported shell "${shell}"`); - console.error(`Supported shells: ${shells.join(', ')}`); - process.exit(1); - } + if (!shells.includes(shell)) { + console.error(`Error: Unsupported shell "${shell}"`); + console.error(`Supported shells: ${shells.join(', ')}`); + process.exit(1); + } - generateCompletionScript(packageManager, shell); - }); + generateCompletionScript(packageManager, shell); + }); const completion = tab(cli); cli.parse(); function generateCompletionScript(packageManager: string, shell: string) { - const name = packageManager; - const executable = process.env.npm_execpath ? - `${packageManager} exec @bombsh/tab ${packageManager}` : - `node ${process.argv[1]} ${packageManager}`; - script(shell as any, name, executable); -} \ No newline at end of file + const name = packageManager; + const executable = process.env.npm_execpath + ? `${packageManager} exec @bombsh/tab ${packageManager}` + : `node ${process.argv[1]} ${packageManager}`; + script(shell as any, name, executable); +} diff --git a/bin/completion-handlers.ts b/bin/completion-handlers.ts index 8f3b109..9e276b6 100644 --- a/bin/completion-handlers.ts +++ b/bin/completion-handlers.ts @@ -1,56 +1,64 @@ import { Completion } from '../src/index.js'; -export function setupCompletionForPackageManager(packageManager: string, completion: Completion) { - if (packageManager === 'pnpm') { - setupPnpmCompletions(completion); - } else if (packageManager === 'npm') { - setupNpmCompletions(completion); - } else if (packageManager === 'yarn') { - setupYarnCompletions(completion); - } else if (packageManager === 'bun') { - setupBunCompletions(completion); - } +export function setupCompletionForPackageManager( + packageManager: string, + completion: Completion +) { + if (packageManager === 'pnpm') { + setupPnpmCompletions(completion); + } else if (packageManager === 'npm') { + setupNpmCompletions(completion); + } else if (packageManager === 'yarn') { + setupYarnCompletions(completion); + } else if (packageManager === 'bun') { + setupBunCompletions(completion); + } } export function setupPnpmCompletions(completion: Completion) { - completion.addCommand('add', 'Install a package', [], async () => []); - completion.addCommand('remove', 'Remove a package', [], async () => []); - completion.addCommand('install', 'Install all dependencies', [], async () => []); - completion.addCommand('update', 'Update packages', [], async () => []); - completion.addCommand('exec', 'Execute a command', [], async () => []); - completion.addCommand('run', 'Run a script', [], async () => []); - completion.addCommand('publish', 'Publish package', [], async () => []); - completion.addCommand('test', 'Run tests', [], async () => []); - completion.addCommand('build', 'Build project', [], async () => []); + completion.addCommand('add', 'Install a package', [], async () => []); + completion.addCommand('remove', 'Remove a package', [], async () => []); + completion.addCommand( + 'install', + 'Install all dependencies', + [], + async () => [] + ); + completion.addCommand('update', 'Update packages', [], async () => []); + completion.addCommand('exec', 'Execute a command', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('publish', 'Publish package', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); } export function setupNpmCompletions(completion: Completion) { - completion.addCommand('install', 'Install a package', [], async () => []); - completion.addCommand('uninstall', 'Uninstall a package', [], async () => []); - completion.addCommand('run', 'Run a script', [], async () => []); - completion.addCommand('test', 'Run tests', [], async () => []); - completion.addCommand('publish', 'Publish package', [], async () => []); - completion.addCommand('update', 'Update packages', [], async () => []); - completion.addCommand('start', 'Start the application', [], async () => []); - completion.addCommand('build', 'Build project', [], async () => []); + completion.addCommand('install', 'Install a package', [], async () => []); + completion.addCommand('uninstall', 'Uninstall a package', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('publish', 'Publish package', [], async () => []); + completion.addCommand('update', 'Update packages', [], async () => []); + completion.addCommand('start', 'Start the application', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); } export function setupYarnCompletions(completion: Completion) { - completion.addCommand('add', 'Add a package', [], async () => []); - completion.addCommand('remove', 'Remove a package', [], async () => []); - completion.addCommand('run', 'Run a script', [], async () => []); - completion.addCommand('test', 'Run tests', [], async () => []); - completion.addCommand('publish', 'Publish package', [], async () => []); - completion.addCommand('install', 'Install dependencies', [], async () => []); - completion.addCommand('build', 'Build project', [], async () => []); + completion.addCommand('add', 'Add a package', [], async () => []); + completion.addCommand('remove', 'Remove a package', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('publish', 'Publish package', [], async () => []); + completion.addCommand('install', 'Install dependencies', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); } export function setupBunCompletions(completion: Completion) { - completion.addCommand('add', 'Add a package', [], async () => []); - completion.addCommand('remove', 'Remove a package', [], async () => []); - completion.addCommand('run', 'Run a script', [], async () => []); - completion.addCommand('test', 'Run tests', [], async () => []); - completion.addCommand('install', 'Install dependencies', [], async () => []); - completion.addCommand('update', 'Update packages', [], async () => []); - completion.addCommand('build', 'Build project', [], async () => []); -} \ No newline at end of file + completion.addCommand('add', 'Add a package', [], async () => []); + completion.addCommand('remove', 'Remove a package', [], async () => []); + completion.addCommand('run', 'Run a script', [], async () => []); + completion.addCommand('test', 'Run tests', [], async () => []); + completion.addCommand('install', 'Install dependencies', [], async () => []); + completion.addCommand('update', 'Update packages', [], async () => []); + completion.addCommand('build', 'Build project', [], async () => []); +} diff --git a/tsdown.config.ts b/tsdown.config.ts index 460913e..1633327 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ 'src/cac.ts', 'src/commander.ts', 'bin/cli.ts', - 'bin/completion-handlers.ts' + 'bin/completion-handlers.ts', ], format: ['esm'], dts: true, From 06b8e3157e15083a69752c28b1578bdaa6920255 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sat, 3 May 2025 20:33:28 +0330 Subject: [PATCH 03/11] update --- bin/cli.ts | 137 +++++++++++++++++++++++------------------------ tsdown.config.ts | 1 - 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/bin/cli.ts b/bin/cli.ts index 1e1002e..487972c 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -12,85 +12,84 @@ const shells = ['zsh', 'bash', 'fish', 'powershell']; const cli = cac('tab'); cli - .command( - ' complete', - 'Process completion requests from shell' - ) - .allowUnknownOptions() - .action(async (packageManager) => { - if (!packageManagers.includes(packageManager)) { - console.error(`Error: Unsupported package manager "${packageManager}"`); - console.error( - `Supported package managers: ${packageManagers.join(', ')}` - ); - process.exit(1); - } + .command( + ' complete', + 'Process completion requests from shell' + ) + .action(async (packageManager) => { + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error( + `Supported package managers: ${packageManagers.join(', ')}` + ); + process.exit(1); + } - const dashIndex = process.argv.indexOf('--'); - if (dashIndex !== -1) { - const completion = new Completion(); - setupCompletionForPackageManager(packageManager, completion); - const toComplete = process.argv.slice(dashIndex + 1); - await completion.parse(toComplete); - process.exit(0); - } else { - console.error(`Error: Expected '--' followed by command to complete`); - console.error( - `Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete` - ); - process.exit(1); - } - }); + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error( + `Example: ${packageManager} exec @bombsh/tab ${packageManager} complete -- command-to-complete` + ); + process.exit(1); + } + }); cli - .command( - ' ', - 'Generate shell completion script for a package manager' - ) - .action(async (packageManager, shell) => { - if (shell === 'complete') { - const dashIndex = process.argv.indexOf('--'); - if (dashIndex !== -1) { - const completion = new Completion(); - setupCompletionForPackageManager(packageManager, completion); - const toComplete = process.argv.slice(dashIndex + 1); - await completion.parse(toComplete); - process.exit(0); - } else { - console.error(`Error: Expected '--' followed by command to complete`); - console.error( - `Example: npx @bombsh/tab ${packageManager} complete -- command-to-complete` - ); - process.exit(1); - } - return; - } + .command( + ' ', + 'Generate shell completion script for a package manager' + ) + .action(async (packageManager, shell) => { + if (shell === 'complete') { + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error( + `Example: ${packageManager} exec @bombsh/tab ${packageManager} complete -- command-to-complete` + ); + process.exit(1); + } + return; + } - if (!packageManagers.includes(packageManager)) { - console.error(`Error: Unsupported package manager "${packageManager}"`); - console.error( - `Supported package managers: ${packageManagers.join(', ')}` - ); - process.exit(1); - } + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error( + `Supported package managers: ${packageManagers.join(', ')}` + ); + process.exit(1); + } - if (!shells.includes(shell)) { - console.error(`Error: Unsupported shell "${shell}"`); - console.error(`Supported shells: ${shells.join(', ')}`); - process.exit(1); - } + if (!shells.includes(shell)) { + console.error(`Error: Unsupported shell "${shell}"`); + console.error(`Supported shells: ${shells.join(', ')}`); + process.exit(1); + } - generateCompletionScript(packageManager, shell); - }); + generateCompletionScript(packageManager, shell); + }); const completion = tab(cli); cli.parse(); function generateCompletionScript(packageManager: string, shell: string) { - const name = packageManager; - const executable = process.env.npm_execpath - ? `${packageManager} exec @bombsh/tab ${packageManager}` - : `node ${process.argv[1]} ${packageManager}`; - script(shell as any, name, executable); + const name = packageManager; + const executable = process.env.npm_execpath + ? `${packageManager} exec @bombsh/tab ${packageManager}` + : `node ${process.argv[1]} ${packageManager}`; + script(shell as any, name, executable); } diff --git a/tsdown.config.ts b/tsdown.config.ts index 1633327..fb7a5ab 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -7,7 +7,6 @@ export default defineConfig({ 'src/cac.ts', 'src/commander.ts', 'bin/cli.ts', - 'bin/completion-handlers.ts', ], format: ['esm'], dts: true, From 5e2425dfed76e9234f3b461f779808647f54ee67 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 4 May 2025 12:24:43 +0330 Subject: [PATCH 04/11] cli completions --- bin/cli.ts | 136 ++++++++++++++++++------------------- bin/completion-handlers.ts | 121 +++++++++++++++++++++++++++++++++ pnpm-lock.yaml | 3 + src/index.ts | 24 ++++++- 4 files changed, 215 insertions(+), 69 deletions(-) diff --git a/bin/cli.ts b/bin/cli.ts index 487972c..fc18726 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -12,84 +12,84 @@ const shells = ['zsh', 'bash', 'fish', 'powershell']; const cli = cac('tab'); cli - .command( - ' complete', - 'Process completion requests from shell' - ) - .action(async (packageManager) => { - if (!packageManagers.includes(packageManager)) { - console.error(`Error: Unsupported package manager "${packageManager}"`); - console.error( - `Supported package managers: ${packageManagers.join(', ')}` - ); - process.exit(1); - } + .command( + ' complete', + 'Process completion requests from shell' + ) + .action(async (packageManager) => { + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error( + `Supported package managers: ${packageManagers.join(', ')}` + ); + process.exit(1); + } - const dashIndex = process.argv.indexOf('--'); - if (dashIndex !== -1) { - const completion = new Completion(); - setupCompletionForPackageManager(packageManager, completion); - const toComplete = process.argv.slice(dashIndex + 1); - await completion.parse(toComplete); - process.exit(0); - } else { - console.error(`Error: Expected '--' followed by command to complete`); - console.error( - `Example: ${packageManager} exec @bombsh/tab ${packageManager} complete -- command-to-complete` - ); - process.exit(1); - } - }); + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error( + `Example: ${packageManager} exec @bombsh/tab ${packageManager} complete -- command-to-complete` + ); + process.exit(1); + } + }); cli - .command( - ' ', - 'Generate shell completion script for a package manager' - ) - .action(async (packageManager, shell) => { - if (shell === 'complete') { - const dashIndex = process.argv.indexOf('--'); - if (dashIndex !== -1) { - const completion = new Completion(); - setupCompletionForPackageManager(packageManager, completion); - const toComplete = process.argv.slice(dashIndex + 1); - await completion.parse(toComplete); - process.exit(0); - } else { - console.error(`Error: Expected '--' followed by command to complete`); - console.error( - `Example: ${packageManager} exec @bombsh/tab ${packageManager} complete -- command-to-complete` - ); - process.exit(1); - } - return; - } + .command( + ' ', + 'Generate shell completion script for a package manager' + ) + .action(async (packageManager, shell) => { + if (shell === 'complete') { + const dashIndex = process.argv.indexOf('--'); + if (dashIndex !== -1) { + const completion = new Completion(); + setupCompletionForPackageManager(packageManager, completion); + const toComplete = process.argv.slice(dashIndex + 1); + await completion.parse(toComplete); + process.exit(0); + } else { + console.error(`Error: Expected '--' followed by command to complete`); + console.error( + `Example: ${packageManager} exec @bombsh/tab ${packageManager} complete -- command-to-complete` + ); + process.exit(1); + } + return; + } - if (!packageManagers.includes(packageManager)) { - console.error(`Error: Unsupported package manager "${packageManager}"`); - console.error( - `Supported package managers: ${packageManagers.join(', ')}` - ); - process.exit(1); - } + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); + console.error( + `Supported package managers: ${packageManagers.join(', ')}` + ); + process.exit(1); + } - if (!shells.includes(shell)) { - console.error(`Error: Unsupported shell "${shell}"`); - console.error(`Supported shells: ${shells.join(', ')}`); - process.exit(1); - } + if (!shells.includes(shell)) { + console.error(`Error: Unsupported shell "${shell}"`); + console.error(`Supported shells: ${shells.join(', ')}`); + process.exit(1); + } - generateCompletionScript(packageManager, shell); - }); + generateCompletionScript(packageManager, shell); + }); const completion = tab(cli); cli.parse(); function generateCompletionScript(packageManager: string, shell: string) { - const name = packageManager; - const executable = process.env.npm_execpath - ? `${packageManager} exec @bombsh/tab ${packageManager}` - : `node ${process.argv[1]} ${packageManager}`; - script(shell as any, name, executable); + const name = packageManager; + const executable = process.env.npm_execpath + ? `${packageManager} exec @bombsh/tab ${packageManager}` + : `node ${process.argv[1]} ${packageManager}`; + script(shell as any, name, executable); } diff --git a/bin/completion-handlers.ts b/bin/completion-handlers.ts index 9e276b6..5a6fb55 100644 --- a/bin/completion-handlers.ts +++ b/bin/completion-handlers.ts @@ -1,4 +1,61 @@ import { Completion } from '../src/index.js'; +import { execSync } from 'child_process'; + +const DEBUG = false; // for debugging purposes + +function debugLog(...args: any[]) { + if (DEBUG) { + console.error('[DEBUG]', ...args); + } +} + +async function checkCliHasCompletions( + cliName: string, + packageManager: string +): Promise { + try { + debugLog(`Checking if ${cliName} has completions via ${packageManager}`); + const command = `${packageManager} ${cliName} __complete`; + const result = execSync(command, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'], + timeout: 1000, // AMIR: we still havin issues with this, it still hangs if a cli doesn't have completions. + }); + const hasCompletions = !!result.trim(); + debugLog(`${cliName} supports completions: ${hasCompletions}`); + return hasCompletions; + } catch (error) { + debugLog(`Error checking completions for ${cliName}:`, error); + return false; + } +} + +async function getCliCompletions( + cliName: string, + packageManager: string, + args: string[] +): Promise { + try { + const completeArgs = args.map((arg) => + arg.includes(' ') ? `"${arg}"` : arg + ); + const completeCommand = `${packageManager} ${cliName} __complete ${completeArgs.join(' ')}`; + debugLog(`Getting completions with command: ${completeCommand}`); + + const result = execSync(completeCommand, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'], + timeout: 1000, + }); + + const completions = result.trim().split('\n').filter(Boolean); + debugLog(`Got ${completions.length} completions from ${cliName}`); + return completions; + } catch (error) { + debugLog(`Error getting completions from ${cliName}:`, error); + return []; + } +} export function setupCompletionForPackageManager( packageManager: string, @@ -13,6 +70,70 @@ export function setupCompletionForPackageManager( } else if (packageManager === 'bun') { setupBunCompletions(completion); } + + completion.onBeforeParse(async (args: string[]) => { + debugLog(`onBeforeParse: args =`, args); + + if (args.length >= 1) { + const potentialCliName = args[0]; + const knownCommands = [...completion.commands.keys()]; + + debugLog( + `Potential CLI: ${potentialCliName}, Known commands:`, + knownCommands + ); + + if (knownCommands.includes(potentialCliName)) { + debugLog(`${potentialCliName} is a known command, skipping CLI check`); + return; + } + + const hasCompletions = await checkCliHasCompletions( + potentialCliName, + packageManager + ); + if (hasCompletions) { + debugLog( + `${potentialCliName} supports completions, getting suggestions` + ); + + const cliArgs = args.slice(1); + const suggestions = await getCliCompletions( + potentialCliName, + packageManager, + cliArgs + ); + + if (suggestions.length > 0) { + debugLog(`Processing ${suggestions.length} suggestions`); + + completion.result.suppressDefault = true; + + for (const suggestion of suggestions) { + if (suggestion.startsWith(':')) { + debugLog(`Skipping directive: ${suggestion}`); + continue; + } + + if (suggestion.includes('\t')) { + const [value, description] = suggestion.split('\t'); + debugLog( + `Adding completion with description: ${value} -> ${description}` + ); + completion.result.items.push({ value, description }); + } else { + debugLog(`Adding completion without description: ${suggestion}`); + completion.result.items.push({ value: suggestion }); + } + } + } else { + debugLog(`No suggestions found for ${potentialCliName}`); + } + } else { + debugLog(`${potentialCliName} does not support completions`); + } + } + }); } export function setupPnpmCompletions(completion: Completion) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6acace..06c2bf7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,9 @@ importers: commander: specifier: ^13.1.0 version: 13.1.0 + demo-cli-cac: + specifier: link:examples/demo-cli-cac + version: link:examples/demo-cli-cac eslint-config-prettier: specifier: ^10.0.1 version: 10.0.1(eslint@9.18.0(jiti@2.4.2)) diff --git a/src/index.ts b/src/index.ts index 5e0286a..0b3997a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,10 +62,15 @@ export type Positional = { }; type Item = { - description: string; + description?: string; value: string; }; +type CompletionResult = { + items: Item[]; + suppressDefault: boolean; +}; + export type Handler = ( previousArgs: string[], toComplete: string, @@ -91,6 +96,12 @@ export class Completion { commands = new Map(); completions: Item[] = []; directive = ShellCompDirective.ShellCompDirectiveDefault; + result: CompletionResult = { items: [], suppressDefault: false }; + private beforeParseFn: ((args: string[]) => Promise) | null = null; + + onBeforeParse(fn: (args: string[]) => Promise) { + this.beforeParseFn = fn; + } // vite [...files] // args: [false, false, true], only the last argument can be variadic @@ -171,6 +182,17 @@ export class Completion { } async parse(args: string[]) { + this.result = { items: [], suppressDefault: false }; + + if (this.beforeParseFn) { + await this.beforeParseFn(args); + if (this.result.suppressDefault && this.result.items.length > 0) { + this.completions = this.result.items; + this.complete(''); + return; + } + } + const endsWithSpace = args[args.length - 1] === ''; if (endsWithSpace) { From 3cc24ec33b11cd9b7944c72c440a2854eca187d3 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 4 May 2025 12:26:44 +0330 Subject: [PATCH 05/11] pnpm install --- pnpm-lock.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06c2bf7..d6acace 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,9 +27,6 @@ importers: commander: specifier: ^13.1.0 version: 13.1.0 - demo-cli-cac: - specifier: link:examples/demo-cli-cac - version: link:examples/demo-cli-cac eslint-config-prettier: specifier: ^10.0.1 version: 10.0.1(eslint@9.18.0(jiti@2.4.2)) From 8d06638e6c060d7d97b982c993178d9059a162ba Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Fri, 4 Jul 2025 19:17:04 +0330 Subject: [PATCH 06/11] fix: handle complete command manually --- bin/cli.ts | 72 +++++++++++++++++++++--------------------------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/bin/cli.ts b/bin/cli.ts index fc18726..93c7ff0 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -9,14 +9,13 @@ import { setupCompletionForPackageManager } from './completion-handlers'; const packageManagers = ['npm', 'pnpm', 'yarn', 'bun']; const shells = ['zsh', 'bash', 'fish', 'powershell']; -const cli = cac('tab'); +async function main() { + const cli = cac('tab'); + + const args = process.argv.slice(2); + if (args.length >= 2 && args[1] === 'complete') { + const packageManager = args[0]; -cli - .command( - ' complete', - 'Process completion requests from shell' - ) - .action(async (packageManager) => { if (!packageManagers.includes(packageManager)) { console.error(`Error: Unsupported package manager "${packageManager}"`); console.error( @@ -39,52 +38,35 @@ cli ); process.exit(1); } - }); + } -cli - .command( - ' ', - 'Generate shell completion script for a package manager' - ) - .action(async (packageManager, shell) => { - if (shell === 'complete') { - const dashIndex = process.argv.indexOf('--'); - if (dashIndex !== -1) { - const completion = new Completion(); - setupCompletionForPackageManager(packageManager, completion); - const toComplete = process.argv.slice(dashIndex + 1); - await completion.parse(toComplete); - process.exit(0); - } else { - console.error(`Error: Expected '--' followed by command to complete`); + cli + .command( + ' ', + 'Generate shell completion script for a package manager' + ) + .action(async (packageManager, shell) => { + if (!packageManagers.includes(packageManager)) { + console.error(`Error: Unsupported package manager "${packageManager}"`); console.error( - `Example: ${packageManager} exec @bombsh/tab ${packageManager} complete -- command-to-complete` + `Supported package managers: ${packageManagers.join(', ')}` ); process.exit(1); } - return; - } - - if (!packageManagers.includes(packageManager)) { - console.error(`Error: Unsupported package manager "${packageManager}"`); - console.error( - `Supported package managers: ${packageManagers.join(', ')}` - ); - process.exit(1); - } - if (!shells.includes(shell)) { - console.error(`Error: Unsupported shell "${shell}"`); - console.error(`Supported shells: ${shells.join(', ')}`); - process.exit(1); - } + if (!shells.includes(shell)) { + console.error(`Error: Unsupported shell "${shell}"`); + console.error(`Supported shells: ${shells.join(', ')}`); + process.exit(1); + } - generateCompletionScript(packageManager, shell); - }); + generateCompletionScript(packageManager, shell); + }); -const completion = tab(cli); + const completion = tab(cli); -cli.parse(); + cli.parse(); +} function generateCompletionScript(packageManager: string, shell: string) { const name = packageManager; @@ -93,3 +75,5 @@ function generateCompletionScript(packageManager: string, shell: string) { : `node ${process.argv[1]} ${packageManager}`; script(shell as any, name, executable); } + +main().catch(console.error); From 0ad8b91063ccaf879d370bff76bb633c53e3d33c Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sat, 5 Jul 2025 18:56:40 +0330 Subject: [PATCH 07/11] fix: completion-handler __complete => complete --- bin/completion-handlers.ts | 8 +-- examples/demo-cli-cac/demo-cli-cac.js | 85 +++++++++++---------------- examples/demo-cli-cac/package.json | 1 + 3 files changed, 38 insertions(+), 56 deletions(-) diff --git a/bin/completion-handlers.ts b/bin/completion-handlers.ts index 5a6fb55..6485bec 100644 --- a/bin/completion-handlers.ts +++ b/bin/completion-handlers.ts @@ -15,11 +15,11 @@ async function checkCliHasCompletions( ): Promise { try { debugLog(`Checking if ${cliName} has completions via ${packageManager}`); - const command = `${packageManager} ${cliName} __complete`; + const command = `${packageManager} ${cliName} complete --`; const result = execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], - timeout: 1000, // AMIR: we still havin issues with this, it still hangs if a cli doesn't have completions. + timeout: 1000, // AMIR: we still havin issues with this, it still hangs if a cli doesn't have completions. longer timeout needed for shell completion system (shell → node → package manager → cli) }); const hasCompletions = !!result.trim(); debugLog(`${cliName} supports completions: ${hasCompletions}`); @@ -39,13 +39,13 @@ async function getCliCompletions( const completeArgs = args.map((arg) => arg.includes(' ') ? `"${arg}"` : arg ); - const completeCommand = `${packageManager} ${cliName} __complete ${completeArgs.join(' ')}`; + const completeCommand = `${packageManager} ${cliName} complete -- ${completeArgs.join(' ')}`; debugLog(`Getting completions with command: ${completeCommand}`); const result = execSync(completeCommand, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], - timeout: 1000, + timeout: 1000, // same: longer timeout needed for shell completion system (shell → node → package manager → cli) }); const completions = result.trim().split('\n').filter(Boolean); diff --git a/examples/demo-cli-cac/demo-cli-cac.js b/examples/demo-cli-cac/demo-cli-cac.js index 5190d1e..c25560f 100755 --- a/examples/demo-cli-cac/demo-cli-cac.js +++ b/examples/demo-cli-cac/demo-cli-cac.js @@ -1,6 +1,8 @@ #!/usr/bin/env node -const cac = require('cac'); +import cac from 'cac'; +import tab from '../../dist/src/cac.js'; + const cli = cac('demo-cli-cac'); // Define version and help @@ -29,61 +31,40 @@ cli console.log('Options:', options); }); -// Manual implementation of completion for CAC -if (process.argv[2] === '__complete') { - const args = process.argv.slice(3); - const toComplete = args[args.length - 1] || ''; - const previousArgs = args.slice(0, -1); - - // Root command completion - if (previousArgs.length === 0) { - console.log('start\tStart the application'); - console.log('build\tBuild the application'); - console.log('--help\tDisplay help'); - console.log('--version\tOutput the version number'); - console.log('-c\tSpecify config file'); - console.log('--config\tSpecify config file'); - console.log('-d\tEnable debugging'); - console.log('--debug\tEnable debugging'); - process.exit(0); - } - - // Subcommand completion - if (previousArgs[0] === 'start') { - console.log('-p\tPort to use'); - console.log('--port\tPort to use'); - console.log('--help\tDisplay help'); +// Set up completion using the cac adapter +const completion = await tab(cli); - // Port value completion if --port is the last arg - if ( - previousArgs[previousArgs.length - 1] === '--port' || - previousArgs[previousArgs.length - 1] === '-p' - ) { - console.log('3000\tDefault port'); - console.log('8080\tAlternative port'); +// custom config for options +for (const command of completion.commands.values()) { + for (const [optionName, config] of command.options.entries()) { + if (optionName === '--port') { + config.handler = () => { + return [ + { value: '3000', description: 'Default port' }, + { value: '8080', description: 'Alternative port' }, + ]; + }; } - process.exit(0); - } - if (previousArgs[0] === 'build') { - console.log('-m\tBuild mode'); - console.log('--mode\tBuild mode'); - console.log('--help\tDisplay help'); + if (optionName === '--mode') { + config.handler = () => { + return [ + { value: 'development', description: 'Development mode' }, + { value: 'production', description: 'Production mode' }, + { value: 'test', description: 'Test mode' }, + ]; + }; + } - // Mode value completion if --mode is the last arg - if ( - previousArgs[previousArgs.length - 1] === '--mode' || - previousArgs[previousArgs.length - 1] === '-m' - ) { - console.log('development\tDevelopment mode'); - console.log('production\tProduction mode'); - console.log('test\tTest mode'); + if (optionName === '--config') { + config.handler = () => { + return [ + { value: 'config.json', description: 'JSON config file' }, + { value: 'config.js', description: 'JavaScript config file' }, + ]; + }; } - process.exit(0); } - - process.exit(0); -} else { - // Parse CLI args - cli.parse(); } + +cli.parse(); diff --git a/examples/demo-cli-cac/package.json b/examples/demo-cli-cac/package.json index f2e279b..9ccc20d 100644 --- a/examples/demo-cli-cac/package.json +++ b/examples/demo-cli-cac/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "Demo CLI using CAC for testing tab completions with pnpm", "main": "demo-cli-cac.js", + "type": "module", "bin": { "demo-cli-cac": "./demo-cli-cac.js" }, From 507b267a6a5f2010a9817585192cb8961718140d Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 6 Jul 2025 15:04:00 +0330 Subject: [PATCH 08/11] fix: remove examples form package.json --- package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/package.json b/package.json index 1ab841b..02259f5 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "vitest": "^2.1.3" }, "dependencies": { - "examples": "link:./examples", "mri": "^1.2.0" }, "exports": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6acace..78da1d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - examples: - specifier: link:./examples - version: link:examples mri: specifier: ^1.2.0 version: 1.2.0 From 94f8491de34e75c4299c837803acdb10fc37ced6 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Mon, 7 Jul 2025 11:12:11 +0330 Subject: [PATCH 09/11] fix: generateCompletionScript function --- bin/cli.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bin/cli.ts b/bin/cli.ts index 93c7ff0..9dca369 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -68,11 +68,18 @@ async function main() { cli.parse(); } +// function generateCompletionScript(packageManager: string, shell: string) { +// const name = packageManager; +// const executable = process.env.npm_execpath +// ? `${packageManager} exec @bombsh/tab ${packageManager}` +// : `node ${process.argv[1]} ${packageManager}`; +// script(shell as any, name, executable); +// } + function generateCompletionScript(packageManager: string, shell: string) { const name = packageManager; - const executable = process.env.npm_execpath - ? `${packageManager} exec @bombsh/tab ${packageManager}` - : `node ${process.argv[1]} ${packageManager}`; + // this always points at the actual file on disk (TESTING) + const executable = `node ${process.argv[1]} ${packageManager}`; script(shell as any, name, executable); } From 72a94a538ec6a8be146ad4de71844da4d17224b7 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sat, 19 Jul 2025 16:42:58 +0330 Subject: [PATCH 10/11] move the package manager completion logic directly into the parse method --- bin/completion-handlers.ts | 71 +----------------------- src/index.ts | 107 +++++++++++++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 78 deletions(-) diff --git a/bin/completion-handlers.ts b/bin/completion-handlers.ts index 6485bec..69dce11 100644 --- a/bin/completion-handlers.ts +++ b/bin/completion-handlers.ts @@ -71,80 +71,13 @@ export function setupCompletionForPackageManager( setupBunCompletions(completion); } - completion.onBeforeParse(async (args: string[]) => { - debugLog(`onBeforeParse: args =`, args); - - if (args.length >= 1) { - const potentialCliName = args[0]; - const knownCommands = [...completion.commands.keys()]; - - debugLog( - `Potential CLI: ${potentialCliName}, Known commands:`, - knownCommands - ); - - if (knownCommands.includes(potentialCliName)) { - debugLog(`${potentialCliName} is a known command, skipping CLI check`); - return; - } - - const hasCompletions = await checkCliHasCompletions( - potentialCliName, - packageManager - ); - if (hasCompletions) { - debugLog( - `${potentialCliName} supports completions, getting suggestions` - ); - - const cliArgs = args.slice(1); - const suggestions = await getCliCompletions( - potentialCliName, - packageManager, - cliArgs - ); - - if (suggestions.length > 0) { - debugLog(`Processing ${suggestions.length} suggestions`); - - completion.result.suppressDefault = true; - - for (const suggestion of suggestions) { - if (suggestion.startsWith(':')) { - debugLog(`Skipping directive: ${suggestion}`); - continue; - } - - if (suggestion.includes('\t')) { - const [value, description] = suggestion.split('\t'); - debugLog( - `Adding completion with description: ${value} -> ${description}` - ); - completion.result.items.push({ value, description }); - } else { - debugLog(`Adding completion without description: ${suggestion}`); - completion.result.items.push({ value: suggestion }); - } - } - } else { - debugLog(`No suggestions found for ${potentialCliName}`); - } - } else { - debugLog(`${potentialCliName} does not support completions`); - } - } - }); + completion.setPackageManager(packageManager); } export function setupPnpmCompletions(completion: Completion) { completion.addCommand('add', 'Install a package', [], async () => []); completion.addCommand('remove', 'Remove a package', [], async () => []); - completion.addCommand( - 'install', - 'Install all dependencies', - [], - async () => [] - ); + completion.addCommand('install', 'Install all dependencies', [], async () => []); completion.addCommand('update', 'Update packages', [], async () => []); completion.addCommand('exec', 'Execute a command', [], async () => []); completion.addCommand('run', 'Run a script', [], async () => []); diff --git a/src/index.ts b/src/index.ts index 0b3997a..87d9eba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,63 @@ import * as zsh from './zsh'; import * as bash from './bash'; import * as fish from './fish'; import * as powershell from './powershell'; +import { execSync } from 'child_process'; + +const DEBUG = false; + +function debugLog(...args: any[]) { + if (DEBUG) { + console.error('[DEBUG]', ...args); + } +} + +async function checkCliHasCompletions( + cliName: string, + packageManager: string +): Promise { + try { + debugLog(`Checking if ${cliName} has completions via ${packageManager}`); + const command = `${packageManager} ${cliName} complete --`; + const result = execSync(command, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'], + timeout: 1000, + }); + const hasCompletions = !!result.trim(); + debugLog(`${cliName} supports completions: ${hasCompletions}`); + return hasCompletions; + } catch (error) { + debugLog(`Error checking completions for ${cliName}:`, error); + return false; + } +} + +async function getCliCompletions( + cliName: string, + packageManager: string, + args: string[] +): Promise { + try { + const completeArgs = args.map((arg) => + arg.includes(' ') ? `"${arg}"` : arg + ); + const completeCommand = `${packageManager} ${cliName} complete -- ${completeArgs.join(' ')}`; + debugLog(`Getting completions with command: ${completeCommand}`); + + const result = execSync(completeCommand, { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'], + timeout: 1000, + }); + + const completions = result.trim().split('\n').filter(Boolean); + debugLog(`Got ${completions.length} completions from ${cliName}`); + return completions; + } catch (error) { + debugLog(`Error getting completions from ${cliName}:`, error); + return []; + } +} // ShellCompRequestCmd is the name of the hidden command that is used to request // completion results from the program. It is used by the shell completion scripts. @@ -97,10 +154,10 @@ export class Completion { completions: Item[] = []; directive = ShellCompDirective.ShellCompDirectiveDefault; result: CompletionResult = { items: [], suppressDefault: false }; - private beforeParseFn: ((args: string[]) => Promise) | null = null; + private packageManager: string | null = null; - onBeforeParse(fn: (args: string[]) => Promise) { - this.beforeParseFn = fn; + setPackageManager(packageManager: string) { + this.packageManager = packageManager; } // vite [...files] @@ -184,12 +241,44 @@ export class Completion { async parse(args: string[]) { this.result = { items: [], suppressDefault: false }; - if (this.beforeParseFn) { - await this.beforeParseFn(args); - if (this.result.suppressDefault && this.result.items.length > 0) { - this.completions = this.result.items; - this.complete(''); - return; + // Handle package manager completions first + if (this.packageManager && args.length >= 1) { + const potentialCliName = args[0]; + const knownCommands = [...this.commands.keys()]; + + if (!knownCommands.includes(potentialCliName)) { + const hasCompletions = await checkCliHasCompletions( + potentialCliName, + this.packageManager + ); + + if (hasCompletions) { + const cliArgs = args.slice(1); + const suggestions = await getCliCompletions( + potentialCliName, + this.packageManager, + cliArgs + ); + + if (suggestions.length > 0) { + this.result.suppressDefault = true; + + for (const suggestion of suggestions) { + if (suggestion.startsWith(':')) continue; + + if (suggestion.includes('\t')) { + const [value, description] = suggestion.split('\t'); + this.result.items.push({ value, description }); + } else { + this.result.items.push({ value: suggestion }); + } + } + + this.completions = this.result.items; + this.complete(''); + return; + } + } } } From 750d2dddf40fef9c799b70d05f3e6ccf9cc27742 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sat, 19 Jul 2025 16:47:33 +0330 Subject: [PATCH 11/11] prettier --- bin/completion-handlers.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/completion-handlers.ts b/bin/completion-handlers.ts index 69dce11..e51f867 100644 --- a/bin/completion-handlers.ts +++ b/bin/completion-handlers.ts @@ -77,7 +77,12 @@ export function setupCompletionForPackageManager( export function setupPnpmCompletions(completion: Completion) { completion.addCommand('add', 'Install a package', [], async () => []); completion.addCommand('remove', 'Remove a package', [], async () => []); - completion.addCommand('install', 'Install all dependencies', [], async () => []); + completion.addCommand( + 'install', + 'Install all dependencies', + [], + async () => [] + ); completion.addCommand('update', 'Update packages', [], async () => []); completion.addCommand('exec', 'Execute a command', [], async () => []); completion.addCommand('run', 'Run a script', [], async () => []);