diff --git a/src/index.ts b/src/index.ts index 4c6e2254..0b7b4b43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import { Command } from 'commander'; @@ -10,6 +10,7 @@ import { saveConfig, isSetupComplete, getMercuryHome, + appendToMercuryEnv, ensureCreatorField, clearTelegramAccess, isProviderConfigured, @@ -539,18 +540,6 @@ async function promptValidatedValue( } } -function appendToEnv(key: string, value: string): void { - const envPath = join(getMercuryHome(), '.env'); - let envContent = ''; - if (existsSync(envPath)) { - envContent = readFileSync(envPath, 'utf-8'); - } - const lines = envContent.split('\n').filter((l: string) => !l.startsWith(`${key}=`) && l.trim() !== ''); - lines.push(`${key}=${value}`); - writeFileSync(envPath, lines.join('\n') + '\n', 'utf-8'); - process.env[key] = value; -} - function parseGithubRepo(input: string): { owner: string; repo: string } | null { const trimmed = input.trim().replace(/\/+$/, ''); const urlMatch = trimmed.match(/github\.com\/([^/]+)\/([^/]+)/); @@ -1078,7 +1067,7 @@ async function configure(existingConfig?: MercuryConfig): Promise { const ghTokenCurrent = process.env.GITHUB_TOKEN ? ` [${maskKey(process.env.GITHUB_TOKEN)}]` : ''; const ghToken = await ask(chalk.white(` 2. GitHub PAT${ghTokenCurrent}: `)); if (ghToken) { - appendToEnv('GITHUB_TOKEN', ghToken); + appendToMercuryEnv('GITHUB_TOKEN', ghToken); } if (config.github.username || process.env.GITHUB_TOKEN) { @@ -1122,14 +1111,14 @@ async function configure(existingConfig?: MercuryConfig): Promise { const spotifyClientId = await ask(chalk.white(` 1. Spotify Client ID${spotifyIdCurrent}: `)); if (spotifyClientId) { config.spotify.clientId = spotifyClientId; - appendToEnv('SPOTIFY_CLIENT_ID', spotifyClientId); + appendToMercuryEnv('SPOTIFY_CLIENT_ID', spotifyClientId); } const spotifySecretCurrent = isReconfig && config.spotify.clientSecret ? ` [${maskKey(config.spotify.clientSecret)}]` : ''; const spotifyClientSecret = await ask(chalk.white(` 2. Spotify Client Secret${spotifySecretCurrent}: `)); if (spotifyClientSecret) { config.spotify.clientSecret = spotifyClientSecret; - appendToEnv('SPOTIFY_CLIENT_SECRET', spotifyClientSecret); + appendToMercuryEnv('SPOTIFY_CLIENT_SECRET', spotifyClientSecret); } if (spotifyClientId || spotifyClientSecret) { diff --git a/src/utils/config.test.ts b/src/utils/config.test.ts new file mode 100644 index 00000000..b0a6e19e --- /dev/null +++ b/src/utils/config.test.ts @@ -0,0 +1,47 @@ +import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +let mercuryHome = ''; + +async function loadConfigModule() { + vi.resetModules(); + vi.stubEnv('MERCURY_HOME', mercuryHome); + return import('./config.js'); +} + +describe('appendToMercuryEnv', () => { + afterEach(() => { + vi.unstubAllEnvs(); + if (mercuryHome) { + rmSync(mercuryHome, { recursive: true, force: true }); + mercuryHome = ''; + } + }); + + it('creates the Mercury home directory and env file on first write', async () => { + mercuryHome = join(mkdtempSync(join(tmpdir(), 'mercury-config-test-')), 'nested-home'); + const { appendToMercuryEnv } = await loadConfigModule(); + + appendToMercuryEnv('GITHUB_TOKEN', 'ghp_test_token'); + + const envPath = join(mercuryHome, '.env'); + expect(existsSync(envPath)).toBe(true); + expect(readFileSync(envPath, 'utf-8')).toBe('GITHUB_TOKEN=ghp_test_token\n'); + expect(process.env.GITHUB_TOKEN).toBe('ghp_test_token'); + }); + + it('replaces an existing key without duplicating unrelated env entries', async () => { + mercuryHome = mkdtempSync(join(tmpdir(), 'mercury-config-test-')); + const { appendToMercuryEnv } = await loadConfigModule(); + + appendToMercuryEnv('GITHUB_TOKEN', 'ghp_old_token'); + appendToMercuryEnv('SPOTIFY_CLIENT_ID', 'spotify-client'); + appendToMercuryEnv('GITHUB_TOKEN', 'ghp_new_token'); + + expect(readFileSync(join(mercuryHome, '.env'), 'utf-8')).toBe( + 'SPOTIFY_CLIENT_ID=spotify-client\nGITHUB_TOKEN=ghp_new_token\n', + ); + }); +}); diff --git a/src/utils/config.ts b/src/utils/config.ts index e78b8e55..e71bad41 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -16,6 +16,27 @@ export function getMercuryHome(): string { return process.env.MERCURY_HOME || MERCURY_HOME; } +export function appendToMercuryEnv(key: string, value: string): void { + const dir = getMercuryHome(); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + const envPath = join(dir, '.env'); + let envContent = ''; + if (existsSync(envPath)) { + envContent = readFileSync(envPath, 'utf-8'); + } + + const lines = envContent + .split('\n') + .filter((line) => !line.startsWith(`${key}=`) && line.trim() !== ''); + + lines.push(`${key}=${value}`); + process.env[key] = value; + writeFileSync(envPath, lines.join('\n') + '\n', 'utf-8'); +} + export function getMemoryDir(): string { return join(getMercuryHome(), 'memory'); }