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
JavaScript
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