Skip to content

Commit 4609c44

Browse files
committed
fix: ensure .gitignore is updated when .clix directory is first created
Previously ensureGitignore was only called after project selection in SetupUI/LoginUI. If the user interrupted (Ctrl+C) after login but before project selection, the .clix directory was created without a matching .gitignore entry. Extracted gitignore logic to shared utility and moved the call into directory creation (ensureStateDir/ensureConfigDir) so it runs exactly once at the earliest possible moment.
1 parent d1a0da6 commit 4609c44

5 files changed

Lines changed: 62 additions & 61 deletions

File tree

src/lib/auth/credentials.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { chmod, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
2-
import { join } from 'node:path';
2+
import { dirname, join } from 'node:path';
3+
import { ensureClixGitignore } from '../utils/gitignore';
34
import { findProjectRoot } from '../utils/path';
45
import { AUTH_ENV_VARS, getAuth0Config } from './config';
56
import { AuthError } from './errors';
@@ -70,6 +71,7 @@ export class CredentialsManager {
7071
await stat(this.stateDirPath);
7172
} catch {
7273
await mkdir(this.stateDirPath, { recursive: true, mode: 0o755 });
74+
await ensureClixGitignore(dirname(this.stateDirPath));
7375
}
7476
}
7577

src/lib/config/project-config-manager.ts

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
22
import { join } from 'node:path';
33
import { ConfigError, ERROR_CODES } from '../errors/types';
4+
import { ensureClixGitignore } from '../utils/gitignore';
45
import {
56
CURRENT_PROJECT_CONFIG_VERSION,
67
ensureLatestVersion,
@@ -16,16 +17,6 @@ import {
1617
*/
1718
const CONFIG_HEADER = '';
1819

19-
/**
20-
* Gitignore patterns to check for .clix directory.
21-
*/
22-
const GITIGNORE_PATTERNS = ['.clix', '.clix/', '/.clix', '/.clix/'];
23-
24-
/**
25-
* Gitignore entry to add.
26-
*/
27-
const GITIGNORE_ENTRY = '\n# Clix CLI local config\n.clix/\n';
28-
2920
/**
3021
* ProjectConfigManager handles loading and saving project-local configuration.
3122
* Configuration is stored in .clix/config.jsonc in the project root.
@@ -94,6 +85,7 @@ export class ProjectConfigManager {
9485
await stat(this.configDirPath);
9586
} catch {
9687
await mkdir(this.configDirPath, { recursive: true, mode: 0o755 });
88+
await ensureClixGitignore(this.projectPath);
9789
}
9890
}
9991

@@ -221,50 +213,6 @@ export class ProjectConfigManager {
221213
}
222214
}
223215

224-
/**
225-
* Ensure .clix is in .gitignore.
226-
* Adds entry if not already present.
227-
*
228-
* @returns True if gitignore was modified
229-
*/
230-
async ensureGitignore(): Promise<boolean> {
231-
const gitignorePath = join(this.projectPath, '.gitignore');
232-
233-
try {
234-
let content = '';
235-
236-
// Try to read existing .gitignore
237-
try {
238-
content = await readFile(gitignorePath, 'utf-8');
239-
} catch {
240-
// File doesn't exist, will create new one
241-
}
242-
243-
// Check if .clix is already ignored
244-
const lines = content.split('\n');
245-
const hasClixIgnore = lines.some((line) => {
246-
const trimmed = line.trim();
247-
return GITIGNORE_PATTERNS.includes(trimmed);
248-
});
249-
250-
if (hasClixIgnore) {
251-
return false;
252-
}
253-
254-
// Add .clix to gitignore
255-
const newContent =
256-
content.endsWith('\n') || content === ''
257-
? `${content}${GITIGNORE_ENTRY}`
258-
: `${content}\n${GITIGNORE_ENTRY}`;
259-
260-
await writeFile(gitignorePath, newContent, 'utf-8');
261-
return true;
262-
} catch {
263-
// Non-fatal: log warning but continue
264-
return false;
265-
}
266-
}
267-
268216
/**
269217
* Clear the cached config (useful for testing).
270218
*/

src/lib/utils/gitignore.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { readFile, writeFile } from 'node:fs/promises';
2+
import { join } from 'node:path';
3+
4+
/**
5+
* Gitignore patterns to check for .clix directory.
6+
*/
7+
const GITIGNORE_PATTERNS = ['.clix', '.clix/', '/.clix', '/.clix/'];
8+
9+
/**
10+
* Gitignore entry to add.
11+
*/
12+
const GITIGNORE_ENTRY = '\n# Clix CLI local config\n.clix/\n';
13+
14+
/**
15+
* Ensure .clix is in .gitignore at the given project path.
16+
* Adds entry if not already present. Creates .gitignore if it doesn't exist.
17+
*
18+
* @param projectPath - The project root directory containing .gitignore
19+
* @returns True if gitignore was modified
20+
*/
21+
export async function ensureClixGitignore(projectPath: string): Promise<boolean> {
22+
const gitignorePath = join(projectPath, '.gitignore');
23+
24+
try {
25+
let content = '';
26+
27+
// Try to read existing .gitignore
28+
try {
29+
content = await readFile(gitignorePath, 'utf-8');
30+
} catch {
31+
// File doesn't exist, will create new one
32+
}
33+
34+
// Check if .clix is already ignored
35+
const lines = content.split('\n');
36+
const hasClixIgnore = lines.some((line) => {
37+
const trimmed = line.trim();
38+
return GITIGNORE_PATTERNS.includes(trimmed);
39+
});
40+
41+
if (hasClixIgnore) {
42+
return false;
43+
}
44+
45+
// Add .clix to gitignore
46+
const newContent =
47+
content.endsWith('\n') || content === ''
48+
? `${content}${GITIGNORE_ENTRY}`
49+
: `${content}\n${GITIGNORE_ENTRY}`;
50+
51+
await writeFile(gitignorePath, newContent, 'utf-8');
52+
return true;
53+
} catch {
54+
// Non-fatal: log warning but continue
55+
return false;
56+
}
57+
}

src/ui/LoginUI.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,6 @@ export const LoginUI: React.FC<LoginUIProps> = ({ onComplete, onError }) => {
141141
const projectConfigManager = getProjectConfigManager(workspacePath);
142142
await projectConfigManager.save(projectConfig);
143143

144-
// Ensure .clix is in .gitignore
145-
await projectConfigManager.ensureGitignore();
146-
147144
setSavedConfig(projectConfig);
148145
setPhase('complete');
149146
setTimeout(() => {

src/ui/SetupUI.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ export const SetupUI: React.FC<SetupUIProps> = ({ onComplete, onError, projectPa
129129
const projectConfigManager = getProjectConfigManager(workspacePath);
130130
await projectConfigManager.save(config);
131131

132-
// Ensure .clix is in .gitignore
133-
await projectConfigManager.ensureGitignore();
134-
135132
setSavedConfig(config);
136133
setPhase('complete');
137134

0 commit comments

Comments
 (0)