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

159 lines 5.91 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'; 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.options); 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) { // Only log the error message and stack trace if (error instanceof Error) { logger.error(error, 'Error occurred'); // If it's a GraphQL error or API error, show more details const apiError = error; if (apiError.response?.errors) { apiError.response.errors.forEach((gqlError, index) => { logger.error({ path: gqlError.path }, `GraphQL Error ${index + 1}: ${gqlError.message}`); }); } // Show request details if available and in debug mode if (debug && apiError.request) { logger.debug({ url: apiError.request.url, method: apiError.request.method }, 'Request Details'); } } else if (typeof error === 'object') { logger.error({ error }, 'Unknown error occurred'); } else { logger.error({ error }, 'Unknown error occurred'); } if (debug) { logger.debug({ timestamp: new Date().toISOString(), nodeVersion: process.version, platform: `${process.platform} (${process.arch})` }, 'Debug Information'); } } // 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); } if (options.requiresToken) { throw new Error('API token required. Set via --token, BUILDKITE_API_TOKEN/BK_TOKEN env vars, or store it using --save-token.'); } else { return; } } /** * 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