diff --git a/src/utils/__tests__/env-api-key.test.ts b/src/utils/__tests__/env-api-key.test.ts new file mode 100644 index 00000000..e1ed75d8 --- /dev/null +++ b/src/utils/__tests__/env-api-key.test.ts @@ -0,0 +1,58 @@ +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'; +import path from 'path'; +import { tmpdir } from 'os'; +import { readApiKeyFromEnv } from '@utils/env-api-key'; + +describe('readApiKeyFromEnv', () => { + let tmpDir: string; + let cwdSpy: jest.SpyInstance; + + beforeEach(() => { + tmpDir = mkdtempSync(path.join(tmpdir(), 'wizard-env-test-')); + cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue(tmpDir); + }); + + afterEach(() => { + cwdSpy.mockRestore(); + rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('returns undefined when no env file exists', () => { + expect(readApiKeyFromEnv()).toBeUndefined(); + }); + + it('reads the key from .env', () => { + writeFileSync( + path.join(tmpDir, '.env'), + 'POSTHOG_PERSONAL_API_KEY=phx_from_env\n', + ); + expect(readApiKeyFromEnv()).toBe('phx_from_env'); + }); + + it('prefers .env.local over .env', () => { + writeFileSync( + path.join(tmpDir, '.env'), + 'POSTHOG_PERSONAL_API_KEY=phx_from_env\n', + ); + writeFileSync( + path.join(tmpDir, '.env.local'), + 'POSTHOG_PERSONAL_API_KEY=phx_from_local\n', + ); + expect(readApiKeyFromEnv()).toBe('phx_from_local'); + }); + + it('does not crash when .env is a directory (e.g. a Python virtualenv)', () => { + mkdirSync(path.join(tmpDir, '.env')); + expect(() => readApiKeyFromEnv()).not.toThrow(); + expect(readApiKeyFromEnv()).toBeUndefined(); + }); + + it('falls back to .env when .env.local is a directory', () => { + mkdirSync(path.join(tmpDir, '.env.local')); + writeFileSync( + path.join(tmpDir, '.env'), + 'POSTHOG_PERSONAL_API_KEY=phx_from_env\n', + ); + expect(readApiKeyFromEnv()).toBe('phx_from_env'); + }); +}); diff --git a/src/utils/env-api-key.ts b/src/utils/env-api-key.ts index 2eae97dc..5c92f680 100644 --- a/src/utils/env-api-key.ts +++ b/src/utils/env-api-key.ts @@ -9,12 +9,19 @@ export function readApiKeyFromEnv(): string | undefined { const envFiles = ['.env.local', '.env']; for (const envFile of envFiles) { const envPath = path.join(process.cwd(), envFile); - if (fs.existsSync(envPath)) { - const content = fs.readFileSync(envPath, 'utf8'); - const match = content.match(/^POSTHOG_PERSONAL_API_KEY=(.+)$/m); - if (match) { - return match[1].trim(); - } + let content: string; + try { + // `.env`/`.env.local` is occasionally a directory (e.g. a Python + // virtualenv named `.env`), so read directly and skip on failure rather + // than guarding with existsSync, which returns true for directories and + // lets readFileSync throw EISDIR. + content = fs.readFileSync(envPath, 'utf8'); + } catch { + continue; + } + const match = content.match(/^POSTHOG_PERSONAL_API_KEY=(.+)$/m); + if (match) { + return match[1].trim(); } } return undefined;