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

132 lines 5.02 kB
import { BuildkiteClient } from '../services/BuildkiteClient.js'; import { BuildkiteRestClient } from '../services/BuildkiteRestClient.js'; import { FormatterFactory } from '../formatters/index.js'; import { logger } from '../services/logger.js'; import { CredentialManager } from '../services/CredentialManager.js'; import { TokenSetupGuide } from '../services/TokenSetupGuide.js'; import { GuidanceError } from '../errors/index.js'; import { displayCLIError } from '../utils/cli-error-handler.js'; export class BaseCommand { token; requiresToken = true; _client; _restClient; _restClientOptions; options; initialized = false; static credentialManager = new CredentialManager(); constructor(options) { this.options = options || {}; this.token = this.options.token; if (this.options.debug) { // Include token length (not the actual token) for debugging auth issues // Debug mode is already handled here by logger.debug use logger.debug('BaseCommandHandler options:', { ...options, token: this.token ? `${this.token.substring(0, 4)}...${this.token.substring(this.token.length - 4)} (${this.token.length} chars)` : 'Not provided' }); } } get client() { if (this._client) { return this._client; } else { if (this.token) { this._client = new BuildkiteClient(this.token, this.options); return this._client; } else { throw new Error('No token provided'); } } } get restClient() { if (this._restClient) { return this._restClient; } else { if (this.token) { this._restClient = new BuildkiteRestClient(this.token, this.restClientOptions); return this._restClient; } else { throw new Error('No token provided'); } } } get restClientOptions() { if (this._restClientOptions) { return this._restClientOptions; } else { // Configure REST client with the same caching options const restClientOptions = { debug: this.options?.debug, caching: !this.options?.noCache, }; // If a specific cache TTL is provided, apply it to REST client if (this.options?.cacheTTL) { restClientOptions.cacheTTLs = { default: this.options.cacheTTL, builds: this.options.cacheTTL, }; } return restClientOptions; } } static get requiresToken() { return true; } async ensureInitialized() { // No additional initialization needed return Promise.resolve(); } handleError(error, debug = false) { // Use the CLI error display for user-friendly formatting displayCLIError(error, debug); } // Static helper to get token from options, keyring, or environment static async getToken(options) { // First check if token is provided directly in options if (options.token) { return options.token; } // Prefer environment variables first under Alfred or if explicitly provided const envToken = process.env.BUILDKITE_API_TOKEN || process.env.BK_TOKEN; if (envToken) { return envToken; } // Next try to get token from keyring (outside Alfred this will lazy-load) try { const storedToken = await this.credentialManager.getToken(); if (storedToken) { logger.debug('Using token from system keychain'); return storedToken; } } catch (error) { logger.debug('Error retrieving token from keychain', error); } // No token found anywhere. Throw a GuidanceError with environment-aware // setup instructions. The error formatter shows the guidance directly // without adding its own contextual hints/tips on top. const guide = new TokenSetupGuide(); throw new GuidanceError(guide.getSetupGuidance()); } /** * Get the appropriate formatter based on command-specific type and format option * @param type The formatter type ('pipeline', 'build', 'viewer') * @param options Command options that may include a format * @returns The appropriate formatter */ getFormatter(type, options) { // Format precedence: command line option > constructor option > default const format = options.format || this.options.format || 'plain'; if (options.debug) { logger.debug(`Using ${format} formatter for ${type}`); } return FormatterFactory.getFormatter(type, format); } } //# sourceMappingURL=BaseCommand.js.map