mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-16 14:53:19 -07:00
Fix .env directory extension issue
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
Reference in New Issue
Block a user