From fd836de858cc2f888e94b8d3b8a37e9d87aec3e6 Mon Sep 17 00:00:00 2001 From: jonatas Date: Wed, 13 May 2026 23:02:18 +0000 Subject: [PATCH 1/2] fix: Make migrations API key optional Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/bin.ts | 4 ++-- src/commands/migrations.spec.ts | 6 ++++++ src/commands/migrations.ts | 6 ++++-- src/lib/api-key.spec.ts | 18 +++++++++++++++++- src/lib/api-key.ts | 15 +++++++++++---- 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index a5f893bd..8943f2f1 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -2407,10 +2407,10 @@ yargs(rawArgs) }), async (argv) => { await applyInsecureStorage(argv.insecureStorage); - const { resolveApiKey } = await import('./lib/api-key.js'); + const { resolveOptionalApiKey } = await import('./lib/api-key.js'); const { getMigrationsPassthroughArgs, runMigrations } = await import('./commands/migrations.js'); const passthrough = getMigrationsPassthroughArgs(rawArgs); - await runMigrations(passthrough, resolveApiKey({ apiKey: argv.apiKey })); + await runMigrations(passthrough, resolveOptionalApiKey({ apiKey: argv.apiKey })); }, ) .command( diff --git a/src/commands/migrations.spec.ts b/src/commands/migrations.spec.ts index e9d0b6cd..3d94b7ac 100644 --- a/src/commands/migrations.spec.ts +++ b/src/commands/migrations.spec.ts @@ -20,6 +20,12 @@ describe('runMigrations', () => { expect(process.env.WORKOS_SECRET_KEY).toBe('sk_test_123'); }); + it('does not require WORKOS_SECRET_KEY when no API key is provided', async () => { + await runMigrations(['wizard']); + expect(process.env.WORKOS_SECRET_KEY).toBeUndefined(); + expect(mockParseAsync).toHaveBeenCalledWith(['wizard'], { from: 'user' }); + }); + it('delegates to Commander parseAsync with correct args', async () => { await runMigrations(['import', '--csv', 'users.csv'], 'sk_test_123'); expect(mockParseAsync).toHaveBeenCalledWith(['import', '--csv', 'users.csv'], { from: 'user' }); diff --git a/src/commands/migrations.ts b/src/commands/migrations.ts index 83638a45..475b911a 100644 --- a/src/commands/migrations.ts +++ b/src/commands/migrations.ts @@ -43,8 +43,10 @@ export function getMigrationsPassthroughArgs(rawArgs: string[]): string[] { return passthrough; } -export async function runMigrations(args: string[], apiKey: string): Promise { - process.env.WORKOS_SECRET_KEY = apiKey; +export async function runMigrations(args: string[], apiKey?: string): Promise { + if (apiKey) { + process.env.WORKOS_SECRET_KEY = apiKey; + } const { program } = (await import('@workos/migrations/dist/cli/index.js')) as { program: { diff --git a/src/lib/api-key.spec.ts b/src/lib/api-key.spec.ts index 9d82ddbf..4f4be9a9 100644 --- a/src/lib/api-key.spec.ts +++ b/src/lib/api-key.spec.ts @@ -39,7 +39,7 @@ vi.mock('node:os', async (importOriginal) => { }); const { saveConfig, setInsecureConfigStorage, clearConfig } = await import('./config-store.js'); -const { resolveApiKey, resolveApiBaseUrl } = await import('./api-key.js'); +const { resolveApiKey, resolveOptionalApiKey, resolveApiBaseUrl } = await import('./api-key.js'); describe('api-key', () => { const originalEnv = process.env; @@ -117,6 +117,22 @@ describe('api-key', () => { }); }); + describe('resolveOptionalApiKey', () => { + it('returns undefined when no API key is available', () => { + mockExitWithError.mockClear(); + expect(resolveOptionalApiKey()).toBeUndefined(); + expect(mockExitWithError).not.toHaveBeenCalled(); + }); + + it('returns configured API key when available', () => { + saveConfig({ + activeEnvironment: 'prod', + environments: { prod: { name: 'prod', type: 'production', apiKey: 'sk_stored' } }, + }); + expect(resolveOptionalApiKey()).toBe('sk_stored'); + }); + }); + describe('resolveApiBaseUrl', () => { it('returns default URL when no config', () => { expect(resolveApiBaseUrl()).toBe('https://api.workos.com'); diff --git a/src/lib/api-key.ts b/src/lib/api-key.ts index 6d884c62..d8135b86 100644 --- a/src/lib/api-key.ts +++ b/src/lib/api-key.ts @@ -17,6 +17,16 @@ export interface ApiKeyOptions { } export function resolveApiKey(options?: ApiKeyOptions): string { + const apiKey = resolveOptionalApiKey(options); + if (apiKey) return apiKey; + + exitWithError({ + code: 'no_api_key', + message: 'No API key configured. Run `workos env add` to configure an environment, or set WORKOS_API_KEY.', + }); +} + +export function resolveOptionalApiKey(options?: ApiKeyOptions): string | undefined { if (options?.apiKey) return options.apiKey; const envVar = process.env.WORKOS_API_KEY; @@ -25,10 +35,7 @@ export function resolveApiKey(options?: ApiKeyOptions): string { const activeEnv = getActiveEnvironment(); if (activeEnv?.apiKey) return activeEnv.apiKey; - exitWithError({ - code: 'no_api_key', - message: 'No API key configured. Run `workos env add` to configure an environment, or set WORKOS_API_KEY.', - }); + return undefined; } export function resolveApiBaseUrl(): string { From eb66811f5484f9880b0584fb32a4b2fa36550f1a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 23:07:28 +0000 Subject: [PATCH 2/2] test: Cover optional API key resolution paths --- src/lib/api-key.spec.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/api-key.spec.ts b/src/lib/api-key.spec.ts index 4f4be9a9..e8eaa047 100644 --- a/src/lib/api-key.spec.ts +++ b/src/lib/api-key.spec.ts @@ -118,6 +118,24 @@ describe('api-key', () => { }); describe('resolveOptionalApiKey', () => { + it('returns --api-key flag over env var and stored key', () => { + process.env.WORKOS_API_KEY = 'sk_env_var'; + saveConfig({ + activeEnvironment: 'prod', + environments: { prod: { name: 'prod', type: 'production', apiKey: 'sk_stored' } }, + }); + expect(resolveOptionalApiKey({ apiKey: 'sk_flag' })).toBe('sk_flag'); + }); + + it('returns WORKOS_API_KEY env var when no flag provided', () => { + process.env.WORKOS_API_KEY = 'sk_env_var'; + saveConfig({ + activeEnvironment: 'prod', + environments: { prod: { name: 'prod', type: 'production', apiKey: 'sk_stored' } }, + }); + expect(resolveOptionalApiKey()).toBe('sk_env_var'); + }); + it('returns undefined when no API key is available', () => { mockExitWithError.mockClear(); expect(resolveOptionalApiKey()).toBeUndefined();