Fix .env directory extension issue

This commit is contained in:
Christine Betts
2026-02-24 11:41:05 -05:00
parent 15f6c8b8da
commit efd26bc660
4 changed files with 98 additions and 2 deletions
@@ -530,6 +530,7 @@ Would you like to attempt to install via "git clone" instead?`,
return this.loadedExtensions;
}
for (const subdir of fs.readdirSync(extensionsDir)) {
if (subdir === '.env') continue;
const extensionDir = path.join(extensionsDir, subdir);
await this.loadExtension(extensionDir);
}
+11
View File
@@ -280,6 +280,17 @@ describe('extension tests', () => {
]);
});
it('should ignore .env directory in extensions folder', async () => {
// Create a .env directory
const envDir = path.join(userExtensionsDir, '.env');
fs.mkdirSync(envDir);
const extensions = await extensionManager.loadExtensions();
expect(extensions).toEqual([]);
const { debugLogger } = await import('@google/gemini-cli-core');
expect(debugLogger.error).not.toHaveBeenCalled();
});
it('should annotate disabled extensions', async () => {
createExtension({
extensionsDir: userExtensionsDir,
@@ -468,6 +468,32 @@ describe('extensionSettings', () => {
expect(mockIsAvailable).toHaveBeenCalled();
expect(mockListSecrets).not.toHaveBeenCalled();
});
it('should throw error if .env is a directory when prompting for settings', async () => {
const config: ExtensionConfig = {
name: 'test-ext',
version: '1.0.0',
settings: [{ name: 's1', description: 'd1', envVar: 'VAR1' }],
};
const envFilePath = path.join(extensionDir, '.env');
if (fs.existsSync(envFilePath)) {
fs.unlinkSync(envFilePath);
}
fs.mkdirSync(envFilePath);
mockRequestSetting.mockResolvedValue('new-value');
await expect(
maybePromptForSettings(
config,
'12345',
mockRequestSetting,
undefined,
undefined,
),
).rejects.toThrow(
`Cannot update user-scoped settings because "${envFilePath}" is a directory.`,
);
});
});
describe('promptForSetting', () => {
@@ -590,6 +616,23 @@ describe('extensionSettings', () => {
SENSITIVE_VAR: 'workspace-secret',
});
});
it('should ignore .env if it is a directory', async () => {
const userEnvPath = path.join(extensionDir, EXTENSION_SETTINGS_FILENAME);
if (fs.existsSync(userEnvPath)) {
fs.unlinkSync(userEnvPath);
}
fs.mkdirSync(userEnvPath);
const contents = await getScopedEnvContents(
config,
extensionId,
ExtensionSettingScope.USER,
tempWorkspaceDir,
);
expect(contents).toEqual({});
});
});
describe('getEnvContents (merged)', () => {
@@ -890,5 +933,27 @@ describe('extensionSettings', () => {
const actualContent = await fsPromises.readFile(expectedEnvPath, 'utf-8');
expect(actualContent).toContain('VAR1="value with \\"quotes\\""');
});
it('should throw error if .env is a directory when updating a non-sensitive setting', async () => {
const envFilePath = path.join(extensionDir, '.env');
if (fs.existsSync(envFilePath)) {
fs.unlinkSync(envFilePath);
}
fs.mkdirSync(envFilePath);
mockRequestSetting.mockResolvedValue('new-value');
await expect(
updateSetting(
config,
'12345',
'VAR1',
mockRequestSetting,
ExtensionSettingScope.USER,
tempWorkspaceDir,
),
).rejects.toThrow(
`Cannot update user-scoped settings because "${envFilePath}" is a directory.`,
);
});
});
});
@@ -124,6 +124,15 @@ export async function maybePromptForSettings(
const envContent = formatEnvContent(nonSensitiveSettings);
if (
fsSync.existsSync(envFilePath) &&
fsSync.statSync(envFilePath).isDirectory()
) {
throw new Error(
`Cannot update ${scope}-scoped settings because "${envFilePath}" is a directory.`,
);
}
await fs.writeFile(envFilePath, envContent);
}
@@ -172,7 +181,7 @@ export async function getScopedEnvContents(
);
const envFilePath = getEnvFilePath(extensionName, scope, workspaceDir);
let customEnv: Record<string, string> = {};
if (fsSync.existsSync(envFilePath)) {
if (fsSync.existsSync(envFilePath) && fsSync.statSync(envFilePath).isFile()) {
const envFile = fsSync.readFileSync(envFilePath, 'utf-8');
customEnv = dotenv.parse(envFile);
}
@@ -258,6 +267,16 @@ export async function updateSetting(
// For non-sensitive settings, we need to read the existing .env file,
// update the value, and write it back, preserving any other values.
const envFilePath = getEnvFilePath(extensionName, scope, workspaceDir);
if (
fsSync.existsSync(envFilePath) &&
fsSync.statSync(envFilePath).isDirectory()
) {
throw new Error(
`Cannot update ${scope}-scoped settings because "${envFilePath}" is a directory.`,
);
}
let envContent = '';
if (fsSync.existsSync(envFilePath)) {
envContent = await fs.readFile(envFilePath, 'utf-8');
@@ -323,7 +342,7 @@ async function clearSettings(
envFilePath: string,
keychain: KeychainTokenStorage,
) {
if (fsSync.existsSync(envFilePath)) {
if (fsSync.existsSync(envFilePath) && fsSync.statSync(envFilePath).isFile()) {
await fs.writeFile(envFilePath, '');
}
if (!(await keychain.isAvailable())) {