Skip to content

Commit 7c2d3e5

Browse files
committed
fix: ensure .env.local is added to .gitignore during install
1 parent 707193b commit 7c2d3e5

2 files changed

Lines changed: 111 additions & 3 deletions

File tree

src/lib/env-writer.spec.ts

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ describe('writeEnvLocal', () => {
1212
});
1313

1414
afterEach(() => {
15-
const envPath = join(testDir, '.env.local');
16-
if (existsSync(envPath)) {
17-
unlinkSync(envPath);
15+
for (const file of ['.env.local', '.gitignore']) {
16+
const filePath = join(testDir, file);
17+
if (existsSync(filePath)) {
18+
unlinkSync(filePath);
19+
}
1820
}
1921
});
2022

@@ -133,4 +135,82 @@ describe('writeEnvLocal', () => {
133135
const content = readFileSync(join(testDir, '.env.local'), 'utf-8');
134136
expect(content).toContain('WORKOS_API_KEY=sk_test_123');
135137
});
138+
139+
describe('gitignore handling', () => {
140+
const envVars = {
141+
WORKOS_CLIENT_ID: 'client_123',
142+
WORKOS_REDIRECT_URI: 'http://localhost:3000/callback',
143+
};
144+
145+
it('creates .gitignore with .env.local when no .gitignore exists', () => {
146+
writeEnvLocal(testDir, envVars);
147+
148+
const gitignorePath = join(testDir, '.gitignore');
149+
expect(existsSync(gitignorePath)).toBe(true);
150+
expect(readFileSync(gitignorePath, 'utf-8')).toBe('.env.local\n');
151+
});
152+
153+
it('appends .env.local to existing .gitignore that does not include it', () => {
154+
const gitignorePath = join(testDir, '.gitignore');
155+
writeFileSync(gitignorePath, 'node_modules\ndist\n');
156+
157+
writeEnvLocal(testDir, envVars);
158+
159+
const content = readFileSync(gitignorePath, 'utf-8');
160+
expect(content).toContain('node_modules\n');
161+
expect(content).toContain('.env.local\n');
162+
});
163+
164+
it('does not duplicate .env.local if already present', () => {
165+
const gitignorePath = join(testDir, '.gitignore');
166+
writeFileSync(gitignorePath, 'node_modules\n.env.local\n');
167+
168+
writeEnvLocal(testDir, envVars);
169+
170+
const content = readFileSync(gitignorePath, 'utf-8');
171+
const matches = content.match(/\.env\.local/g);
172+
expect(matches).toHaveLength(1);
173+
});
174+
175+
it('does not add .env.local if .env*.local pattern exists', () => {
176+
const gitignorePath = join(testDir, '.gitignore');
177+
writeFileSync(gitignorePath, 'node_modules\n.env*.local\n');
178+
179+
writeEnvLocal(testDir, envVars);
180+
181+
const content = readFileSync(gitignorePath, 'utf-8');
182+
expect(content).not.toContain('\n.env.local\n');
183+
});
184+
185+
it('does not add .env.local if .env* pattern exists', () => {
186+
const gitignorePath = join(testDir, '.gitignore');
187+
writeFileSync(gitignorePath, '.env*\n');
188+
189+
writeEnvLocal(testDir, envVars);
190+
191+
const content = readFileSync(gitignorePath, 'utf-8');
192+
expect(content).toBe('.env*\n');
193+
});
194+
195+
it('preserves existing .gitignore content when appending', () => {
196+
const gitignorePath = join(testDir, '.gitignore');
197+
const original = 'node_modules\ndist\n.DS_Store\n';
198+
writeFileSync(gitignorePath, original);
199+
200+
writeEnvLocal(testDir, envVars);
201+
202+
const content = readFileSync(gitignorePath, 'utf-8');
203+
expect(content).toBe(original + '.env.local\n');
204+
});
205+
206+
it('handles .gitignore without trailing newline', () => {
207+
const gitignorePath = join(testDir, '.gitignore');
208+
writeFileSync(gitignorePath, 'node_modules');
209+
210+
writeEnvLocal(testDir, envVars);
211+
212+
const content = readFileSync(gitignorePath, 'utf-8');
213+
expect(content).toBe('node_modules\n.env.local\n');
214+
});
215+
});
136216
});

src/lib/env-writer.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,32 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
22
import { join } from 'path';
33
import { parseEnvFile } from '../utils/env-parser.js';
44

5+
const ENV_LOCAL_COVERING_PATTERNS = ['.env.local', '.env*.local', '.env*'];
6+
7+
/**
8+
* Ensure .env.local is in .gitignore.
9+
* Creates .gitignore if it doesn't exist.
10+
* No-ops if a covering pattern is already present.
11+
*/
12+
function ensureGitignore(installDir: string): void {
13+
const gitignorePath = join(installDir, '.gitignore');
14+
15+
if (!existsSync(gitignorePath)) {
16+
writeFileSync(gitignorePath, '.env.local\n');
17+
return;
18+
}
19+
20+
const content = readFileSync(gitignorePath, 'utf-8');
21+
const lines = content.split('\n').map((line) => line.trim());
22+
23+
if (lines.some((line) => ENV_LOCAL_COVERING_PATTERNS.includes(line))) {
24+
return;
25+
}
26+
27+
const separator = content.endsWith('\n') ? '' : '\n';
28+
writeFileSync(gitignorePath, `${content}${separator}.env.local\n`);
29+
}
30+
531
interface EnvVars {
632
WORKOS_API_KEY?: string;
733
WORKOS_CLIENT_ID: string;
@@ -50,4 +76,6 @@ export function writeEnvLocal(installDir: string, envVars: Partial<EnvVars>): vo
5076
.join('\n');
5177

5278
writeFileSync(envPath, content + '\n');
79+
80+
ensureGitignore(installDir);
5381
}

0 commit comments

Comments
 (0)