Skip to content

Commit 471cc4f

Browse files
fix: Make migrations API key optional (#153)
* fix: Make migrations API key optional Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: Cover optional API key resolution paths --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 29818d0 commit 471cc4f

5 files changed

Lines changed: 58 additions & 9 deletions

File tree

src/bin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,10 +2407,10 @@ yargs(rawArgs)
24072407
}),
24082408
async (argv) => {
24092409
await applyInsecureStorage(argv.insecureStorage);
2410-
const { resolveApiKey } = await import('./lib/api-key.js');
2410+
const { resolveOptionalApiKey } = await import('./lib/api-key.js');
24112411
const { getMigrationsPassthroughArgs, runMigrations } = await import('./commands/migrations.js');
24122412
const passthrough = getMigrationsPassthroughArgs(rawArgs);
2413-
await runMigrations(passthrough, resolveApiKey({ apiKey: argv.apiKey }));
2413+
await runMigrations(passthrough, resolveOptionalApiKey({ apiKey: argv.apiKey }));
24142414
},
24152415
)
24162416
.command(

src/commands/migrations.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ describe('runMigrations', () => {
2020
expect(process.env.WORKOS_SECRET_KEY).toBe('sk_test_123');
2121
});
2222

23+
it('does not require WORKOS_SECRET_KEY when no API key is provided', async () => {
24+
await runMigrations(['wizard']);
25+
expect(process.env.WORKOS_SECRET_KEY).toBeUndefined();
26+
expect(mockParseAsync).toHaveBeenCalledWith(['wizard'], { from: 'user' });
27+
});
28+
2329
it('delegates to Commander parseAsync with correct args', async () => {
2430
await runMigrations(['import', '--csv', 'users.csv'], 'sk_test_123');
2531
expect(mockParseAsync).toHaveBeenCalledWith(['import', '--csv', 'users.csv'], { from: 'user' });

src/commands/migrations.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ export function getMigrationsPassthroughArgs(rawArgs: string[]): string[] {
4343
return passthrough;
4444
}
4545

46-
export async function runMigrations(args: string[], apiKey: string): Promise<void> {
47-
process.env.WORKOS_SECRET_KEY = apiKey;
46+
export async function runMigrations(args: string[], apiKey?: string): Promise<void> {
47+
if (apiKey) {
48+
process.env.WORKOS_SECRET_KEY = apiKey;
49+
}
4850

4951
const { program } = (await import('@workos/migrations/dist/cli/index.js')) as {
5052
program: {

src/lib/api-key.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ vi.mock('node:os', async (importOriginal) => {
3939
});
4040

4141
const { saveConfig, setInsecureConfigStorage, clearConfig } = await import('./config-store.js');
42-
const { resolveApiKey, resolveApiBaseUrl } = await import('./api-key.js');
42+
const { resolveApiKey, resolveOptionalApiKey, resolveApiBaseUrl } = await import('./api-key.js');
4343

4444
describe('api-key', () => {
4545
const originalEnv = process.env;
@@ -117,6 +117,40 @@ describe('api-key', () => {
117117
});
118118
});
119119

120+
describe('resolveOptionalApiKey', () => {
121+
it('returns --api-key flag over env var and stored key', () => {
122+
process.env.WORKOS_API_KEY = 'sk_env_var';
123+
saveConfig({
124+
activeEnvironment: 'prod',
125+
environments: { prod: { name: 'prod', type: 'production', apiKey: 'sk_stored' } },
126+
});
127+
expect(resolveOptionalApiKey({ apiKey: 'sk_flag' })).toBe('sk_flag');
128+
});
129+
130+
it('returns WORKOS_API_KEY env var when no flag provided', () => {
131+
process.env.WORKOS_API_KEY = 'sk_env_var';
132+
saveConfig({
133+
activeEnvironment: 'prod',
134+
environments: { prod: { name: 'prod', type: 'production', apiKey: 'sk_stored' } },
135+
});
136+
expect(resolveOptionalApiKey()).toBe('sk_env_var');
137+
});
138+
139+
it('returns undefined when no API key is available', () => {
140+
mockExitWithError.mockClear();
141+
expect(resolveOptionalApiKey()).toBeUndefined();
142+
expect(mockExitWithError).not.toHaveBeenCalled();
143+
});
144+
145+
it('returns configured API key when available', () => {
146+
saveConfig({
147+
activeEnvironment: 'prod',
148+
environments: { prod: { name: 'prod', type: 'production', apiKey: 'sk_stored' } },
149+
});
150+
expect(resolveOptionalApiKey()).toBe('sk_stored');
151+
});
152+
});
153+
120154
describe('resolveApiBaseUrl', () => {
121155
it('returns default URL when no config', () => {
122156
expect(resolveApiBaseUrl()).toBe('https://api.workos.com');

src/lib/api-key.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ export interface ApiKeyOptions {
1717
}
1818

1919
export function resolveApiKey(options?: ApiKeyOptions): string {
20+
const apiKey = resolveOptionalApiKey(options);
21+
if (apiKey) return apiKey;
22+
23+
exitWithError({
24+
code: 'no_api_key',
25+
message: 'No API key configured. Run `workos env add` to configure an environment, or set WORKOS_API_KEY.',
26+
});
27+
}
28+
29+
export function resolveOptionalApiKey(options?: ApiKeyOptions): string | undefined {
2030
if (options?.apiKey) return options.apiKey;
2131

2232
const envVar = process.env.WORKOS_API_KEY;
@@ -25,10 +35,7 @@ export function resolveApiKey(options?: ApiKeyOptions): string {
2535
const activeEnv = getActiveEnvironment();
2636
if (activeEnv?.apiKey) return activeEnv.apiKey;
2737

28-
exitWithError({
29-
code: 'no_api_key',
30-
message: 'No API key configured. Run `workos env add` to configure an environment, or set WORKOS_API_KEY.',
31-
});
38+
return undefined;
3239
}
3340

3441
export function resolveApiBaseUrl(): string {

0 commit comments

Comments
 (0)