diff --git a/src/cli/commands/env.ts b/src/cli/commands/env.ts index d9d1608..f4d4ab7 100644 --- a/src/cli/commands/env.ts +++ b/src/cli/commands/env.ts @@ -7,11 +7,62 @@ interface EnvVar { value: string } -export async function envList(name: string | undefined) { +function parseKeyValue(keyOrKeyValue: string, valueArg?: string): { key: string, value: string } | null { + if (valueArg !== undefined) { + const key = keyOrKeyValue.trim() + if (!key) { console.error('Key cannot be empty'); return null } + return { key, value: valueArg } + } + const eqIndex = keyOrKeyValue.indexOf('=') + if (eqIndex === -1) { + console.error('Invalid format. Use: KEY value or KEY=value') + return null + } + const key = keyOrKeyValue.slice(0, eqIndex).trim() + if (!key) { console.error('Key cannot be empty'); return null } + return { key, value: keyOrKeyValue.slice(eqIndex + 1) } +} + +async function globalEnvSet(keyOrKeyValue: string, valueArg?: string) { + const parsed = parseKeyValue(keyOrKeyValue, valueArg) + if (!parsed) return + const { key, value } = parsed + + try { + const result = await post<{ ok: boolean, error?: string }>('/api/env', { key, value }) + if (result?.ok) { + console.log(color.green(`Set global ${color.bold(key.toUpperCase())}`)) + } else { + console.error(result?.error ?? 'Failed to set variable') + } + } catch (error) { + handleError(error) + } +} + +export async function envList(name: string | undefined, opts: { global?: boolean }) { + if (opts.global) { + const vars = await get('/api/env') + console.log(color.bold().cyan('Global Environment Variables')) + console.log() + if (!vars || vars.length === 0) { + console.log(color.gray(' No global environment variables set')) + return + } + for (const v of vars) { + console.log(` ${color.bold(v.key)}=${color.gray(v.value)}`) + } + return + } + const appName = resolveAppName(name) if (!appName) return - const vars = await get(`/api/apps/${appName}/env`) + const [vars, globalVars] = await Promise.all([ + get(`/api/apps/${appName}/env`), + get('/api/env'), + ]) + if (!vars) { console.error(`App not found: ${appName}`) return @@ -20,7 +71,9 @@ export async function envList(name: string | undefined) { console.log(color.bold().cyan(`Environment Variables for ${appName}`)) console.log() - if (vars.length === 0) { + const appKeys = new Set(vars.map(v => v.key)) + + if (vars.length === 0 && (!globalVars || globalVars.length === 0)) { console.log(color.gray(' No environment variables set')) return } @@ -28,31 +81,30 @@ export async function envList(name: string | undefined) { for (const v of vars) { console.log(` ${color.bold(v.key)}=${color.gray(v.value)}`) } + + if (globalVars && globalVars.length > 0) { + const inherited = globalVars.filter(v => !appKeys.has(v.key)) + if (inherited.length > 0) { + if (vars.length > 0) console.log() + console.log(color.gray(' Inherited from global:')) + for (const v of inherited) { + console.log(` ${color.bold(v.key)}=${color.gray(v.value)}`) + } + } + } } -export async function envSet(name: string | undefined, keyOrKeyValue: string, valueArg?: string) { - let key: string - let value: string - - if (valueArg !== undefined) { - // KEY value format - key = keyOrKeyValue.trim() - value = valueArg - } else { - // KEY=value format - const eqIndex = keyOrKeyValue.indexOf('=') - if (eqIndex === -1) { - console.error('Invalid format. Use: KEY value or KEY=value') - return - } - key = keyOrKeyValue.slice(0, eqIndex).trim() - value = keyOrKeyValue.slice(eqIndex + 1) +export async function envSet(name: string | undefined, keyOrKeyValue: string, valueArg: string | undefined, opts: { global?: boolean }) { + // With --global, args shift: name becomes key, key becomes value + if (opts.global) { + const actualKey = name ?? keyOrKeyValue + const actualValue = name ? keyOrKeyValue : valueArg + return globalEnvSet(actualKey, actualValue) } - if (!key) { - console.error('Key cannot be empty') - return - } + const parsed = parseKeyValue(keyOrKeyValue, valueArg) + if (!parsed) return + const { key, value } = parsed const appName = resolveAppName(name) if (!appName) return @@ -70,7 +122,21 @@ export async function envSet(name: string | undefined, keyOrKeyValue: string, va } } -export async function envRm(name: string | undefined, key: string) { +export async function envRm(name: string | undefined, key: string, opts: { global?: boolean }) { + // With --global, args shift: name becomes key + if (opts.global) { + const actualKey = name ?? key + if (!actualKey) { + console.error('Key is required') + return + } + const ok = await del(`/api/env/${actualKey.toUpperCase()}`) + if (ok) { + console.log(color.green(`Removed global ${color.bold(actualKey.toUpperCase())}`)) + } + return + } + if (!key) { console.error('Key is required') return diff --git a/src/cli/setup.ts b/src/cli/setup.ts index a10a659..135dc57 100644 --- a/src/cli/setup.ts +++ b/src/cli/setup.ts @@ -231,6 +231,7 @@ const env = program .helpGroup('Config:') .description('Manage environment variables') .argument('[name]', 'app name (uses current directory if omitted)') + .option('-g, --global', 'manage global variables shared by all apps') .action(envList) env @@ -239,6 +240,7 @@ env .argument('[name]', 'app name (uses current directory if omitted)') .argument('', 'variable name') .argument('[value]', 'variable value (or use KEY=value format)') + .option('-g, --global', 'set a global variable shared by all apps') .action(envSet) env @@ -246,6 +248,7 @@ env .description('Remove an environment variable') .argument('[name]', 'app name (uses current directory if omitted)') .argument('', 'variable name to remove') + .option('-g, --global', 'remove a global variable') .action(envRm) program