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