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

201 lines 8.67 kB
import { BaseCommand } from './BaseCommand.js'; import { logger } from '../services/logger.js'; import { TokenSetupGuide } from '../services/TokenSetupGuide.js'; import prompts from 'prompts'; import { FormatterFactory, FormatterType } from '../formatters/FormatterFactory.js'; import { isRunningInAlfred } from '../utils/alfred.js'; import { Reporter } from '../ui/reporter.js'; export class ManageToken extends BaseCommand { formatter; reporter; constructor(options) { super(options); this.formatter = FormatterFactory.getFormatter(FormatterType.TOKEN, options?.format); this.reporter = new Reporter(options?.format || 'plain', options?.quiet, options?.tips); } static get requiresToken() { return false; } async execute(options) { try { // Handle option priorities: if multiple options are provided, // we'll process them in this priority: store > reset > check if (options.store) { const { success, errors } = await this.storeToken(); if (success) { // Add next-steps hints after successful token storage (no redundant success message) this.reporter.tips([ 'Verify access with: bktide token --check', 'Explore your organizations: bktide orgs', 'List pipelines: bktide pipelines' ]); return 0; } else { const formattedErrors = this.formatter.formatAuthErrors('storing', errors); logger.console(formattedErrors); return 1; } } else if (options.reset) { await this.resetToken(); } else if (options.check) { const { errors } = await this.checkToken({ format: options.format }); if (errors.length > 0) { const formattedErrors = this.formatter.formatAuthErrors('validating', errors); logger.console(formattedErrors); return 0; } } else { const { errors } = await this.checkOrStoreToken({ format: options.format }); if (errors.length > 0) { const formattedErrors = this.formatter.formatAuthErrors('checking or storing', errors); logger.console(formattedErrors); return 0; } } return 0; // Success } catch (error) { const formattedError = this.formatter.formatError('executing', error); logger.console(formattedError); return 1; // Error } } async storeToken() { try { let tokenToStore; // If token is provided in options, use it if (this.options.token) { tokenToStore = this.options.token; } else { const guide = new TokenSetupGuide(); const env = guide.detectEnvironment(); if (env === 'agent') { const guidance = guide.getStoreGuidance(); logger.console(guidance); return { success: false, errors: [new Error('Token setup must be done interactively by the user.')] }; } if (isRunningInAlfred()) { return { success: false, errors: [new Error('In Alfred, set token via Workflow Configuration.')] }; } // Otherwise prompt the user const response = await prompts({ type: 'password', name: 'token', message: 'Enter your Buildkite API token:', validate: value => value.length > 0 ? true : 'Please enter a valid token' }); // Check if user cancelled the prompt (Ctrl+C) if (!response.token) { return { success: false, errors: [new Error('Token storage cancelled')] }; } tokenToStore = response.token; } // Ensure we have a valid token before proceeding if (!tokenToStore) { return { success: false, errors: [new Error('No token provided')] }; } // Validate the token using the CredentialManager const validationResult = await BaseCommand.credentialManager.validateToken(tokenToStore, { showProgress: true // Show progress when validating during store }); if (!validationResult.canListOrganizations) { throw new Error('Token is invalid or does not have access to list organizations'); } if (!validationResult.valid) { const invalidOrgs = Object.entries(validationResult.organizations) .filter(([_, status]) => !status.graphql || !status.builds || !status.organizations) .map(([org, status]) => { const invalidApis = []; if (!status.graphql) invalidApis.push('GraphQL'); if (!status.builds) invalidApis.push('Builds'); if (!status.organizations) invalidApis.push('Organizations'); return `${org} (${invalidApis.join(', ')})`; }); throw new Error(`Token has limited access in some organizations: ${invalidOrgs.join(', ')}`); } // Store the token if it's valid const success = await BaseCommand.credentialManager.saveToken(tokenToStore); return { success, errors: [] }; } catch (error) { return { success: false, errors: [error] }; } } async resetToken() { try { const hadToken = await BaseCommand.credentialManager.hasToken(); let success = false; if (hadToken) { success = await BaseCommand.credentialManager.deleteToken(); } const formattedResult = this.formatter.formatTokenResetResult(success, hadToken); logger.console(formattedResult); } catch (error) { const formattedError = this.formatter.formatError('resetting', error); logger.console(formattedError); } } async checkOrStoreToken(options) { const { status, errors } = await this.checkToken({ ...options, suppressOutput: true }); if (!status.hasToken || !status.isValid) { const { success, errors: storeErrors } = await this.storeToken(); if (success) { return { stored: true, errors: [] }; } else { return { stored: false, errors: storeErrors }; } } return { stored: false, errors }; } async checkToken(options) { const errors = []; // Get token using standard resolution: --token flag > env var > keychain // This ensures `bktide token --check --token <token>` validates the provided token // and `BUILDKITE_API_TOKEN=<token> bktide token --check` works as expected const token = this.options.token || process.env.BUILDKITE_API_TOKEN || process.env.BK_TOKEN || await BaseCommand.credentialManager.getToken(); const hasToken = !!token; let isValid = false; let validation = { valid: false, canListOrganizations: false, organizations: {} }; if (hasToken && token) { // Validate the token using the CredentialManager try { validation = await BaseCommand.credentialManager.validateToken(token, { format: options?.format, showProgress: true }); isValid = validation.valid && validation.canListOrganizations; } catch (error) { errors.push(error); } } const tokenStatus = { hasToken, isValid, validation }; if (!options?.suppressOutput) { const formattedResult = this.formatter.formatTokenStatus(tokenStatus); logger.console(formattedResult); } return { status: tokenStatus, errors }; } } //# sourceMappingURL=ManageToken.js.map