UNPKG

bktide

Version:

Command-line interface for Buildkite CI/CD workflows with rich shell completions (Fish, Bash, Zsh) and Alfred workflow integration for macOS power users

238 lines 10.2 kB
import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { BaseCommand } from './BaseCommand.js'; import { logger } from '../services/logger.js'; export class GenerateCompletions extends BaseCommand { static requiresToken = false; async execute(options) { const shell = options.shell || this.detectShell(); if (!shell) { logger.error('Could not detect shell. Please specify with: bktide completions <shell>'); logger.info('Supported shells: fish, bash, zsh'); return 1; } try { const completionScript = await this.getCompletionScript(shell); if (completionScript) { // Output the completion script to stdout console.log(completionScript); if (!options.quiet && process.stderr.isTTY) { // Show installation instructions on stderr this.showInstallInstructions(shell); } return 0; } else { logger.error(`No completion script available for shell: ${shell}`); logger.info('Supported shells: fish, bash, zsh'); return 1; } } catch (error) { this.handleError(error, options.debug); return 1; } } detectShell() { // Try to detect the current shell const shellEnv = process.env.SHELL; if (shellEnv) { const shellName = path.basename(shellEnv); if (['fish', 'bash', 'zsh'].includes(shellName)) { return shellName; } } return undefined; } async getCompletionScript(shell) { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Look for completion scripts in the completions directory const completionsDir = path.resolve(__dirname, '../../completions'); let scriptFile; switch (shell) { case 'fish': // Use the dynamic version if jq is available, otherwise static if (await this.isCommandAvailable('jq')) { scriptFile = path.join(completionsDir, 'bktide-dynamic.fish'); } else { scriptFile = path.join(completionsDir, 'bktide.fish'); } break; case 'bash': scriptFile = path.join(completionsDir, 'bktide.bash'); break; case 'zsh': scriptFile = path.join(completionsDir, 'bktide.zsh'); break; default: return undefined; } // Check if the completion script exists if (fs.existsSync(scriptFile)) { return fs.readFileSync(scriptFile, 'utf-8'); } // Fallback: Generate basic completion based on shell type return this.generateBasicCompletion(shell); } async isCommandAvailable(command) { try { const { exec } = await import('child_process'); return new Promise((resolve) => { exec(`which ${command}`, (error) => { resolve(!error); }); }); } catch { return false; } } generateBasicCompletion(shell) { // Basic completion generation for shells const commands = ['viewer', 'orgs', 'pipelines', 'builds', 'token', 'annotations', 'completions', 'boom']; const globalOptions = [ '--log-level', '--debug', '--no-cache', '--cache-ttl', '--clear-cache', '--token', '--save-token', '--format', '--color', '--quiet', '--tips', '--no-tips', '--ascii', '--help', '--version' ]; switch (shell) { case 'fish': return this.generateFishCompletion(commands, globalOptions); case 'bash': return this.generateBashCompletion(commands, globalOptions); case 'zsh': return this.generateZshCompletion(commands, globalOptions); default: return ''; } } generateFishCompletion(commands, options) { let script = '# Fish completions for bktide\n'; script += '# Generated by bktide completions fish\n\n'; script += '# Disable file completions\n'; script += 'complete -c bktide -f\n'; script += 'complete -c bin/bktide -f\n\n'; script += '# Commands\n'; for (const cmd of commands) { script += `complete -c bktide -n __fish_use_subcommand -a ${cmd}\n`; script += `complete -c bin/bktide -n __fish_use_subcommand -a ${cmd}\n`; } script += '\n# Global options\n'; for (const opt of options) { const shortOpt = this.getShortOption(opt); if (shortOpt) { script += `complete -c bktide -s ${shortOpt} -l ${opt.replace('--', '')}\n`; script += `complete -c bin/bktide -s ${shortOpt} -l ${opt.replace('--', '')}\n`; } else { script += `complete -c bktide -l ${opt.replace('--', '')}\n`; script += `complete -c bin/bktide -l ${opt.replace('--', '')}\n`; } } return script; } generateBashCompletion(commands, options) { let script = '#!/bin/bash\n'; script += '# Bash completions for bktide\n'; script += '# Generated by bktide completions bash\n\n'; script += '_bktide() {\n'; script += ' local cur prev commands options\n'; script += ' COMPREPLY=()\n'; script += ' cur="${COMP_WORDS[COMP_CWORD]}"\n'; script += ' prev="${COMP_WORDS[COMP_CWORD-1]}"\n'; script += ` commands="${commands.join(' ')}"\n`; script += ` options="${options.join(' ')}"\n\n`; script += ' if [[ ${COMP_CWORD} -eq 1 ]]; then\n'; script += ' COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )\n'; script += ' else\n'; script += ' COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )\n'; script += ' fi\n'; script += ' return 0\n'; script += '}\n\n'; script += 'complete -F _bktide bktide\n'; script += 'complete -F _bktide bin/bktide\n'; return script; } generateZshCompletion(commands, options) { let script = '#compdef bktide\n'; script += '# Zsh completions for bktide\n'; script += '# Generated by bktide completions zsh\n\n'; script += '_bktide() {\n'; script += ' local -a commands options\n'; script += ` commands=(${commands.map(c => `'${c}'`).join(' ')})\n`; script += ` options=(${options.map(o => `'${o}'`).join(' ')})\n\n`; script += ' if (( CURRENT == 2 )); then\n'; script += ' _describe -t commands "bktide commands" commands\n'; script += ' else\n'; script += ' _describe -t options "bktide options" options\n'; script += ' fi\n'; script += '}\n\n'; script += '_bktide "$@"\n'; return script; } getShortOption(longOption) { const shortMap = { '--debug': 'd', '--token': 't', '--format': 'f', '--quiet': 'q', '--help': 'h', '--version': 'V', }; return shortMap[longOption]; } showInstallInstructions(shell) { // Use console.error to ensure instructions go to stderr, not stdout console.error('\n'); console.error(`To install ${shell} completions:`); switch (shell) { case 'fish': console.error(' Option 1 (User-specific):'); console.error(' bktide completions fish > ~/.config/fish/completions/bktide.fish'); console.error(''); console.error(' Option 2 (System-wide - requires sudo):'); console.error(' bktide completions fish | sudo tee /usr/share/fish/vendor_completions.d/bktide.fish'); console.error(''); console.error(' For local development with bin/bktide:'); console.error(' The completions will work for both "bktide" and "bin/bktide" automatically'); break; case 'bash': console.error(' Option 1 (User-specific - add to ~/.bashrc):'); console.error(' source <(bktide completions bash)'); console.error(''); console.error(' Option 2 (System-wide - requires sudo):'); console.error(' bktide completions bash | sudo tee /etc/bash_completion.d/bktide'); console.error(''); console.error(' For macOS with Homebrew bash-completion:'); console.error(' bktide completions bash > $(brew --prefix)/etc/bash_completion.d/bktide'); break; case 'zsh': console.error(' Option 1 (User-specific - add to ~/.zshrc):'); console.error(' source <(bktide completions zsh)'); console.error(''); console.error(' Option 2 (Add to fpath - add to ~/.zshrc):'); console.error(' mkdir -p ~/.zsh/completions'); console.error(' bktide completions zsh > ~/.zsh/completions/_bktide'); console.error(' echo "fpath=(~/.zsh/completions $fpath)" >> ~/.zshrc'); console.error(' echo "autoload -U compinit && compinit" >> ~/.zshrc'); break; } console.error(''); console.error('After installation, restart your shell or run:'); switch (shell) { case 'fish': console.error(' source ~/.config/fish/config.fish'); break; case 'bash': console.error(' source ~/.bashrc'); break; case 'zsh': console.error(' source ~/.zshrc'); break; } } } //# sourceMappingURL=GenerateCompletions.js.map