-
Notifications
You must be signed in to change notification settings - Fork 9
feat: autocomplete clis executing through a package manager #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
f806955
init
AmirSa12 6d7ba3f
prettier
AmirSa12 06b8e31
update
AmirSa12 5e2425d
cli completions
AmirSa12 3cc24ec
pnpm install
AmirSa12 8d06638
fix: handle complete command manually
AmirSa12 0ad8b91
fix: completion-handler __complete => complete
AmirSa12 507b267
fix: remove examples form package.json
AmirSa12 94f8491
fix: generateCompletionScript function
AmirSa12 72a94a5
move the package manager completion logic directly into the parse method
AmirSa12 750d2dd
prettier
AmirSa12 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| #!/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']; | ||
|
|
||
| async function main() { | ||
| const cli = cac('tab'); | ||
|
|
||
| const args = process.argv.slice(2); | ||
| if (args.length >= 2 && args[1] === 'complete') { | ||
| const packageManager = args[0]; | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
|
|
||
| cli | ||
| .command( | ||
| '<packageManager> <shell>', | ||
| '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( | ||
| `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); | ||
| // } | ||
|
|
||
| function generateCompletionScript(packageManager: string, shell: string) { | ||
| const name = packageManager; | ||
| // this always points at the actual file on disk (TESTING) | ||
| const executable = `node ${process.argv[1]} ${packageManager}`; | ||
| script(shell as any, name, executable); | ||
| } | ||
|
|
||
| main().catch(console.error); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| 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<boolean> { | ||
| 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. longer timeout needed for shell completion system (shell → node → package manager → cli) | ||
| }); | ||
| 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<string[]> { | ||
| 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, // same: longer timeout needed for shell completion system (shell → node → package manager → cli) | ||
| }); | ||
|
|
||
| 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, | ||
| 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); | ||
| } | ||
|
|
||
| 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('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 () => []); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.