[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
4

Configure Feed

Select the types of activity you want to include in your feed.

fix: use pnpm'n main help - pnpm's official descriptions for cmds and opts (#46)

* update

* trigger ci

* machanism change - parse --help

* types

* fix/descriptions

* big update

* update

* update

authored by

AmirHossein Sakhravi and committed by
GitHub
(Sep 9, 2025, 4:40 PM +0330) 0a1cc2fb 44f9091d

+358 -453
+1 -1
bin/cli.ts
··· 30 30 if (dashIndex !== -1) { 31 31 // Use the new PackageManagerCompletion wrapper 32 32 const completion = new PackageManagerCompletion(packageManager); 33 - setupCompletionForPackageManager(packageManager, completion); 33 + await setupCompletionForPackageManager(packageManager, completion); 34 34 const toComplete = process.argv.slice(dashIndex + 1); 35 35 await completion.parse(toComplete); 36 36 process.exit(0);
+27 -451
bin/completion-handlers.ts
··· 1 - import { PackageManagerCompletion } from './package-manager-completion.js'; 2 - import { readFileSync } from 'fs'; 3 - 4 - // Helper functions for dynamic completions 5 - function getPackageJsonScripts(): string[] { 6 - try { 7 - const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); 8 - return Object.keys(packageJson.scripts || {}); 9 - } catch { 10 - return []; 11 - } 12 - } 13 - 14 - function getPackageJsonDependencies(): string[] { 15 - try { 16 - const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); 17 - const deps = { 18 - ...packageJson.dependencies, 19 - ...packageJson.devDependencies, 20 - ...packageJson.peerDependencies, 21 - ...packageJson.optionalDependencies, 22 - }; 23 - return Object.keys(deps); 24 - } catch { 25 - return []; 26 - } 27 - } 28 - 29 - // Common completion handlers 30 - const scriptCompletion = async (complete: any) => { 31 - const scripts = getPackageJsonScripts(); 32 - scripts.forEach((script) => complete(script, `Run ${script} script`)); 33 - }; 1 + /** 2 + * Main entry point for package manager completion handlers 3 + * Delegates to specific package manager handlers 4 + */ 34 5 35 - const dependencyCompletion = async (complete: any) => { 36 - const deps = getPackageJsonDependencies(); 37 - deps.forEach((dep) => complete(dep, '')); 38 - }; 6 + import type { PackageManagerCompletion } from './package-manager-completion.js'; 7 + import { setupPnpmCompletions } from './handlers/pnpm-handler.js'; 8 + import { setupNpmCompletions } from './handlers/npm-handler.js'; 9 + import { setupYarnCompletions } from './handlers/yarn-handler.js'; 10 + import { setupBunCompletions } from './handlers/bun-handler.js'; 39 11 40 - export function setupCompletionForPackageManager( 12 + export async function setupCompletionForPackageManager( 41 13 packageManager: string, 42 14 completion: PackageManagerCompletion 43 - ) { 44 - if (packageManager === 'pnpm') { 45 - setupPnpmCompletions(completion); 46 - } else if (packageManager === 'npm') { 47 - setupNpmCompletions(completion); 48 - } else if (packageManager === 'yarn') { 49 - setupYarnCompletions(completion); 50 - } else if (packageManager === 'bun') { 51 - setupBunCompletions(completion); 15 + ): Promise<void> { 16 + switch (packageManager) { 17 + case 'pnpm': 18 + await setupPnpmCompletions(completion); 19 + break; 20 + case 'npm': 21 + await setupNpmCompletions(completion); 22 + break; 23 + case 'yarn': 24 + await setupYarnCompletions(completion); 25 + break; 26 + case 'bun': 27 + await setupBunCompletions(completion); 28 + break; 29 + default: 30 + // silently ignore unknown package managers 31 + break; 52 32 } 53 33 } 54 - 55 - export function setupPnpmCompletions(completion: PackageManagerCompletion) { 56 - // Package management 57 - const addCmd = completion.command('add', 'Install packages'); 58 - addCmd.option('save-dev', 'Save to devDependencies', 'D'); 59 - addCmd.option('save-optional', 'Save to optionalDependencies', 'O'); 60 - addCmd.option('save-exact', 'Save exact version', 'E'); 61 - addCmd.option('global', 'Install globally', 'g'); 62 - addCmd.option('workspace', 'Install to workspace'); 63 - addCmd.option('filter', 'Filter packages'); 64 - 65 - const removeCmd = completion.command('remove', 'Remove packages'); 66 - removeCmd.argument('package', dependencyCompletion); 67 - removeCmd.option('global', 'Remove globally', 'g'); 68 - 69 - const installCmd = completion.command('install', 'Install dependencies'); 70 - installCmd.option('frozen-lockfile', 'Install with frozen lockfile'); 71 - installCmd.option('prefer-frozen-lockfile', 'Prefer frozen lockfile'); 72 - installCmd.option('production', 'Install production dependencies only'); 73 - installCmd.option('dev', 'Install dev dependencies only'); 74 - installCmd.option('optional', 'Include optional dependencies'); 75 - installCmd.option('filter', 'Filter packages'); 76 - 77 - const updateCmd = completion.command('update', 'Update dependencies'); 78 - updateCmd.argument('package', dependencyCompletion); 79 - updateCmd.option('latest', 'Update to latest versions'); 80 - updateCmd.option('global', 'Update global packages', 'g'); 81 - updateCmd.option('interactive', 'Interactive update', 'i'); 82 - 83 - // Script execution 84 - const runCmd = completion.command('run', 'Run scripts'); 85 - runCmd.argument('script', scriptCompletion, true); 86 - runCmd.option('parallel', 'Run scripts in parallel'); 87 - runCmd.option('stream', 'Stream output'); 88 - runCmd.option('filter', 'Filter packages'); 89 - 90 - const execCmd = completion.command('exec', 'Execute commands'); 91 - execCmd.option('filter', 'Filter packages'); 92 - execCmd.option('parallel', 'Run in parallel'); 93 - execCmd.option('stream', 'Stream output'); 94 - 95 - completion.command('dlx', 'Run package without installing'); 96 - 97 - // Project management 98 - completion.command('create', 'Create new project'); 99 - completion.command('init', 'Initialize project'); 100 - 101 - // Publishing 102 - const publishCmd = completion.command('publish', 'Publish package'); 103 - publishCmd.option('tag', 'Publish with tag'); 104 - publishCmd.option('access', 'Set access level'); 105 - publishCmd.option('otp', 'One-time password'); 106 - publishCmd.option('dry-run', 'Dry run'); 107 - 108 - const packCmd = completion.command('pack', 'Create tarball'); 109 - packCmd.option('pack-destination', 'Destination directory'); 110 - 111 - // Linking 112 - const linkCmd = completion.command('link', 'Link packages'); 113 - linkCmd.option('global', 'Link globally', 'g'); 114 - 115 - const unlinkCmd = completion.command('unlink', 'Unlink packages'); 116 - unlinkCmd.option('global', 'Unlink globally', 'g'); 117 - 118 - // Information 119 - const listCmd = completion.command('list', 'List packages'); 120 - listCmd.option('depth', 'Max depth'); 121 - listCmd.option('global', 'List global packages', 'g'); 122 - listCmd.option('long', 'Show extended information'); 123 - listCmd.option('parseable', 'Parseable output'); 124 - listCmd.option('json', 'JSON output'); 125 - 126 - const outdatedCmd = completion.command('outdated', 'Check outdated packages'); 127 - outdatedCmd.option('global', 'Check global packages', 'g'); 128 - outdatedCmd.option('long', 'Show extended information'); 129 - 130 - const auditCmd = completion.command('audit', 'Security audit'); 131 - auditCmd.option('fix', 'Automatically fix vulnerabilities'); 132 - auditCmd.option('json', 'JSON output'); 133 - 134 - // Workspace commands 135 - completion.command('workspace', 'Workspace commands'); 136 - 137 - // Store management 138 - completion.command('store', 'Store management'); 139 - completion.command('store status', 'Store status'); 140 - completion.command('store prune', 'Prune store'); 141 - completion.command('store path', 'Store path'); 142 - 143 - // Configuration 144 - completion.command('config', 'Configuration'); 145 - completion.command('config get', 'Get config value'); 146 - completion.command('config set', 'Set config value'); 147 - completion.command('config delete', 'Delete config value'); 148 - completion.command('config list', 'List config'); 149 - 150 - // Other useful commands 151 - completion.command('why', 'Explain why package is installed'); 152 - completion.command('rebuild', 'Rebuild packages'); 153 - completion.command('root', 'Print root directory'); 154 - completion.command('bin', 'Print bin directory'); 155 - completion.command('start', 'Run start script'); 156 - completion.command('test', 'Run test script'); 157 - completion.command('restart', 'Run restart script'); 158 - completion.command('stop', 'Run stop script'); 159 - } 160 - 161 - export function setupNpmCompletions(completion: PackageManagerCompletion) { 162 - // Package management 163 - const installCmd = completion.command('install', 'Install packages'); 164 - installCmd.option('save', 'Save to dependencies', 'S'); 165 - installCmd.option('save-dev', 'Save to devDependencies', 'D'); 166 - installCmd.option('save-optional', 'Save to optionalDependencies', 'O'); 167 - installCmd.option('save-exact', 'Save exact version', 'E'); 168 - installCmd.option('global', 'Install globally', 'g'); 169 - installCmd.option('production', 'Production install'); 170 - installCmd.option('only', 'Install only specific dependencies'); 171 - 172 - const uninstallCmd = completion.command('uninstall', 'Remove packages'); 173 - uninstallCmd.argument('package', dependencyCompletion); 174 - uninstallCmd.option('save', 'Remove from dependencies', 'S'); 175 - uninstallCmd.option('save-dev', 'Remove from devDependencies', 'D'); 176 - uninstallCmd.option('global', 'Remove globally', 'g'); 177 - 178 - const updateCmd = completion.command('update', 'Update packages'); 179 - updateCmd.argument('package', dependencyCompletion); 180 - updateCmd.option('global', 'Update global packages', 'g'); 181 - 182 - // Script execution 183 - const runCmd = completion.command('run', 'Run scripts'); 184 - runCmd.argument('script', scriptCompletion, true); 185 - 186 - const runScriptCmd = completion.command('run-script', 'Run scripts'); 187 - runScriptCmd.argument('script', scriptCompletion, true); 188 - 189 - completion.command('exec', 'Execute command'); 190 - 191 - // Project management 192 - const initCmd = completion.command('init', 'Initialize project'); 193 - initCmd.option('yes', 'Use defaults', 'y'); 194 - initCmd.option('scope', 'Set scope'); 195 - 196 - // Publishing 197 - const publishCmd = completion.command('publish', 'Publish package'); 198 - publishCmd.option('tag', 'Publish with tag'); 199 - publishCmd.option('access', 'Set access level'); 200 - publishCmd.option('otp', 'One-time password'); 201 - publishCmd.option('dry-run', 'Dry run'); 202 - 203 - completion.command('pack', 'Create tarball'); 204 - 205 - // Linking 206 - completion.command('link', 'Link packages'); 207 - completion.command('unlink', 'Unlink packages'); 208 - 209 - // Information 210 - const listCmd = completion.command('list', 'List packages'); 211 - listCmd.option('depth', 'Max depth'); 212 - listCmd.option('global', 'List global packages', 'g'); 213 - listCmd.option('long', 'Show extended information'); 214 - listCmd.option('parseable', 'Parseable output'); 215 - listCmd.option('json', 'JSON output'); 216 - 217 - const lsCmd = completion.command('ls', 'List packages (alias)'); 218 - lsCmd.option('depth', 'Max depth'); 219 - lsCmd.option('global', 'List global packages', 'g'); 220 - 221 - const outdatedCmd = completion.command('outdated', 'Check outdated packages'); 222 - outdatedCmd.option('global', 'Check global packages', 'g'); 223 - 224 - const auditCmd = completion.command('audit', 'Security audit'); 225 - auditCmd.option('fix', 'Fix vulnerabilities'); 226 - auditCmd.option('json', 'JSON output'); 227 - 228 - // Configuration 229 - completion.command('config', 'Configuration'); 230 - completion.command('config get', 'Get config value'); 231 - completion.command('config set', 'Set config value'); 232 - completion.command('config delete', 'Delete config value'); 233 - completion.command('config list', 'List config'); 234 - 235 - // Other commands 236 - completion.command('version', 'Bump version'); 237 - completion.command('view', 'View package info'); 238 - completion.command('search', 'Search packages'); 239 - completion.command('whoami', 'Display username'); 240 - completion.command('login', 'Login to registry'); 241 - completion.command('logout', 'Logout from registry'); 242 - completion.command('adduser', 'Add user'); 243 - completion.command('owner', 'Manage package owners'); 244 - completion.command('deprecate', 'Deprecate package'); 245 - completion.command('dist-tag', 'Manage distribution tags'); 246 - completion.command('cache', 'Manage cache'); 247 - completion.command('completion', 'Tab completion'); 248 - completion.command('explore', 'Browse package'); 249 - completion.command('docs', 'Open documentation'); 250 - completion.command('repo', 'Open repository'); 251 - completion.command('bugs', 'Open bug tracker'); 252 - completion.command('help', 'Get help'); 253 - completion.command('root', 'Print root directory'); 254 - completion.command('prefix', 'Print prefix'); 255 - completion.command('bin', 'Print bin directory'); 256 - completion.command('fund', 'Fund packages'); 257 - completion.command('find-dupes', 'Find duplicate packages'); 258 - completion.command('dedupe', 'Deduplicate packages'); 259 - completion.command('prune', 'Remove extraneous packages'); 260 - completion.command('rebuild', 'Rebuild packages'); 261 - completion.command('start', 'Run start script'); 262 - completion.command('stop', 'Run stop script'); 263 - completion.command('test', 'Run test script'); 264 - completion.command('restart', 'Run restart script'); 265 - } 266 - 267 - export function setupYarnCompletions(completion: PackageManagerCompletion) { 268 - // Package management 269 - const addCmd = completion.command('add', 'Add packages'); 270 - addCmd.option('dev', 'Add to devDependencies', 'D'); 271 - addCmd.option('peer', 'Add to peerDependencies', 'P'); 272 - addCmd.option('optional', 'Add to optionalDependencies', 'O'); 273 - addCmd.option('exact', 'Add exact version', 'E'); 274 - addCmd.option('tilde', 'Add with tilde range', 'T'); 275 - 276 - const removeCmd = completion.command('remove', 'Remove packages'); 277 - removeCmd.argument('package', dependencyCompletion); 278 - 279 - const installCmd = completion.command('install', 'Install dependencies'); 280 - installCmd.option('frozen-lockfile', 'Install with frozen lockfile'); 281 - installCmd.option('prefer-offline', 'Prefer offline'); 282 - installCmd.option('production', 'Production install'); 283 - installCmd.option('pure-lockfile', 'Pure lockfile'); 284 - installCmd.option('focus', 'Focus install'); 285 - installCmd.option('har', 'Save HAR file'); 286 - 287 - const upgradeCmd = completion.command('upgrade', 'Upgrade packages'); 288 - upgradeCmd.argument('package', dependencyCompletion); 289 - upgradeCmd.option('latest', 'Upgrade to latest'); 290 - upgradeCmd.option('pattern', 'Upgrade pattern'); 291 - upgradeCmd.option('scope', 'Upgrade scope'); 292 - 293 - const upgradeInteractiveCmd = completion.command( 294 - 'upgrade-interactive', 295 - 'Interactive upgrade' 296 - ); 297 - upgradeInteractiveCmd.option('latest', 'Show latest versions'); 298 - 299 - // Script execution 300 - const runCmd = completion.command('run', 'Run scripts'); 301 - runCmd.argument('script', scriptCompletion, true); 302 - 303 - completion.command('exec', 'Execute command'); 304 - 305 - // Project management 306 - completion.command('create', 'Create new project'); 307 - const initCmd = completion.command('init', 'Initialize project'); 308 - initCmd.option('yes', 'Use defaults', 'y'); 309 - initCmd.option('private', 'Create private package', 'p'); 310 - 311 - // Publishing 312 - const publishCmd = completion.command('publish', 'Publish package'); 313 - publishCmd.option('tag', 'Publish with tag'); 314 - publishCmd.option('access', 'Set access level'); 315 - publishCmd.option('new-version', 'Set new version'); 316 - 317 - const packCmd = completion.command('pack', 'Create tarball'); 318 - packCmd.option('filename', 'Output filename'); 319 - 320 - // Linking 321 - completion.command('link', 'Link packages'); 322 - completion.command('unlink', 'Unlink packages'); 323 - 324 - // Information 325 - const listCmd = completion.command('list', 'List packages'); 326 - listCmd.option('depth', 'Max depth'); 327 - listCmd.option('pattern', 'Filter pattern'); 328 - 329 - completion.command('info', 'Show package info'); 330 - completion.command('outdated', 'Check outdated packages'); 331 - const auditCmd = completion.command('audit', 'Security audit'); 332 - auditCmd.option('level', 'Minimum severity level'); 333 - 334 - // Workspace commands 335 - completion.command('workspace', 'Workspace commands'); 336 - completion.command('workspaces', 'Workspaces commands'); 337 - completion.command('workspaces info', 'Workspace info'); 338 - completion.command('workspaces run', 'Run in workspaces'); 339 - 340 - // Configuration 341 - completion.command('config', 'Configuration'); 342 - completion.command('config get', 'Get config value'); 343 - completion.command('config set', 'Set config value'); 344 - completion.command('config delete', 'Delete config value'); 345 - completion.command('config list', 'List config'); 346 - 347 - // Cache management 348 - completion.command('cache', 'Cache management'); 349 - completion.command('cache list', 'List cache'); 350 - completion.command('cache dir', 'Cache directory'); 351 - completion.command('cache clean', 'Clean cache'); 352 - 353 - // Other commands 354 - completion.command('version', 'Show version'); 355 - completion.command('versions', 'Show all versions'); 356 - completion.command('why', 'Explain installation'); 357 - completion.command('owner', 'Manage owners'); 358 - completion.command('team', 'Manage teams'); 359 - completion.command('login', 'Login to registry'); 360 - completion.command('logout', 'Logout from registry'); 361 - completion.command('tag', 'Manage tags'); 362 - completion.command('global', 'Global packages'); 363 - completion.command('bin', 'Print bin directory'); 364 - completion.command('dir', 'Print modules directory'); 365 - completion.command('licenses', 'List licenses'); 366 - completion.command('generate-lock-entry', 'Generate lock entry'); 367 - completion.command('check', 'Verify package tree'); 368 - completion.command('import', 'Import from npm'); 369 - completion.command('install-peerdeps', 'Install peer dependencies'); 370 - completion.command('autoclean', 'Clean unnecessary files'); 371 - completion.command('policies', 'Policies'); 372 - completion.command('start', 'Run start script'); 373 - completion.command('test', 'Run test script'); 374 - completion.command('node', 'Run node'); 375 - } 376 - 377 - export function setupBunCompletions(completion: PackageManagerCompletion) { 378 - // Package management 379 - const addCmd = completion.command('add', 'Add packages'); 380 - addCmd.option('development', 'Add to devDependencies', 'd'); 381 - addCmd.option('optional', 'Add to optionalDependencies'); 382 - addCmd.option('exact', 'Add exact version', 'E'); 383 - addCmd.option('global', 'Install globally', 'g'); 384 - 385 - const removeCmd = completion.command('remove', 'Remove packages'); 386 - removeCmd.argument('package', dependencyCompletion); 387 - removeCmd.option('global', 'Remove globally', 'g'); 388 - 389 - const installCmd = completion.command('install', 'Install dependencies'); 390 - installCmd.option('production', 'Production install'); 391 - installCmd.option('frozen-lockfile', 'Use frozen lockfile'); 392 - installCmd.option('dry-run', 'Dry run'); 393 - installCmd.option('force', 'Force install'); 394 - installCmd.option('silent', 'Silent install'); 395 - 396 - const updateCmd = completion.command('update', 'Update packages'); 397 - updateCmd.argument('package', dependencyCompletion); 398 - updateCmd.option('global', 'Update global packages', 'g'); 399 - 400 - // Script execution and running 401 - const runCmd = completion.command('run', 'Run scripts'); 402 - runCmd.argument('script', scriptCompletion, true); 403 - runCmd.option('silent', 'Silent output'); 404 - runCmd.option('bun', 'Use bun runtime'); 405 - 406 - const xCmd = completion.command('x', 'Execute packages'); 407 - xCmd.option('bun', 'Use bun runtime'); 408 - 409 - // Bun-specific commands 410 - completion.command('dev', 'Development server'); 411 - completion.command('build', 'Build project'); 412 - completion.command('test', 'Run tests'); 413 - 414 - // Project management 415 - completion.command('create', 'Create new project'); 416 - const initCmd = completion.command('init', 'Initialize project'); 417 - initCmd.option('yes', 'Use defaults', 'y'); 418 - 419 - // Publishing 420 - const publishCmd = completion.command('publish', 'Publish package'); 421 - publishCmd.option('tag', 'Publish with tag'); 422 - publishCmd.option('access', 'Set access level'); 423 - publishCmd.option('otp', 'One-time password'); 424 - 425 - completion.command('pack', 'Create tarball'); 426 - 427 - // Linking 428 - completion.command('link', 'Link packages'); 429 - completion.command('unlink', 'Unlink packages'); 430 - 431 - // Information 432 - const listCmd = completion.command('list', 'List packages'); 433 - listCmd.option('global', 'List global packages', 'g'); 434 - 435 - completion.command('outdated', 'Check outdated packages'); 436 - completion.command('audit', 'Security audit'); 437 - 438 - // Configuration 439 - completion.command('config', 'Configuration'); 440 - 441 - // Bun runtime commands 442 - completion.command('bun', 'Run with Bun runtime'); 443 - completion.command('node', 'Node.js compatibility'); 444 - completion.command('upgrade', 'Upgrade Bun'); 445 - completion.command('completions', 'Generate completions'); 446 - completion.command('discord', 'Open Discord'); 447 - completion.command('help', 'Show help'); 448 - 449 - // File operations 450 - completion.command('install.cache', 'Cache operations'); 451 - completion.command('pm', 'Package manager operations'); 452 - 453 - // Other commands 454 - completion.command('start', 'Run start script'); 455 - completion.command('stop', 'Run stop script'); 456 - completion.command('restart', 'Run restart script'); 457 - }
+21
bin/completions/completion-producers.ts
··· 1 + import type { Complete } from '../../src/t.js'; 2 + import { 3 + getPackageJsonScripts, 4 + getPackageJsonDependencies, 5 + } from '../utils/package-json-utils.js'; 6 + 7 + // provides completions for npm scripts from package.json.. like: start,dev,build 8 + export const packageJsonScriptCompletion = async ( 9 + complete: Complete 10 + ): Promise<void> => { 11 + getPackageJsonScripts().forEach((script) => 12 + complete(script, `Run ${script} script`) 13 + ); 14 + }; 15 + 16 + // provides completions for package dependencies from package.json.. for commands like remove `pnpm remove <dependency>` 17 + export const packageJsonDependencyCompletion = async ( 18 + complete: Complete 19 + ): Promise<void> => { 20 + getPackageJsonDependencies().forEach((dep) => complete(dep, '')); 21 + };
+5
bin/handlers/bun-handler.ts
··· 1 + import type { PackageManagerCompletion } from '../package-manager-completion.js'; 2 + 3 + export async function setupBunCompletions( 4 + completion: PackageManagerCompletion 5 + ): Promise<void> {}
+5
bin/handlers/npm-handler.ts
··· 1 + import type { PackageManagerCompletion } from '../package-manager-completion.js'; 2 + 3 + export async function setupNpmCompletions( 4 + completion: PackageManagerCompletion 5 + ): Promise<void> {}
+233
bin/handlers/pnpm-handler.ts
··· 1 + import { promisify } from 'node:util'; 2 + import child_process from 'node:child_process'; 3 + 4 + const exec = promisify(child_process.exec); 5 + const { execSync } = child_process; 6 + import type { PackageManagerCompletion } from '../package-manager-completion.js'; 7 + import { Command, Option } from '../../src/t.js'; 8 + 9 + interface LazyCommand extends Command { 10 + _lazyCommand?: string; 11 + _optionsLoaded?: boolean; 12 + optionsRaw?: Map<string, Option>; 13 + } 14 + 15 + import { 16 + packageJsonScriptCompletion, 17 + packageJsonDependencyCompletion, 18 + } from '../completions/completion-producers.js'; 19 + import { 20 + stripAnsiEscapes, 21 + measureIndent, 22 + parseAliasList, 23 + COMMAND_ROW_RE, 24 + OPTION_ROW_RE, 25 + OPTION_HEAD_RE, 26 + type ParsedOption, 27 + } from '../utils/text-utils.js'; 28 + 29 + // regex to detect options section in help text 30 + const OPTIONS_SECTION_RE = /^\s*Options:/i; 31 + 32 + // we parse the pnpm help text to extract commands and their descriptions! 33 + export function parsePnpmHelp(helpText: string): Record<string, string> { 34 + const helpLines = stripAnsiEscapes(helpText).split(/\r?\n/); 35 + 36 + // we find the earliest description column across command rows. 37 + let descColumnIndex = Number.POSITIVE_INFINITY; 38 + for (const line of helpLines) { 39 + const rowMatch = line.match(COMMAND_ROW_RE); 40 + if (!rowMatch) continue; 41 + const descColumnIndexOnThisLine = line.indexOf(rowMatch[2]); 42 + if ( 43 + descColumnIndexOnThisLine >= 0 && 44 + descColumnIndexOnThisLine < descColumnIndex 45 + ) { 46 + descColumnIndex = descColumnIndexOnThisLine; 47 + } 48 + } 49 + if (!Number.isFinite(descColumnIndex)) return {}; 50 + 51 + // we fold rows, and join continuation lines aligned to descColumnIndex or deeper. 52 + type PendingRow = { names: string[]; desc: string } | null; 53 + let pendingRow: PendingRow = null; 54 + 55 + const commandMap = new Map<string, string>(); 56 + const flushPendingRow = () => { 57 + if (!pendingRow) return; 58 + const desc = pendingRow.desc.trim(); 59 + for (const name of pendingRow.names) commandMap.set(name, desc); 60 + pendingRow = null; 61 + }; 62 + 63 + for (const line of helpLines) { 64 + if (OPTIONS_SECTION_RE.test(line)) break; // we stop at options 65 + 66 + // we match the command row 67 + const rowMatch = line.match(COMMAND_ROW_RE); 68 + if (rowMatch) { 69 + flushPendingRow(); 70 + pendingRow = { 71 + names: parseAliasList(rowMatch[1]), 72 + desc: rowMatch[2].trim(), 73 + }; 74 + continue; 75 + } 76 + 77 + // we join continuation lines aligned to descColumnIndex or deeper 78 + if (pendingRow) { 79 + const indentWidth = measureIndent(line); 80 + if (indentWidth >= descColumnIndex && line.trim()) { 81 + pendingRow.desc += ' ' + line.trim(); 82 + } 83 + } 84 + } 85 + // we flush the pending row and return the command map 86 + flushPendingRow(); 87 + 88 + return Object.fromEntries(commandMap); 89 + } 90 + 91 + // now we get the pnpm commands from the main help output 92 + export async function getPnpmCommandsFromMainHelp(): Promise< 93 + Record<string, string> 94 + > { 95 + try { 96 + const { stdout } = await exec('pnpm --help', { 97 + encoding: 'utf8', 98 + timeout: 500, 99 + maxBuffer: 4 * 1024 * 1024, 100 + }); 101 + return parsePnpmHelp(stdout); 102 + } catch { 103 + return {}; 104 + } 105 + } 106 + 107 + // here we parse the pnpm options from the help text 108 + export function parsePnpmOptions( 109 + helpText: string, 110 + { flagsOnly = true }: { flagsOnly?: boolean } = {} 111 + ): ParsedOption[] { 112 + // we strip the ANSI escapes from the help text 113 + const helpLines = stripAnsiEscapes(helpText).split(/\r?\n/); 114 + 115 + // we find the earliest description column among option rows we care about 116 + let descColumnIndex = Number.POSITIVE_INFINITY; 117 + for (const line of helpLines) { 118 + const optionMatch = line.match(OPTION_ROW_RE); 119 + if (!optionMatch) continue; 120 + if (flagsOnly && optionMatch.groups?.val) continue; // skip value-taking options, we will add them manually with their value 121 + const descColumnIndexOnThisLine = line.indexOf(optionMatch.groups!.desc); 122 + if ( 123 + descColumnIndexOnThisLine >= 0 && 124 + descColumnIndexOnThisLine < descColumnIndex 125 + ) { 126 + descColumnIndex = descColumnIndexOnThisLine; 127 + } 128 + } 129 + if (!Number.isFinite(descColumnIndex)) return []; 130 + 131 + // we fold the option rows and join the continuations 132 + const optionsOut: ParsedOption[] = []; 133 + let pendingOption: ParsedOption | null = null; 134 + 135 + const flushPendingOption = () => { 136 + if (!pendingOption) return; 137 + pendingOption.desc = pendingOption.desc.trim(); 138 + optionsOut.push(pendingOption); 139 + pendingOption = null; 140 + }; 141 + 142 + // we match the option row 143 + for (const line of helpLines) { 144 + const optionMatch = line.match(OPTION_ROW_RE); 145 + if (optionMatch) { 146 + if (flagsOnly && optionMatch.groups?.val) continue; 147 + flushPendingOption(); 148 + pendingOption = { 149 + short: optionMatch.groups?.short || undefined, 150 + long: optionMatch.groups!.long, 151 + desc: optionMatch.groups!.desc.trim(), 152 + }; 153 + continue; 154 + } 155 + 156 + // we join the continuations 157 + if (pendingOption) { 158 + const indentWidth = measureIndent(line); 159 + const startsNewOption = OPTION_HEAD_RE.test(line); 160 + if (indentWidth >= descColumnIndex && line.trim() && !startsNewOption) { 161 + pendingOption.desc += ' ' + line.trim(); 162 + } 163 + } 164 + } 165 + // we flush the pending option 166 + flushPendingOption(); 167 + 168 + return optionsOut; 169 + } 170 + 171 + // we load the dynamic options synchronously when requested ( separated from the command loading ) 172 + export function loadDynamicOptionsSync( 173 + cmd: LazyCommand, 174 + command: string 175 + ): void { 176 + try { 177 + const stdout = execSync(`pnpm ${command} --help`, { 178 + encoding: 'utf8', 179 + timeout: 500, 180 + }); 181 + 182 + const parsedOptions = parsePnpmOptions(stdout, { flagsOnly: true }); 183 + 184 + for (const { long, short, desc } of parsedOptions) { 185 + const alreadyDefined = cmd.optionsRaw?.get?.(long); 186 + if (!alreadyDefined) cmd.option(long, desc, short); 187 + } 188 + } catch (_err) {} 189 + } 190 + 191 + // we setup the lazy option loading for a command 192 + 193 + function setupLazyOptionLoading(cmd: LazyCommand, command: string): void { 194 + cmd._lazyCommand = command; 195 + cmd._optionsLoaded = false; 196 + 197 + const optionsStore = cmd.options; 198 + cmd.optionsRaw = optionsStore; 199 + 200 + Object.defineProperty(cmd, 'options', { 201 + get() { 202 + if (!this._optionsLoaded) { 203 + this._optionsLoaded = true; 204 + loadDynamicOptionsSync(this, this._lazyCommand); // block until filled 205 + } 206 + return optionsStore; 207 + }, 208 + configurable: true, 209 + }); 210 + } 211 + 212 + export async function setupPnpmCompletions( 213 + completion: PackageManagerCompletion 214 + ): Promise<void> { 215 + try { 216 + const commandsWithDescriptions = await getPnpmCommandsFromMainHelp(); 217 + 218 + for (const [command, description] of Object.entries( 219 + commandsWithDescriptions 220 + )) { 221 + const cmd = completion.command(command, description); 222 + 223 + if (['remove', 'rm', 'update', 'up'].includes(command)) { 224 + cmd.argument('package', packageJsonDependencyCompletion); 225 + } 226 + if (command === 'run') { 227 + cmd.argument('script', packageJsonScriptCompletion, true); 228 + } 229 + 230 + setupLazyOptionLoading(cmd, command); 231 + } 232 + } catch (_err) {} 233 + }
+5
bin/handlers/yarn-handler.ts
··· 1 + import type { PackageManagerCompletion } from '../package-manager-completion.js'; 2 + 3 + export async function setupYarnCompletions( 4 + completion: PackageManagerCompletion 5 + ): Promise<void> {}
+25
bin/utils/package-json-utils.ts
··· 1 + import { readFileSync } from 'fs'; 2 + 3 + export function getPackageJsonScripts(): string[] { 4 + try { 5 + const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); 6 + return Object.keys(packageJson.scripts || {}); 7 + } catch { 8 + return []; 9 + } 10 + } 11 + 12 + export function getPackageJsonDependencies(): string[] { 13 + try { 14 + const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); 15 + const deps = { 16 + ...packageJson.dependencies, 17 + ...packageJson.devDependencies, 18 + ...packageJson.peerDependencies, 19 + ...packageJson.optionalDependencies, 20 + }; 21 + return Object.keys(deps); 22 + } catch { 23 + return []; 24 + } 25 + }
+35
bin/utils/text-utils.ts
··· 1 + // regex for parsing help text 2 + export const ANSI_ESCAPE_RE = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g; 3 + 4 + // Command row: <indent><names><>=2 spaces><desc> 5 + // e.g. " install, i Install all dependencies" 6 + export const COMMAND_ROW_RE = /^\s+([a-z][a-z\s,-]*?)\s{2,}(\S.*)$/i; 7 + 8 + // Option row (optional value part captured in (?<val>)): 9 + // [indent][-x, ]--long[ <value>| [value]] <>=2 spaces> <desc> 10 + export const OPTION_ROW_RE = 11 + /^\s*(?:-(?<short>[A-Za-z]),\s*)?--(?<long>[a-z0-9-]+)(?<val>\s+(?:<[^>]+>|\[[^\]]+\]))?\s{2,}(?<desc>\S.*)$/i; 12 + 13 + // we detect the start of a new option head (used to stop continuation) 14 + export const OPTION_HEAD_RE = /^\s*(?:-[A-Za-z],\s*)?--[a-z0-9-]+/i; 15 + 16 + // we remove the ANSI escape sequences from a string 17 + export const stripAnsiEscapes = (s: string): string => 18 + s.replace(ANSI_ESCAPE_RE, ''); 19 + 20 + // measure the indentation level of a string 21 + export const measureIndent = (s: string): number => 22 + (s.match(/^\s*/) || [''])[0].length; 23 + 24 + // parse a comma-separated list of aliases 25 + export const parseAliasList = (s: string): string[] => 26 + s 27 + .split(',') 28 + .map((t) => t.trim()) 29 + .filter(Boolean); 30 + 31 + export type ParsedOption = { 32 + long: string; 33 + short?: string; 34 + desc: string; 35 + };
+1 -1
src/t.ts
··· 12 12 13 13 export type OptionsMap = Map<string, Option>; 14 14 15 - type Complete = (value: string, description: string) => void; 15 + export type Complete = (value: string, description: string) => void; 16 16 17 17 export type OptionHandler = ( 18 18 this: Option,