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
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';
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