From 35e4a2f83a54d532e5487de2b803742b69244c18 Mon Sep 17 00:00:00 2001 From: dal Date: Fri, 26 Sep 2025 14:31:39 -0600 Subject: [PATCH] cli GH compat --- apps/cli/src/commands/auth.tsx | 13 +++-- .../commands/deploy/deployment/strategies.ts | 13 +++-- apps/cli/src/index.tsx | 51 +++++++++++++++++++ 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/apps/cli/src/commands/auth.tsx b/apps/cli/src/commands/auth.tsx index 64ea4ded1..d4c5335f7 100644 --- a/apps/cli/src/commands/auth.tsx +++ b/apps/cli/src/commands/auth.tsx @@ -44,6 +44,9 @@ export function Auth({ apiKey, host, local, cloud, clear, noSave, show }: AuthPr const [apiKeyValue, setApiKeyValue] = useState(''); const [error, setError] = useState(null); const [existingCreds, setExistingCreds] = useState(null); + + // Check if we're in a TTY environment + const isTTY = process.stdin.isTTY; // Initialize based on flags useEffect(() => { @@ -95,6 +98,11 @@ export function Auth({ apiKey, host, local, cloud, clear, noSave, show }: AuthPr setHostValue(initialHost || host || DEFAULT_HOST); setApiKeyValue(apiKey); setStep('validate'); + } else if (!isTTY) { + // Non-TTY environment - require API key from flags or env + console.error('❌ Non-interactive environment detected.'); + console.error(' Please provide API key via --api-key flag or BUSTER_API_KEY environment variable.'); + exit(); } else if (initialHost || host) { // If we only have host, set it and prompt for API key setHostValue(initialHost || host || ''); @@ -107,10 +115,7 @@ export function Auth({ apiKey, host, local, cloud, clear, noSave, show }: AuthPr } }, [show, clear, apiKey, host, local, cloud, exit]); - // Handle keyboard input - // Check if we're in a TTY environment to avoid errors - const isTTY = process.stdin.isTTY; - + // Handle keyboard input only if in TTY mode useInput((input, key) => { // Skip input handling if not in TTY or not in input steps if (!isTTY || (step !== 'host' && step !== 'apikey')) return; diff --git a/apps/cli/src/commands/deploy/deployment/strategies.ts b/apps/cli/src/commands/deploy/deployment/strategies.ts index 34b52497b..d7dfe7895 100644 --- a/apps/cli/src/commands/deploy/deployment/strategies.ts +++ b/apps/cli/src/commands/deploy/deployment/strategies.ts @@ -4,7 +4,7 @@ import type { deploy } from '@buster/server-shared'; type UnifiedDeployRequest = deploy.UnifiedDeployRequest; type UnifiedDeployResponse = deploy.UnifiedDeployResponse; -import { loadCredentials } from '../../../utils/credentials'; +import { getCredentials } from '../../../utils/credentials'; import type { DeploymentFailure, DeploymentItem } from '../schemas'; /** @@ -85,15 +85,22 @@ export function createLiveDeployer(sdk: BusterSDK): DeployFunction { * This is the only function that performs I/O in this module */ export async function createAuthenticatedDeployer(): Promise { - const credentials = await loadCredentials(); + // Use getCredentials which checks env vars first, then saved credentials + const credentials = await getCredentials(); if (!credentials?.apiKey) { + const isCIEnvironment = process.env.CI || !process.stdin.isTTY; + if (isCIEnvironment) { + throw new Error( + 'Not authenticated. Please set BUSTER_API_KEY environment variable or use --api-key flag.' + ); + } throw new Error('Not authenticated. Please run: buster auth'); } const sdk = createBusterSDK({ apiKey: credentials.apiKey, - apiUrl: credentials.apiUrl || 'https://api.buster.so', + apiUrl: credentials.apiUrl || 'https://api2.buster.so', }); return createLiveDeployer(sdk); diff --git a/apps/cli/src/index.tsx b/apps/cli/src/index.tsx index abe62e7aa..40938bec6 100644 --- a/apps/cli/src/index.tsx +++ b/apps/cli/src/index.tsx @@ -68,6 +68,57 @@ program .option('--show', 'Show current credentials') .option('--no-save', "Don't save credentials to disk") .action(async (options) => { + // Check if we're in a non-TTY environment (CI/CD) + const isTTY = process.stdin.isTTY; + const isCIEnvironment = process.env.CI || !isTTY; + + // In CI environments, we need to handle auth differently + if (isCIEnvironment && !options.apiKey && !process.env.BUSTER_API_KEY) { + console.error('❌ Non-interactive environment detected.'); + console.error(' Please provide API key via --api-key flag or BUSTER_API_KEY environment variable.'); + console.error(' Example: buster auth --api-key YOUR_API_KEY'); + console.error(' Or set: export BUSTER_API_KEY=YOUR_API_KEY'); + process.exit(1); + } + + // If we have an API key in CI, just validate and save it without interactive UI + if (isCIEnvironment && (options.apiKey || process.env.BUSTER_API_KEY)) { + const { createBusterSDK } = await import('@buster/sdk'); + const { saveCredentials } = await import('./utils/credentials'); + + const apiKey = options.apiKey || process.env.BUSTER_API_KEY; + const host = options.host || (options.local ? 'http://localhost:3001' : (options.cloud ? 'https://api2.buster.so' : 'https://api2.buster.so')); + const normalizedHost = host.startsWith('http') ? host : `https://${host}`; + + try { + // Validate the API key + const sdk = createBusterSDK({ + apiKey: apiKey, + apiUrl: normalizedHost, + timeout: 30000, + }); + + const isValid = await sdk.auth.isApiKeyValid(); + + if (isValid) { + if (!options.noSave) { + await saveCredentials({ apiKey, apiUrl: normalizedHost }); + console.log('✅ Authentication successful and credentials saved.'); + } else { + console.log('✅ Authentication successful (credentials not saved due to --no-save flag).'); + } + process.exit(0); + } else { + console.error('❌ Invalid API key.'); + process.exit(1); + } + } catch (error) { + console.error('❌ Authentication failed:', error.message); + process.exit(1); + } + } + + // For interactive environments, use the Ink UI render(); });