@google/gemini-cli 0.21.0-nightly.20251207.025e450ac → 0.21.0-nightly.20251209.ec9a8c7a7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/google-gemini-cli-0.21.0-nightly.20251207.025e450ac.tgz +0 -0
- package/dist/package.json +2 -2
- package/dist/src/commands/extensions/settings.js +22 -7
- package/dist/src/commands/extensions/settings.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.d.ts +6 -1
- package/dist/src/config/extensions/extensionSettings.js +56 -27
- package/dist/src/config/extensions/extensionSettings.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.test.js +125 -100
- package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
- package/dist/src/config/settingsSchema.js +4 -0
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/ui/commands/chatCommand.js +2 -2
- package/dist/src/ui/commands/chatCommand.js.map +1 -1
- package/dist/src/ui/commands/hooksCommand.js +2 -0
- package/dist/src/ui/commands/hooksCommand.js.map +1 -1
- package/dist/src/ui/commands/mcpCommand.js +1 -1
- package/dist/src/ui/commands/mcpCommand.js.map +1 -1
- package/dist/src/ui/commands/restoreCommand.js +1 -1
- package/dist/src/ui/commands/restoreCommand.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +20 -2
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +135 -0
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/hooks/useCommandCompletion.d.ts +2 -0
- package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
- package/dist/src/ui/hooks/useSlashCompletion.d.ts +2 -0
- package/dist/src/ui/hooks/useSlashCompletion.js +2 -0
- package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -1
- package/dist/src/ui/utils/commandUtils.d.ts +1 -1
- package/dist/src/ui/utils/commandUtils.js.map +1 -1
- package/dist/src/utils/sandboxUtils.js +3 -2
- package/dist/src/utils/sandboxUtils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/google-gemini-cli-0.21.0-nightly.20251204.3da4fd5f7.tgz +0 -0
|
Binary file
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@google/gemini-cli",
|
|
3
|
-
"version": "0.21.0-nightly.
|
|
3
|
+
"version": "0.21.0-nightly.20251209.ec9a8c7a7",
|
|
4
4
|
"description": "Gemini CLI",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"config": {
|
|
28
|
-
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.21.0-nightly.
|
|
28
|
+
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.21.0-nightly.20251209.ec9a8c7a7"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@google/gemini-cli-core": "file:../core",
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { updateSetting, promptForSetting, ExtensionSettingScope, getScopedEnvContents, } from '../../config/extensions/extensionSettings.js';
|
|
7
7
|
import { getExtensionAndManager } from './utils.js';
|
|
8
8
|
import { debugLogger } from '@google/gemini-cli-core';
|
|
9
9
|
import { exitCli } from '../utils.js';
|
|
10
10
|
const setCommand = {
|
|
11
|
-
command: 'set <name> <setting>',
|
|
11
|
+
command: 'set [--scope] <name> <setting>',
|
|
12
12
|
describe: 'Set a specific setting for an extension.',
|
|
13
13
|
builder: (yargs) => yargs
|
|
14
14
|
.positional('name', {
|
|
@@ -20,9 +20,15 @@ const setCommand = {
|
|
|
20
20
|
describe: 'The setting to configure (name or env var).',
|
|
21
21
|
type: 'string',
|
|
22
22
|
demandOption: true,
|
|
23
|
+
})
|
|
24
|
+
.option('scope', {
|
|
25
|
+
describe: 'The scope to set the setting in.',
|
|
26
|
+
type: 'string',
|
|
27
|
+
choices: ['user', 'workspace'],
|
|
28
|
+
default: 'user',
|
|
23
29
|
}),
|
|
24
30
|
handler: async (args) => {
|
|
25
|
-
const { name, setting } = args;
|
|
31
|
+
const { name, setting, scope } = args;
|
|
26
32
|
const { extension, extensionManager } = await getExtensionAndManager(name);
|
|
27
33
|
if (!extension || !extensionManager) {
|
|
28
34
|
return;
|
|
@@ -32,7 +38,7 @@ const setCommand = {
|
|
|
32
38
|
debugLogger.error(`Could not find configuration for extension "${name}".`);
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
|
-
await updateSetting(extensionConfig, extension.id, setting, promptForSetting);
|
|
41
|
+
await updateSetting(extensionConfig, extension.id, setting, promptForSetting, scope);
|
|
36
42
|
await exitCli();
|
|
37
43
|
},
|
|
38
44
|
};
|
|
@@ -57,11 +63,20 @@ const listCommand = {
|
|
|
57
63
|
debugLogger.log(`Extension "${name}" has no settings to configure.`);
|
|
58
64
|
return;
|
|
59
65
|
}
|
|
60
|
-
const
|
|
66
|
+
const userSettings = await getScopedEnvContents(extensionConfig, extension.id, ExtensionSettingScope.USER);
|
|
67
|
+
const workspaceSettings = await getScopedEnvContents(extensionConfig, extension.id, ExtensionSettingScope.WORKSPACE);
|
|
68
|
+
const mergedSettings = { ...userSettings, ...workspaceSettings };
|
|
61
69
|
debugLogger.log(`Settings for "${name}":`);
|
|
62
70
|
for (const setting of extensionConfig.settings) {
|
|
63
|
-
const value =
|
|
71
|
+
const value = mergedSettings[setting.envVar];
|
|
64
72
|
let displayValue;
|
|
73
|
+
let scopeInfo = '';
|
|
74
|
+
if (workspaceSettings[setting.envVar] !== undefined) {
|
|
75
|
+
scopeInfo = ' (workspace)';
|
|
76
|
+
}
|
|
77
|
+
else if (userSettings[setting.envVar] !== undefined) {
|
|
78
|
+
scopeInfo = ' (user)';
|
|
79
|
+
}
|
|
65
80
|
if (value === undefined) {
|
|
66
81
|
displayValue = '[not set]';
|
|
67
82
|
}
|
|
@@ -74,7 +89,7 @@ const listCommand = {
|
|
|
74
89
|
debugLogger.log(`
|
|
75
90
|
- ${setting.name} (${setting.envVar})`);
|
|
76
91
|
debugLogger.log(` Description: ${setting.description}`);
|
|
77
|
-
debugLogger.log(` Value: ${displayValue}`);
|
|
92
|
+
debugLogger.log(` Value: ${displayValue}${scopeInfo}`);
|
|
78
93
|
}
|
|
79
94
|
await exitCli();
|
|
80
95
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../../src/commands/extensions/settings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../../src/commands/extensions/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import type { ExtensionConfig } from '../extension.js';
|
|
7
|
+
export declare enum ExtensionSettingScope {
|
|
8
|
+
USER = "user",
|
|
9
|
+
WORKSPACE = "workspace"
|
|
10
|
+
}
|
|
7
11
|
export interface ExtensionSetting {
|
|
8
12
|
name: string;
|
|
9
13
|
description: string;
|
|
@@ -12,5 +16,6 @@ export interface ExtensionSetting {
|
|
|
12
16
|
}
|
|
13
17
|
export declare function maybePromptForSettings(extensionConfig: ExtensionConfig, extensionId: string, requestSetting: (setting: ExtensionSetting) => Promise<string>, previousExtensionConfig?: ExtensionConfig, previousSettings?: Record<string, string>): Promise<void>;
|
|
14
18
|
export declare function promptForSetting(setting: ExtensionSetting): Promise<string>;
|
|
19
|
+
export declare function getScopedEnvContents(extensionConfig: ExtensionConfig, extensionId: string, scope: ExtensionSettingScope): Promise<Record<string, string>>;
|
|
15
20
|
export declare function getEnvContents(extensionConfig: ExtensionConfig, extensionId: string): Promise<Record<string, string>>;
|
|
16
|
-
export declare function updateSetting(extensionConfig: ExtensionConfig, extensionId: string, settingKey: string, requestSetting: (setting: ExtensionSetting) => Promise<string
|
|
21
|
+
export declare function updateSetting(extensionConfig: ExtensionConfig, extensionId: string, settingKey: string, requestSetting: (setting: ExtensionSetting) => Promise<string>, scope: ExtensionSettingScope): Promise<void>;
|
|
@@ -6,10 +6,29 @@
|
|
|
6
6
|
import * as fs from 'node:fs/promises';
|
|
7
7
|
import * as fsSync from 'node:fs';
|
|
8
8
|
import * as dotenv from 'dotenv';
|
|
9
|
+
import * as path from 'node:path';
|
|
9
10
|
import { ExtensionStorage } from './storage.js';
|
|
10
11
|
import prompts from 'prompts';
|
|
11
12
|
import { debugLogger, KeychainTokenStorage } from '@google/gemini-cli-core';
|
|
12
|
-
|
|
13
|
+
import { EXTENSION_SETTINGS_FILENAME } from './variables.js';
|
|
14
|
+
export var ExtensionSettingScope;
|
|
15
|
+
(function (ExtensionSettingScope) {
|
|
16
|
+
ExtensionSettingScope["USER"] = "user";
|
|
17
|
+
ExtensionSettingScope["WORKSPACE"] = "workspace";
|
|
18
|
+
})(ExtensionSettingScope || (ExtensionSettingScope = {}));
|
|
19
|
+
const getKeychainStorageName = (extensionName, extensionId, scope) => {
|
|
20
|
+
const base = `Gemini CLI Extensions ${extensionName} ${extensionId}`;
|
|
21
|
+
if (scope === ExtensionSettingScope.WORKSPACE) {
|
|
22
|
+
return `${base} ${process.cwd()}`;
|
|
23
|
+
}
|
|
24
|
+
return base;
|
|
25
|
+
};
|
|
26
|
+
const getEnvFilePath = (extensionName, scope) => {
|
|
27
|
+
if (scope === ExtensionSettingScope.WORKSPACE) {
|
|
28
|
+
return path.join(process.cwd(), EXTENSION_SETTINGS_FILENAME);
|
|
29
|
+
}
|
|
30
|
+
return new ExtensionStorage(extensionName).getEnvFilePath();
|
|
31
|
+
};
|
|
13
32
|
export async function maybePromptForSettings(extensionConfig, extensionId, requestSetting, previousExtensionConfig, previousSettings) {
|
|
14
33
|
const { name: extensionName, settings } = extensionConfig;
|
|
15
34
|
if ((!settings || settings.length === 0) &&
|
|
@@ -17,8 +36,11 @@ export async function maybePromptForSettings(extensionConfig, extensionId, reque
|
|
|
17
36
|
previousExtensionConfig.settings.length === 0)) {
|
|
18
37
|
return;
|
|
19
38
|
}
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
// We assume user scope here because we don't have a way to ask the user for scope during the initial setup.
|
|
40
|
+
// The user can change the scope later using the `settings set` command.
|
|
41
|
+
const scope = ExtensionSettingScope.USER;
|
|
42
|
+
const envFilePath = getEnvFilePath(extensionName, scope);
|
|
43
|
+
const keychain = new KeychainTokenStorage(getKeychainStorageName(extensionName, extensionId, scope));
|
|
22
44
|
if (!settings || settings.length === 0) {
|
|
23
45
|
await clearSettings(envFilePath, keychain);
|
|
24
46
|
return;
|
|
@@ -67,15 +89,13 @@ export async function promptForSetting(setting) {
|
|
|
67
89
|
});
|
|
68
90
|
return response.value;
|
|
69
91
|
}
|
|
70
|
-
export async function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const extensionStorage = new ExtensionStorage(extensionConfig.name);
|
|
75
|
-
const keychain = new KeychainTokenStorage(getKeychainStorageName(extensionConfig.name, extensionId));
|
|
92
|
+
export async function getScopedEnvContents(extensionConfig, extensionId, scope) {
|
|
93
|
+
const { name: extensionName } = extensionConfig;
|
|
94
|
+
const keychain = new KeychainTokenStorage(getKeychainStorageName(extensionName, extensionId, scope));
|
|
95
|
+
const envFilePath = getEnvFilePath(extensionName, scope);
|
|
76
96
|
let customEnv = {};
|
|
77
|
-
if (fsSync.existsSync(
|
|
78
|
-
const envFile = fsSync.readFileSync(
|
|
97
|
+
if (fsSync.existsSync(envFilePath)) {
|
|
98
|
+
const envFile = fsSync.readFileSync(envFilePath, 'utf-8');
|
|
79
99
|
customEnv = dotenv.parse(envFile);
|
|
80
100
|
}
|
|
81
101
|
if (extensionConfig.settings) {
|
|
@@ -90,7 +110,15 @@ export async function getEnvContents(extensionConfig, extensionId) {
|
|
|
90
110
|
}
|
|
91
111
|
return customEnv;
|
|
92
112
|
}
|
|
93
|
-
export async function
|
|
113
|
+
export async function getEnvContents(extensionConfig, extensionId) {
|
|
114
|
+
if (!extensionConfig.settings || extensionConfig.settings.length === 0) {
|
|
115
|
+
return Promise.resolve({});
|
|
116
|
+
}
|
|
117
|
+
const userSettings = await getScopedEnvContents(extensionConfig, extensionId, ExtensionSettingScope.USER);
|
|
118
|
+
const workspaceSettings = await getScopedEnvContents(extensionConfig, extensionId, ExtensionSettingScope.WORKSPACE);
|
|
119
|
+
return { ...userSettings, ...workspaceSettings };
|
|
120
|
+
}
|
|
121
|
+
export async function updateSetting(extensionConfig, extensionId, settingKey, requestSetting, scope) {
|
|
94
122
|
const { name: extensionName, settings } = extensionConfig;
|
|
95
123
|
if (!settings || settings.length === 0) {
|
|
96
124
|
debugLogger.log('This extension does not have any settings.');
|
|
@@ -102,29 +130,30 @@ export async function updateSetting(extensionConfig, extensionId, settingKey, re
|
|
|
102
130
|
return;
|
|
103
131
|
}
|
|
104
132
|
const newValue = await requestSetting(settingToUpdate);
|
|
105
|
-
const keychain = new KeychainTokenStorage(getKeychainStorageName(extensionName, extensionId));
|
|
133
|
+
const keychain = new KeychainTokenStorage(getKeychainStorageName(extensionName, extensionId, scope));
|
|
106
134
|
if (settingToUpdate.sensitive) {
|
|
107
135
|
await keychain.setSecret(settingToUpdate.envVar, newValue);
|
|
108
136
|
return;
|
|
109
137
|
}
|
|
110
138
|
// For non-sensitive settings, we need to read the existing .env file,
|
|
111
|
-
// update the value, and write it back.
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
139
|
+
// update the value, and write it back, preserving any other values.
|
|
140
|
+
const envFilePath = getEnvFilePath(extensionName, scope);
|
|
141
|
+
let envContent = '';
|
|
142
|
+
if (fsSync.existsSync(envFilePath)) {
|
|
143
|
+
envContent = await fs.readFile(envFilePath, 'utf-8');
|
|
144
|
+
}
|
|
145
|
+
const parsedEnv = dotenv.parse(envContent);
|
|
146
|
+
parsedEnv[settingToUpdate.envVar] = newValue;
|
|
147
|
+
// We only want to write back the variables that are not sensitive.
|
|
115
148
|
const nonSensitiveSettings = {};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
const value = allSettings[setting.envVar];
|
|
122
|
-
if (value !== undefined) {
|
|
123
|
-
nonSensitiveSettings[setting.envVar] = value;
|
|
149
|
+
const sensitiveEnvVars = new Set(settings.filter((s) => s.sensitive).map((s) => s.envVar));
|
|
150
|
+
for (const [key, value] of Object.entries(parsedEnv)) {
|
|
151
|
+
if (!sensitiveEnvVars.has(key)) {
|
|
152
|
+
nonSensitiveSettings[key] = value;
|
|
124
153
|
}
|
|
125
154
|
}
|
|
126
|
-
const
|
|
127
|
-
await fs.writeFile(envFilePath,
|
|
155
|
+
const newEnvContent = formatEnvContent(nonSensitiveSettings);
|
|
156
|
+
await fs.writeFile(envFilePath, newEnvContent);
|
|
128
157
|
}
|
|
129
158
|
function getSettingsChanges(settings, oldSettings) {
|
|
130
159
|
const isSameSetting = (a, b) => a.envVar === b.envVar && (a.sensitive ?? false) === (b.sensitive ?? false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensionSettings.js","sourceRoot":"","sources":["../../../../src/config/extensions/extensionSettings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"extensionSettings.js","sourceRoot":"","sources":["../../../../src/config/extensions/extensionSettings.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import * as os from 'node:os';
|
|
9
|
-
import { getEnvContents, maybePromptForSettings, promptForSetting, updateSetting, } from './extensionSettings.js';
|
|
9
|
+
import { getEnvContents, maybePromptForSettings, promptForSetting, updateSetting, ExtensionSettingScope, getScopedEnvContents, } from './extensionSettings.js';
|
|
10
10
|
import { ExtensionStorage } from './storage.js';
|
|
11
11
|
import prompts from 'prompts';
|
|
12
12
|
import * as fsPromises from 'node:fs/promises';
|
|
13
13
|
import * as fs from 'node:fs';
|
|
14
14
|
import { KeychainTokenStorage } from '@google/gemini-cli-core';
|
|
15
|
+
import { EXTENSION_SETTINGS_FILENAME } from './variables.js';
|
|
15
16
|
vi.mock('prompts');
|
|
16
17
|
vi.mock('os', async (importOriginal) => {
|
|
17
18
|
const mockedOs = await importOriginal();
|
|
@@ -24,51 +25,54 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|
|
24
25
|
const actual = await importOriginal();
|
|
25
26
|
return {
|
|
26
27
|
...actual,
|
|
27
|
-
KeychainTokenStorage: vi.fn()
|
|
28
|
-
getSecret: vi.fn(),
|
|
29
|
-
setSecret: vi.fn(),
|
|
30
|
-
deleteSecret: vi.fn(),
|
|
31
|
-
listSecrets: vi.fn(),
|
|
32
|
-
isAvailable: vi.fn().mockResolvedValue(true),
|
|
33
|
-
})),
|
|
28
|
+
KeychainTokenStorage: vi.fn(),
|
|
34
29
|
};
|
|
35
30
|
});
|
|
36
31
|
describe('extensionSettings', () => {
|
|
37
32
|
let tempHomeDir;
|
|
33
|
+
let tempWorkspaceDir;
|
|
38
34
|
let extensionDir;
|
|
39
|
-
let
|
|
40
|
-
let keychainData;
|
|
35
|
+
let mockKeychainData;
|
|
41
36
|
beforeEach(() => {
|
|
42
37
|
vi.clearAllMocks();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.mockImplementation(async () =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
38
|
+
mockKeychainData = {};
|
|
39
|
+
vi.mocked(KeychainTokenStorage).mockImplementation((serviceName) => {
|
|
40
|
+
if (!mockKeychainData[serviceName]) {
|
|
41
|
+
mockKeychainData[serviceName] = {};
|
|
42
|
+
}
|
|
43
|
+
const keychainData = mockKeychainData[serviceName];
|
|
44
|
+
return {
|
|
45
|
+
getSecret: vi
|
|
46
|
+
.fn()
|
|
47
|
+
.mockImplementation(async (key) => keychainData[key] || null),
|
|
48
|
+
setSecret: vi
|
|
49
|
+
.fn()
|
|
50
|
+
.mockImplementation(async (key, value) => {
|
|
51
|
+
keychainData[key] = value;
|
|
52
|
+
}),
|
|
53
|
+
deleteSecret: vi.fn().mockImplementation(async (key) => {
|
|
54
|
+
delete keychainData[key];
|
|
55
|
+
}),
|
|
56
|
+
listSecrets: vi
|
|
57
|
+
.fn()
|
|
58
|
+
.mockImplementation(async () => Object.keys(keychainData)),
|
|
59
|
+
isAvailable: vi.fn().mockResolvedValue(true),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
62
|
tempHomeDir = os.tmpdir() + path.sep + `gemini-cli-test-home-${Date.now()}`;
|
|
63
|
+
tempWorkspaceDir = path.join(os.tmpdir(), `gemini-cli-test-workspace-${Date.now()}`);
|
|
63
64
|
extensionDir = path.join(tempHomeDir, '.gemini', 'extensions', 'test-ext');
|
|
64
65
|
// Spy and mock the method, but also create the directory so we can write to it.
|
|
65
66
|
vi.spyOn(ExtensionStorage.prototype, 'getExtensionDir').mockReturnValue(extensionDir);
|
|
66
67
|
fs.mkdirSync(extensionDir, { recursive: true });
|
|
68
|
+
fs.mkdirSync(tempWorkspaceDir, { recursive: true });
|
|
67
69
|
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
|
70
|
+
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
|
|
68
71
|
vi.mocked(prompts).mockClear();
|
|
69
72
|
});
|
|
70
73
|
afterEach(() => {
|
|
71
74
|
fs.rmSync(tempHomeDir, { recursive: true, force: true });
|
|
75
|
+
fs.rmSync(tempWorkspaceDir, { recursive: true, force: true });
|
|
72
76
|
vi.restoreAllMocks();
|
|
73
77
|
});
|
|
74
78
|
describe('maybePromptForSettings', () => {
|
|
@@ -150,14 +154,15 @@ describe('extensionSettings', () => {
|
|
|
150
154
|
VAR1: 'previous-VAR1',
|
|
151
155
|
SENSITIVE_VAR: 'secret',
|
|
152
156
|
};
|
|
153
|
-
|
|
157
|
+
const userKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext 12345`);
|
|
158
|
+
await userKeychain.setSecret('SENSITIVE_VAR', 'secret');
|
|
154
159
|
const envPath = path.join(extensionDir, '.env');
|
|
155
160
|
await fsPromises.writeFile(envPath, 'VAR1=previous-VAR1');
|
|
156
161
|
await maybePromptForSettings(newConfig, '12345', mockRequestSetting, previousConfig, previousSettings);
|
|
157
162
|
expect(mockRequestSetting).not.toHaveBeenCalled();
|
|
158
163
|
const actualContent = await fsPromises.readFile(envPath, 'utf-8');
|
|
159
164
|
expect(actualContent).toBe('');
|
|
160
|
-
expect(
|
|
165
|
+
expect(await userKeychain.getSecret('SENSITIVE_VAR')).toBeNull();
|
|
161
166
|
});
|
|
162
167
|
it('should remove sensitive settings from keychain', async () => {
|
|
163
168
|
const previousConfig = {
|
|
@@ -178,9 +183,10 @@ describe('extensionSettings', () => {
|
|
|
178
183
|
settings: [],
|
|
179
184
|
};
|
|
180
185
|
const previousSettings = { SENSITIVE_VAR: 'secret' };
|
|
181
|
-
|
|
186
|
+
const userKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext 12345`);
|
|
187
|
+
await userKeychain.setSecret('SENSITIVE_VAR', 'secret');
|
|
182
188
|
await maybePromptForSettings(newConfig, '12345', mockRequestSetting, previousConfig, previousSettings);
|
|
183
|
-
expect(
|
|
189
|
+
expect(await userKeychain.getSecret('SENSITIVE_VAR')).toBeNull();
|
|
184
190
|
});
|
|
185
191
|
it('should remove settings that are no longer in the config', async () => {
|
|
186
192
|
const previousConfig = {
|
|
@@ -326,7 +332,7 @@ describe('extensionSettings', () => {
|
|
|
326
332
|
expect(result).toBeUndefined();
|
|
327
333
|
});
|
|
328
334
|
});
|
|
329
|
-
describe('
|
|
335
|
+
describe('getScopedEnvContents', () => {
|
|
330
336
|
const config = {
|
|
331
337
|
name: 'test-ext',
|
|
332
338
|
version: '1.0.0',
|
|
@@ -340,30 +346,58 @@ describe('extensionSettings', () => {
|
|
|
340
346
|
},
|
|
341
347
|
],
|
|
342
348
|
};
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const
|
|
349
|
+
const extensionId = '12345';
|
|
350
|
+
it('should return combined contents from user .env and keychain for USER scope', async () => {
|
|
351
|
+
const userEnvPath = path.join(extensionDir, EXTENSION_SETTINGS_FILENAME);
|
|
352
|
+
await fsPromises.writeFile(userEnvPath, 'VAR1=user-value1');
|
|
353
|
+
const userKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext 12345`);
|
|
354
|
+
await userKeychain.setSecret('SENSITIVE_VAR', 'user-secret');
|
|
355
|
+
const contents = await getScopedEnvContents(config, extensionId, ExtensionSettingScope.USER);
|
|
348
356
|
expect(contents).toEqual({
|
|
349
|
-
VAR1: 'value1',
|
|
350
|
-
SENSITIVE_VAR: 'secret',
|
|
357
|
+
VAR1: 'user-value1',
|
|
358
|
+
SENSITIVE_VAR: 'user-secret',
|
|
351
359
|
});
|
|
352
360
|
});
|
|
353
|
-
it('should return
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
+
it('should return combined contents from workspace .env and keychain for WORKSPACE scope', async () => {
|
|
362
|
+
const workspaceEnvPath = path.join(tempWorkspaceDir, EXTENSION_SETTINGS_FILENAME);
|
|
363
|
+
await fsPromises.writeFile(workspaceEnvPath, 'VAR1=workspace-value1');
|
|
364
|
+
const workspaceKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext 12345 ${tempWorkspaceDir}`);
|
|
365
|
+
await workspaceKeychain.setSecret('SENSITIVE_VAR', 'workspace-secret');
|
|
366
|
+
const contents = await getScopedEnvContents(config, extensionId, ExtensionSettingScope.WORKSPACE);
|
|
367
|
+
expect(contents).toEqual({
|
|
368
|
+
VAR1: 'workspace-value1',
|
|
369
|
+
SENSITIVE_VAR: 'workspace-secret',
|
|
370
|
+
});
|
|
361
371
|
});
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
372
|
+
});
|
|
373
|
+
describe('getEnvContents (merged)', () => {
|
|
374
|
+
const config = {
|
|
375
|
+
name: 'test-ext',
|
|
376
|
+
version: '1.0.0',
|
|
377
|
+
settings: [
|
|
378
|
+
{ name: 's1', description: 'd1', envVar: 'VAR1' },
|
|
379
|
+
{ name: 's2', description: 'd2', envVar: 'VAR2', sensitive: true },
|
|
380
|
+
{ name: 's3', description: 'd3', envVar: 'VAR3' },
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
const extensionId = '12345';
|
|
384
|
+
it('should merge user and workspace settings, with workspace taking precedence', async () => {
|
|
385
|
+
// User settings
|
|
386
|
+
const userEnvPath = path.join(extensionDir, EXTENSION_SETTINGS_FILENAME);
|
|
387
|
+
await fsPromises.writeFile(userEnvPath, 'VAR1=user-value1\nVAR3=user-value3');
|
|
388
|
+
const userKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext ${extensionId}`);
|
|
389
|
+
await userKeychain.setSecret('VAR2', 'user-secret2');
|
|
390
|
+
// Workspace settings
|
|
391
|
+
const workspaceEnvPath = path.join(tempWorkspaceDir, EXTENSION_SETTINGS_FILENAME);
|
|
392
|
+
await fsPromises.writeFile(workspaceEnvPath, 'VAR1=workspace-value1');
|
|
393
|
+
const workspaceKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext ${extensionId} ${tempWorkspaceDir}`);
|
|
394
|
+
await workspaceKeychain.setSecret('VAR2', 'workspace-secret2');
|
|
395
|
+
const contents = await getEnvContents(config, extensionId);
|
|
396
|
+
expect(contents).toEqual({
|
|
397
|
+
VAR1: 'workspace-value1',
|
|
398
|
+
VAR2: 'workspace-secret2',
|
|
399
|
+
VAR3: 'user-value3',
|
|
400
|
+
});
|
|
367
401
|
});
|
|
368
402
|
});
|
|
369
403
|
describe('updateSetting', () => {
|
|
@@ -377,64 +411,55 @@ describe('extensionSettings', () => {
|
|
|
377
411
|
};
|
|
378
412
|
const mockRequestSetting = vi.fn();
|
|
379
413
|
beforeEach(async () => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
await
|
|
384
|
-
keychainData['VAR2'] = 'value2';
|
|
414
|
+
const userEnvPath = path.join(extensionDir, '.env');
|
|
415
|
+
await fsPromises.writeFile(userEnvPath, 'VAR1=value1\n');
|
|
416
|
+
const userKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext 12345`);
|
|
417
|
+
await userKeychain.setSecret('VAR2', 'value2');
|
|
385
418
|
mockRequestSetting.mockClear();
|
|
386
419
|
});
|
|
387
|
-
it('should update a non-sensitive setting', async () => {
|
|
420
|
+
it('should update a non-sensitive setting in USER scope', async () => {
|
|
388
421
|
mockRequestSetting.mockResolvedValue('new-value1');
|
|
389
|
-
await updateSetting(config, '12345', 'VAR1', mockRequestSetting);
|
|
390
|
-
expect(mockRequestSetting).toHaveBeenCalledWith(config.settings[0]);
|
|
422
|
+
await updateSetting(config, '12345', 'VAR1', mockRequestSetting, ExtensionSettingScope.USER);
|
|
391
423
|
const expectedEnvPath = path.join(extensionDir, '.env');
|
|
392
424
|
const actualContent = await fsPromises.readFile(expectedEnvPath, 'utf-8');
|
|
393
425
|
expect(actualContent).toContain('VAR1=new-value1');
|
|
394
|
-
expect(mockKeychainStorage.setSecret).not.toHaveBeenCalled();
|
|
395
426
|
});
|
|
396
|
-
it('should update a non-sensitive setting
|
|
397
|
-
mockRequestSetting.mockResolvedValue('new-value
|
|
398
|
-
await updateSetting(config, '12345', '
|
|
399
|
-
|
|
400
|
-
const expectedEnvPath = path.join(extensionDir, '.env');
|
|
427
|
+
it('should update a non-sensitive setting in WORKSPACE scope', async () => {
|
|
428
|
+
mockRequestSetting.mockResolvedValue('new-workspace-value');
|
|
429
|
+
await updateSetting(config, '12345', 'VAR1', mockRequestSetting, ExtensionSettingScope.WORKSPACE);
|
|
430
|
+
const expectedEnvPath = path.join(tempWorkspaceDir, '.env');
|
|
401
431
|
const actualContent = await fsPromises.readFile(expectedEnvPath, 'utf-8');
|
|
402
|
-
expect(actualContent).toContain('VAR1=new-value
|
|
403
|
-
expect(mockKeychainStorage.setSecret).not.toHaveBeenCalled();
|
|
432
|
+
expect(actualContent).toContain('VAR1=new-workspace-value');
|
|
404
433
|
});
|
|
405
|
-
it('should update a sensitive setting', async () => {
|
|
434
|
+
it('should update a sensitive setting in USER scope', async () => {
|
|
406
435
|
mockRequestSetting.mockResolvedValue('new-value2');
|
|
407
|
-
await updateSetting(config, '12345', 'VAR2', mockRequestSetting);
|
|
408
|
-
|
|
409
|
-
expect(
|
|
410
|
-
const expectedEnvPath = path.join(extensionDir, '.env');
|
|
411
|
-
const actualContent = await fsPromises.readFile(expectedEnvPath, 'utf-8');
|
|
412
|
-
expect(actualContent).not.toContain('VAR2=new-value2');
|
|
436
|
+
await updateSetting(config, '12345', 'VAR2', mockRequestSetting, ExtensionSettingScope.USER);
|
|
437
|
+
const userKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext 12345`);
|
|
438
|
+
expect(await userKeychain.getSecret('VAR2')).toBe('new-value2');
|
|
413
439
|
});
|
|
414
|
-
it('should update a sensitive setting
|
|
415
|
-
mockRequestSetting.mockResolvedValue('new-
|
|
416
|
-
await updateSetting(config, '12345', '
|
|
417
|
-
|
|
418
|
-
expect(
|
|
419
|
-
});
|
|
420
|
-
it('should do nothing if the setting does not exist', async () => {
|
|
421
|
-
await updateSetting(config, '12345', 'VAR3', mockRequestSetting);
|
|
422
|
-
expect(mockRequestSetting).not.toHaveBeenCalled();
|
|
440
|
+
it('should update a sensitive setting in WORKSPACE scope', async () => {
|
|
441
|
+
mockRequestSetting.mockResolvedValue('new-workspace-secret');
|
|
442
|
+
await updateSetting(config, '12345', 'VAR2', mockRequestSetting, ExtensionSettingScope.WORKSPACE);
|
|
443
|
+
const workspaceKeychain = new KeychainTokenStorage(`Gemini CLI Extensions test-ext 12345 ${tempWorkspaceDir}`);
|
|
444
|
+
expect(await workspaceKeychain.getSecret('VAR2')).toBe('new-workspace-secret');
|
|
423
445
|
});
|
|
424
|
-
it('should
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
expect(actualContent).toContain('VAR1=
|
|
446
|
+
it('should leave existing, unmanaged .env variables intact when updating in WORKSPACE scope', async () => {
|
|
447
|
+
// Setup a pre-existing .env file in the workspace with unmanaged variables
|
|
448
|
+
const workspaceEnvPath = path.join(tempWorkspaceDir, '.env');
|
|
449
|
+
const originalEnvContent = 'PROJECT_VAR_1=value_1\nPROJECT_VAR_2=value_2\nVAR1=original-value'; // VAR1 is managed by extension
|
|
450
|
+
await fsPromises.writeFile(workspaceEnvPath, originalEnvContent);
|
|
451
|
+
// Simulate updating an extension-managed non-sensitive setting
|
|
452
|
+
mockRequestSetting.mockResolvedValue('updated-value');
|
|
453
|
+
await updateSetting(config, '12345', 'VAR1', mockRequestSetting, ExtensionSettingScope.WORKSPACE);
|
|
454
|
+
// Read the .env file after update
|
|
455
|
+
const actualContent = await fsPromises.readFile(workspaceEnvPath, 'utf-8');
|
|
456
|
+
// Assert that original variables are intact and extension variable is updated
|
|
457
|
+
expect(actualContent).toContain('PROJECT_VAR_1=value_1');
|
|
458
|
+
expect(actualContent).toContain('PROJECT_VAR_2=value_2');
|
|
459
|
+
expect(actualContent).toContain('VAR1=updated-value');
|
|
460
|
+
// Ensure no other unexpected changes or deletions
|
|
461
|
+
const lines = actualContent.split('\n').filter((line) => line.length > 0);
|
|
462
|
+
expect(lines).toHaveLength(3); // Should only have the three variables
|
|
438
463
|
});
|
|
439
464
|
});
|
|
440
465
|
});
|