@google/gemini-cli 0.12.0-nightly.20251023.c4c0c0d1 → 0.12.0-preview.0
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/README.md +7 -5
- package/dist/package.json +3 -3
- package/dist/src/commands/extensions/disable.d.ts +1 -1
- package/dist/src/commands/extensions/disable.js +5 -4
- package/dist/src/commands/extensions/disable.js.map +1 -1
- package/dist/src/commands/extensions/enable.d.ts +1 -1
- package/dist/src/commands/extensions/enable.js +3 -2
- package/dist/src/commands/extensions/enable.js.map +1 -1
- package/dist/src/commands/extensions/install.js +2 -1
- package/dist/src/commands/extensions/install.js.map +1 -1
- package/dist/src/commands/extensions/install.test.js +1 -0
- package/dist/src/commands/extensions/install.test.js.map +1 -1
- package/dist/src/commands/extensions/link.js +2 -1
- package/dist/src/commands/extensions/link.js.map +1 -1
- package/dist/src/commands/extensions/list.js +2 -2
- package/dist/src/commands/extensions/list.js.map +1 -1
- package/dist/src/commands/extensions/uninstall.js +2 -1
- package/dist/src/commands/extensions/uninstall.js.map +1 -1
- package/dist/src/commands/extensions/update.js +2 -2
- package/dist/src/commands/extensions/update.js.map +1 -1
- package/dist/src/commands/mcp/list.js +2 -2
- package/dist/src/commands/mcp/list.js.map +1 -1
- package/dist/src/config/config.d.ts +6 -3
- package/dist/src/config/config.js +56 -11
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +208 -175
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension-manager.d.ts +23 -10
- package/dist/src/config/extension-manager.js +90 -64
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension.test.js +183 -76
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
- package/dist/src/config/extensions/extensionEnablement.js +3 -2
- package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
- package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
- package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.d.ts +3 -3
- package/dist/src/config/extensions/extensionSettings.js +74 -24
- package/dist/src/config/extensions/extensionSettings.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.test.js +145 -24
- package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
- package/dist/src/config/extensions/github.js +3 -3
- package/dist/src/config/extensions/github.js.map +1 -1
- package/dist/src/config/extensions/github.test.js +1 -1
- package/dist/src/config/extensions/github.test.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.d.ts +1 -1
- package/dist/src/config/extensions/github_fetch.js +13 -1
- package/dist/src/config/extensions/github_fetch.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
- package/dist/src/config/extensions/github_fetch.test.js +169 -0
- package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
- package/dist/src/config/extensions/update.js +7 -6
- package/dist/src/config/extensions/update.js.map +1 -1
- package/dist/src/config/extensions/update.test.js +54 -30
- package/dist/src/config/extensions/update.test.js.map +1 -1
- package/dist/src/config/keyBindings.js +1 -1
- package/dist/src/config/keyBindings.js.map +1 -1
- package/dist/src/config/policies/read-only.toml +56 -0
- package/dist/src/config/policies/write.toml +63 -0
- package/dist/src/config/policies/yolo.toml +31 -0
- package/dist/src/config/policy-engine.integration.test.js +41 -38
- package/dist/src/config/policy-engine.integration.test.js.map +1 -1
- package/dist/src/config/policy-toml-loader.d.ts +46 -0
- package/dist/src/config/policy-toml-loader.js +314 -0
- package/dist/src/config/policy-toml-loader.js.map +1 -0
- package/dist/src/config/policy-toml-loader.test.d.ts +6 -0
- package/dist/src/config/policy-toml-loader.test.js +626 -0
- package/dist/src/config/policy-toml-loader.test.js.map +1 -0
- package/dist/src/config/policy.d.ts +9 -2
- package/dist/src/config/policy.js +139 -110
- package/dist/src/config/policy.js.map +1 -1
- package/dist/src/config/policy.test.js +780 -82
- package/dist/src/config/policy.test.js.map +1 -1
- package/dist/src/config/settings.test.js +6 -6
- package/dist/src/config/settings.test.js.map +1 -1
- package/dist/src/core/initializer.js +2 -1
- package/dist/src/core/initializer.js.map +1 -1
- package/dist/src/gemini.js +6 -17
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.js +27 -2
- package/dist/src/gemini.test.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/generated/git-commit.js.map +1 -1
- package/dist/src/nonInteractiveCli.js +16 -4
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/nonInteractiveCli.test.js +67 -12
- package/dist/src/nonInteractiveCli.test.js.map +1 -1
- package/dist/src/test-utils/render.d.ts +12 -0
- package/dist/src/test-utils/render.js +28 -1
- package/dist/src/test-utils/render.js.map +1 -1
- package/dist/src/test-utils/render.test.d.ts +6 -0
- package/dist/src/test-utils/render.test.js +54 -0
- package/dist/src/test-utils/render.test.js.map +1 -0
- package/dist/src/ui/AppContainer.js +29 -23
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +8 -0
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/commands/directoryCommand.js +1 -1
- package/dist/src/ui/commands/directoryCommand.js.map +1 -1
- package/dist/src/ui/commands/extensionsCommand.js +45 -1
- package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
- package/dist/src/ui/commands/extensionsCommand.test.js +64 -1
- package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
- package/dist/src/ui/commands/memoryCommand.js +1 -1
- package/dist/src/ui/commands/memoryCommand.js.map +1 -1
- package/dist/src/ui/commands/memoryCommand.test.js +3 -1
- package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
- package/dist/src/ui/components/ConsoleSummaryDisplay.js +1 -1
- package/dist/src/ui/components/ConsoleSummaryDisplay.js.map +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
- package/dist/src/ui/components/FolderTrustDialog.test.js +4 -4
- package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/Footer.js +1 -1
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/Footer.test.js +24 -0
- package/dist/src/ui/components/Footer.test.js.map +1 -1
- package/dist/src/ui/components/Help.test.js +0 -1
- package/dist/src/ui/components/Help.test.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +442 -342
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.test.js +5 -5
- package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +11 -12
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.test.js +13 -14
- package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
- package/dist/src/ui/components/ThemeDialog.test.js +1 -2
- package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
- package/dist/src/ui/components/shared/BaseSelectionList.test.js +11 -12
- package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.test.js +2 -1
- package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
- package/dist/src/ui/components/views/ExtensionsList.d.ts +1 -1
- package/dist/src/ui/components/views/ExtensionsList.js +4 -1
- package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
- package/dist/src/ui/contexts/KeypressContext.js +114 -64
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +166 -482
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/SessionContext.test.js +27 -13
- package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
- package/dist/src/ui/hooks/atCommandProcessor.js +2 -2
- package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.test.js +18 -2
- package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.test.js +74 -80
- package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/useAtCompletion.test.js +32 -23
- package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +2 -1
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useCommandCompletion.test.js +79 -78
- package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
- package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
- package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
- package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.d.ts +1 -2
- package/dist/src/ui/hooks/useExtensionUpdates.js +4 -2
- package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.test.js +37 -26
- package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
- package/dist/src/ui/hooks/useFlickerDetector.test.js +9 -5
- package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
- package/dist/src/ui/hooks/useFocus.test.js +25 -9
- package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.test.js +45 -22
- package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +56 -19
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +155 -74
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.test.js +29 -16
- package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
- package/dist/src/ui/hooks/useHistoryManager.test.js +2 -1
- package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
- package/dist/src/ui/hooks/useIdeTrustListener.test.js +24 -7
- package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistory.test.js +2 -1
- package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js +2 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.test.js +94 -113
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
- package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
- package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
- package/dist/src/ui/hooks/useMessageQueue.test.js +61 -45
- package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
- package/dist/src/ui/hooks/useModelCommand.test.js +18 -11
- package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +2 -2
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.js +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.test.js +83 -110
- package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
- package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -10
- package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
- package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js +33 -40
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js +37 -26
- package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.test.js +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +2 -2
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useSelectionList.test.js +193 -132
- package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
- package/dist/src/ui/hooks/useShellHistory.test.js +40 -16
- package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useSlashCompletion.test.js +54 -49
- package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useTimer.test.js +43 -14
- package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +163 -74
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/vim.test.js +251 -356
- package/dist/src/ui/hooks/vim.test.js.map +1 -1
- package/dist/src/ui/keyMatchers.test.js +3 -3
- package/dist/src/ui/keyMatchers.test.js.map +1 -1
- package/dist/src/ui/utils/textOutput.d.ts +25 -0
- package/dist/src/ui/utils/textOutput.js +49 -0
- package/dist/src/ui/utils/textOutput.js.map +1 -0
- package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
- package/dist/src/ui/utils/textOutput.test.js +79 -0
- package/dist/src/ui/utils/textOutput.test.js.map +1 -0
- package/dist/src/ui/utils/updateCheck.d.ts +7 -1
- package/dist/src/ui/utils/updateCheck.js +27 -26
- package/dist/src/ui/utils/updateCheck.js.map +1 -1
- package/dist/src/ui/utils/updateCheck.test.js +19 -49
- package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.js +9 -3
- package/dist/src/utils/handleAutoUpdate.js.map +1 -1
- package/dist/src/zed-integration/zedIntegration.d.ts +2 -2
- package/dist/src/zed-integration/zedIntegration.js +9 -16
- package/dist/src/zed-integration/zedIntegration.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
|
@@ -7,7 +7,7 @@ import { vi } from 'vitest';
|
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import * as os from 'node:os';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
|
-
import { ExtensionUninstallEvent, ExtensionDisableEvent, ExtensionEnableEvent, } from '@google/gemini-cli-core';
|
|
10
|
+
import { ExtensionUninstallEvent, ExtensionDisableEvent, ExtensionEnableEvent, KeychainTokenStorage, } from '@google/gemini-cli-core';
|
|
11
11
|
import { loadSettings, SettingScope } from './settings.js';
|
|
12
12
|
import { isWorkspaceTrusted } from './trustedFolders.js';
|
|
13
13
|
import { createExtension } from '../test-utils/createExtension.js';
|
|
@@ -42,11 +42,12 @@ vi.mock('simple-git', () => ({
|
|
|
42
42
|
return mockGit;
|
|
43
43
|
}),
|
|
44
44
|
}));
|
|
45
|
+
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
|
45
46
|
vi.mock('os', async (importOriginal) => {
|
|
46
47
|
const mockedOs = await importOriginal();
|
|
47
48
|
return {
|
|
48
49
|
...mockedOs,
|
|
49
|
-
homedir:
|
|
50
|
+
homedir: mockHomedir,
|
|
50
51
|
};
|
|
51
52
|
});
|
|
52
53
|
vi.mock('./trustedFolders.js', async (importOriginal) => {
|
|
@@ -74,6 +75,13 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|
|
74
75
|
ExtensionInstallEvent: vi.fn(),
|
|
75
76
|
ExtensionUninstallEvent: vi.fn(),
|
|
76
77
|
ExtensionDisableEvent: vi.fn(),
|
|
78
|
+
KeychainTokenStorage: vi.fn().mockImplementation(() => ({
|
|
79
|
+
getSecret: vi.fn(),
|
|
80
|
+
setSecret: vi.fn(),
|
|
81
|
+
deleteSecret: vi.fn(),
|
|
82
|
+
listSecrets: vi.fn(),
|
|
83
|
+
isAvailable: vi.fn().mockResolvedValue(true),
|
|
84
|
+
})),
|
|
77
85
|
};
|
|
78
86
|
});
|
|
79
87
|
vi.mock('child_process', async (importOriginal) => {
|
|
@@ -90,7 +98,29 @@ describe('extension tests', () => {
|
|
|
90
98
|
let extensionManager;
|
|
91
99
|
let mockRequestConsent;
|
|
92
100
|
let mockPromptForSettings;
|
|
101
|
+
let mockKeychainStorage;
|
|
102
|
+
let keychainData;
|
|
93
103
|
beforeEach(() => {
|
|
104
|
+
vi.clearAllMocks();
|
|
105
|
+
keychainData = {};
|
|
106
|
+
mockKeychainStorage = {
|
|
107
|
+
getSecret: vi
|
|
108
|
+
.fn()
|
|
109
|
+
.mockImplementation(async (key) => keychainData[key] || null),
|
|
110
|
+
setSecret: vi
|
|
111
|
+
.fn()
|
|
112
|
+
.mockImplementation(async (key, value) => {
|
|
113
|
+
keychainData[key] = value;
|
|
114
|
+
}),
|
|
115
|
+
deleteSecret: vi.fn().mockImplementation(async (key) => {
|
|
116
|
+
delete keychainData[key];
|
|
117
|
+
}),
|
|
118
|
+
listSecrets: vi
|
|
119
|
+
.fn()
|
|
120
|
+
.mockImplementation(async () => Object.keys(keychainData)),
|
|
121
|
+
isAvailable: vi.fn().mockResolvedValue(true),
|
|
122
|
+
};
|
|
123
|
+
KeychainTokenStorage.mockImplementation(() => mockKeychainStorage);
|
|
94
124
|
tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-test-home-'));
|
|
95
125
|
tempWorkspaceDir = fs.mkdtempSync(path.join(tempHomeDir, 'gemini-cli-test-workspace-'));
|
|
96
126
|
userExtensionsDir = path.join(tempHomeDir, EXTENSIONS_DIRECTORY_NAME);
|
|
@@ -109,7 +139,7 @@ describe('extension tests', () => {
|
|
|
109
139
|
workspaceDir: tempWorkspaceDir,
|
|
110
140
|
requestConsent: mockRequestConsent,
|
|
111
141
|
requestSetting: mockPromptForSettings,
|
|
112
|
-
|
|
142
|
+
settings: loadSettings(tempWorkspaceDir).merged,
|
|
113
143
|
});
|
|
114
144
|
});
|
|
115
145
|
afterEach(() => {
|
|
@@ -118,7 +148,7 @@ describe('extension tests', () => {
|
|
|
118
148
|
vi.restoreAllMocks();
|
|
119
149
|
});
|
|
120
150
|
describe('loadExtensions', () => {
|
|
121
|
-
it('should include extension path in loaded extension', () => {
|
|
151
|
+
it('should include extension path in loaded extension', async () => {
|
|
122
152
|
const extensionDir = path.join(userExtensionsDir, 'test-extension');
|
|
123
153
|
fs.mkdirSync(extensionDir, { recursive: true });
|
|
124
154
|
createExtension({
|
|
@@ -126,12 +156,12 @@ describe('extension tests', () => {
|
|
|
126
156
|
name: 'test-extension',
|
|
127
157
|
version: '1.0.0',
|
|
128
158
|
});
|
|
129
|
-
const extensions = extensionManager.loadExtensions();
|
|
159
|
+
const extensions = await extensionManager.loadExtensions();
|
|
130
160
|
expect(extensions).toHaveLength(1);
|
|
131
161
|
expect(extensions[0].path).toBe(extensionDir);
|
|
132
162
|
expect(extensions[0].name).toBe('test-extension');
|
|
133
163
|
});
|
|
134
|
-
it('should load context file path when GEMINI.md is present', () => {
|
|
164
|
+
it('should load context file path when GEMINI.md is present', async () => {
|
|
135
165
|
createExtension({
|
|
136
166
|
extensionsDir: userExtensionsDir,
|
|
137
167
|
name: 'ext1',
|
|
@@ -143,7 +173,7 @@ describe('extension tests', () => {
|
|
|
143
173
|
name: 'ext2',
|
|
144
174
|
version: '2.0.0',
|
|
145
175
|
});
|
|
146
|
-
const extensions = extensionManager.loadExtensions();
|
|
176
|
+
const extensions = await extensionManager.loadExtensions();
|
|
147
177
|
expect(extensions).toHaveLength(2);
|
|
148
178
|
const ext1 = extensions.find((e) => e.name === 'ext1');
|
|
149
179
|
const ext2 = extensions.find((e) => e.name === 'ext2');
|
|
@@ -152,7 +182,7 @@ describe('extension tests', () => {
|
|
|
152
182
|
]);
|
|
153
183
|
expect(ext2?.contextFiles).toEqual([]);
|
|
154
184
|
});
|
|
155
|
-
it('should load context file path from the extension config', () => {
|
|
185
|
+
it('should load context file path from the extension config', async () => {
|
|
156
186
|
createExtension({
|
|
157
187
|
extensionsDir: userExtensionsDir,
|
|
158
188
|
name: 'ext1',
|
|
@@ -160,14 +190,14 @@ describe('extension tests', () => {
|
|
|
160
190
|
addContextFile: false,
|
|
161
191
|
contextFileName: 'my-context-file.md',
|
|
162
192
|
});
|
|
163
|
-
const extensions = extensionManager.loadExtensions();
|
|
193
|
+
const extensions = await extensionManager.loadExtensions();
|
|
164
194
|
expect(extensions).toHaveLength(1);
|
|
165
195
|
const ext1 = extensions.find((e) => e.name === 'ext1');
|
|
166
196
|
expect(ext1?.contextFiles).toEqual([
|
|
167
197
|
path.join(userExtensionsDir, 'ext1', 'my-context-file.md'),
|
|
168
198
|
]);
|
|
169
199
|
});
|
|
170
|
-
it('should annotate disabled extensions', () => {
|
|
200
|
+
it('should annotate disabled extensions', async () => {
|
|
171
201
|
createExtension({
|
|
172
202
|
extensionsDir: userExtensionsDir,
|
|
173
203
|
name: 'disabled-extension',
|
|
@@ -178,15 +208,16 @@ describe('extension tests', () => {
|
|
|
178
208
|
name: 'enabled-extension',
|
|
179
209
|
version: '2.0.0',
|
|
180
210
|
});
|
|
181
|
-
extensionManager.
|
|
182
|
-
|
|
211
|
+
await extensionManager.loadExtensions();
|
|
212
|
+
await extensionManager.disableExtension('disabled-extension', SettingScope.User);
|
|
213
|
+
const extensions = extensionManager.getExtensions();
|
|
183
214
|
expect(extensions).toHaveLength(2);
|
|
184
215
|
expect(extensions[0].name).toBe('disabled-extension');
|
|
185
216
|
expect(extensions[0].isActive).toBe(false);
|
|
186
217
|
expect(extensions[1].name).toBe('enabled-extension');
|
|
187
218
|
expect(extensions[1].isActive).toBe(true);
|
|
188
219
|
});
|
|
189
|
-
it('should hydrate variables', () => {
|
|
220
|
+
it('should hydrate variables', async () => {
|
|
190
221
|
createExtension({
|
|
191
222
|
extensionsDir: userExtensionsDir,
|
|
192
223
|
name: 'test-extension',
|
|
@@ -199,7 +230,7 @@ describe('extension tests', () => {
|
|
|
199
230
|
},
|
|
200
231
|
},
|
|
201
232
|
});
|
|
202
|
-
const extensions = extensionManager.loadExtensions();
|
|
233
|
+
const extensions = await extensionManager.loadExtensions();
|
|
203
234
|
expect(extensions).toHaveLength(1);
|
|
204
235
|
const expectedCwd = path.join(userExtensionsDir, 'test-extension', 'server');
|
|
205
236
|
expect(extensions[0].mcpServers?.['test-server'].cwd).toBe(expectedCwd);
|
|
@@ -212,12 +243,13 @@ describe('extension tests', () => {
|
|
|
212
243
|
contextFileName: 'context.md',
|
|
213
244
|
});
|
|
214
245
|
fs.writeFileSync(path.join(sourceExtDir, 'context.md'), 'linked context');
|
|
215
|
-
|
|
246
|
+
await extensionManager.loadExtensions();
|
|
247
|
+
const extension = await extensionManager.installOrUpdateExtension({
|
|
216
248
|
source: sourceExtDir,
|
|
217
249
|
type: 'link',
|
|
218
250
|
});
|
|
219
|
-
expect(
|
|
220
|
-
const extensions = extensionManager.
|
|
251
|
+
expect(extension.name).toEqual('my-linked-extension');
|
|
252
|
+
const extensions = extensionManager.getExtensions();
|
|
221
253
|
expect(extensions).toHaveLength(1);
|
|
222
254
|
const linkedExt = extensions[0];
|
|
223
255
|
expect(linkedExt.name).toBe('my-linked-extension');
|
|
@@ -230,7 +262,32 @@ describe('extension tests', () => {
|
|
|
230
262
|
path.join(sourceExtDir, 'context.md'),
|
|
231
263
|
]);
|
|
232
264
|
});
|
|
233
|
-
it('should
|
|
265
|
+
it('should hydrate ${extensionPath} correctly for linked extensions', async () => {
|
|
266
|
+
const sourceExtDir = createExtension({
|
|
267
|
+
extensionsDir: tempWorkspaceDir,
|
|
268
|
+
name: 'my-linked-extension-with-path',
|
|
269
|
+
version: '1.0.0',
|
|
270
|
+
mcpServers: {
|
|
271
|
+
'test-server': {
|
|
272
|
+
command: 'node',
|
|
273
|
+
args: ['${extensionPath}${/}server${/}index.js'],
|
|
274
|
+
cwd: '${extensionPath}${/}server',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
await extensionManager.loadExtensions();
|
|
279
|
+
await extensionManager.installOrUpdateExtension({
|
|
280
|
+
source: sourceExtDir,
|
|
281
|
+
type: 'link',
|
|
282
|
+
});
|
|
283
|
+
const extensions = extensionManager.getExtensions();
|
|
284
|
+
expect(extensions).toHaveLength(1);
|
|
285
|
+
expect(extensions[0].mcpServers?.['test-server'].cwd).toBe(path.join(sourceExtDir, 'server'));
|
|
286
|
+
expect(extensions[0].mcpServers?.['test-server'].args).toEqual([
|
|
287
|
+
path.join(sourceExtDir, 'server', 'index.js'),
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
it('should resolve environment variables in extension configuration', async () => {
|
|
234
291
|
process.env['TEST_API_KEY'] = 'test-api-key-123';
|
|
235
292
|
process.env['TEST_DB_URL'] = 'postgresql://localhost:5432/testdb';
|
|
236
293
|
try {
|
|
@@ -256,7 +313,7 @@ describe('extension tests', () => {
|
|
|
256
313
|
},
|
|
257
314
|
};
|
|
258
315
|
fs.writeFileSync(configPath, JSON.stringify(extensionConfig));
|
|
259
|
-
const extensions = extensionManager.loadExtensions();
|
|
316
|
+
const extensions = await extensionManager.loadExtensions();
|
|
260
317
|
expect(extensions).toHaveLength(1);
|
|
261
318
|
const extension = extensions[0];
|
|
262
319
|
expect(extension.name).toBe('test-extension');
|
|
@@ -273,7 +330,7 @@ describe('extension tests', () => {
|
|
|
273
330
|
delete process.env['TEST_DB_URL'];
|
|
274
331
|
}
|
|
275
332
|
});
|
|
276
|
-
it('should resolve environment variables from an extension .env file', () => {
|
|
333
|
+
it('should resolve environment variables from an extension .env file', async () => {
|
|
277
334
|
const extDir = createExtension({
|
|
278
335
|
extensionsDir: userExtensionsDir,
|
|
279
336
|
name: 'test-extension',
|
|
@@ -288,10 +345,17 @@ describe('extension tests', () => {
|
|
|
288
345
|
},
|
|
289
346
|
},
|
|
290
347
|
},
|
|
348
|
+
settings: [
|
|
349
|
+
{
|
|
350
|
+
name: 'My API Key',
|
|
351
|
+
description: 'API key for testing.',
|
|
352
|
+
envVar: 'MY_API_KEY',
|
|
353
|
+
},
|
|
354
|
+
],
|
|
291
355
|
});
|
|
292
356
|
const envFilePath = path.join(extDir, '.env');
|
|
293
357
|
fs.writeFileSync(envFilePath, 'MY_API_KEY=test-key-from-file\n');
|
|
294
|
-
const extensions = extensionManager.loadExtensions();
|
|
358
|
+
const extensions = await extensionManager.loadExtensions();
|
|
295
359
|
expect(extensions).toHaveLength(1);
|
|
296
360
|
const extension = extensions[0];
|
|
297
361
|
const serverConfig = extension.mcpServers['test-server'];
|
|
@@ -299,7 +363,7 @@ describe('extension tests', () => {
|
|
|
299
363
|
expect(serverConfig.env['API_KEY']).toBe('test-key-from-file');
|
|
300
364
|
expect(serverConfig.env['STATIC_VALUE']).toBe('no-substitution');
|
|
301
365
|
});
|
|
302
|
-
it('should handle missing environment variables gracefully', () => {
|
|
366
|
+
it('should handle missing environment variables gracefully', async () => {
|
|
303
367
|
const userExtensionsDir = path.join(tempHomeDir, EXTENSIONS_DIRECTORY_NAME);
|
|
304
368
|
fs.mkdirSync(userExtensionsDir, { recursive: true });
|
|
305
369
|
const extDir = path.join(userExtensionsDir, 'test-extension');
|
|
@@ -319,7 +383,7 @@ describe('extension tests', () => {
|
|
|
319
383
|
},
|
|
320
384
|
};
|
|
321
385
|
fs.writeFileSync(path.join(extDir, EXTENSIONS_CONFIG_FILENAME), JSON.stringify(extensionConfig));
|
|
322
|
-
const extensions = extensionManager.loadExtensions();
|
|
386
|
+
const extensions = await extensionManager.loadExtensions();
|
|
323
387
|
expect(extensions).toHaveLength(1);
|
|
324
388
|
const extension = extensions[0];
|
|
325
389
|
const serverConfig = extension.mcpServers['test-server'];
|
|
@@ -327,7 +391,7 @@ describe('extension tests', () => {
|
|
|
327
391
|
expect(serverConfig.env['MISSING_VAR']).toBe('$UNDEFINED_ENV_VAR');
|
|
328
392
|
expect(serverConfig.env['MISSING_VAR_BRACES']).toBe('${ALSO_UNDEFINED}');
|
|
329
393
|
});
|
|
330
|
-
it('should skip extensions with invalid JSON and log a warning', () => {
|
|
394
|
+
it('should skip extensions with invalid JSON and log a warning', async () => {
|
|
331
395
|
const consoleSpy = vi
|
|
332
396
|
.spyOn(console, 'error')
|
|
333
397
|
.mockImplementation(() => { });
|
|
@@ -342,14 +406,13 @@ describe('extension tests', () => {
|
|
|
342
406
|
fs.mkdirSync(badExtDir);
|
|
343
407
|
const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
|
|
344
408
|
fs.writeFileSync(badConfigPath, '{ "name": "bad-ext"'); // Malformed
|
|
345
|
-
const extensions = extensionManager.loadExtensions();
|
|
409
|
+
const extensions = await extensionManager.loadExtensions();
|
|
346
410
|
expect(extensions).toHaveLength(1);
|
|
347
411
|
expect(extensions[0].name).toBe('good-ext');
|
|
348
|
-
expect(consoleSpy).
|
|
349
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}`));
|
|
412
|
+
expect(consoleSpy).toHaveBeenCalledExactlyOnceWith(expect.stringContaining(`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}`));
|
|
350
413
|
consoleSpy.mockRestore();
|
|
351
414
|
});
|
|
352
|
-
it('should skip extensions with missing name and log a warning', () => {
|
|
415
|
+
it('should skip extensions with missing name and log a warning', async () => {
|
|
353
416
|
const consoleSpy = vi
|
|
354
417
|
.spyOn(console, 'error')
|
|
355
418
|
.mockImplementation(() => { });
|
|
@@ -364,14 +427,13 @@ describe('extension tests', () => {
|
|
|
364
427
|
fs.mkdirSync(badExtDir);
|
|
365
428
|
const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
|
|
366
429
|
fs.writeFileSync(badConfigPath, JSON.stringify({ version: '1.0.0' }));
|
|
367
|
-
const extensions = extensionManager.loadExtensions();
|
|
430
|
+
const extensions = await extensionManager.loadExtensions();
|
|
368
431
|
expect(extensions).toHaveLength(1);
|
|
369
432
|
expect(extensions[0].name).toBe('good-ext');
|
|
370
|
-
expect(consoleSpy).
|
|
371
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}: Invalid configuration in ${badConfigPath}: missing "name"`));
|
|
433
|
+
expect(consoleSpy).toHaveBeenCalledExactlyOnceWith(expect.stringContaining(`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}: Invalid configuration in ${badConfigPath}: missing "name"`));
|
|
372
434
|
consoleSpy.mockRestore();
|
|
373
435
|
});
|
|
374
|
-
it('should filter trust out of mcp servers', () => {
|
|
436
|
+
it('should filter trust out of mcp servers', async () => {
|
|
375
437
|
createExtension({
|
|
376
438
|
extensionsDir: userExtensionsDir,
|
|
377
439
|
name: 'test-extension',
|
|
@@ -384,27 +446,28 @@ describe('extension tests', () => {
|
|
|
384
446
|
},
|
|
385
447
|
},
|
|
386
448
|
});
|
|
387
|
-
const extensions = extensionManager.loadExtensions();
|
|
449
|
+
const extensions = await extensionManager.loadExtensions();
|
|
388
450
|
expect(extensions).toHaveLength(1);
|
|
389
451
|
expect(extensions[0].mcpServers?.['test-server'].trust).toBeUndefined();
|
|
390
452
|
});
|
|
391
|
-
it('should throw an error for invalid extension names', () => {
|
|
453
|
+
it('should throw an error for invalid extension names', async () => {
|
|
392
454
|
const consoleSpy = vi
|
|
393
455
|
.spyOn(console, 'error')
|
|
394
456
|
.mockImplementation(() => { });
|
|
395
|
-
|
|
457
|
+
createExtension({
|
|
396
458
|
extensionsDir: userExtensionsDir,
|
|
397
459
|
name: 'bad_name',
|
|
398
460
|
version: '1.0.0',
|
|
399
461
|
});
|
|
400
|
-
const
|
|
401
|
-
|
|
462
|
+
const extensions = await extensionManager.loadExtensions();
|
|
463
|
+
const extension = extensions.find((e) => e.name === 'bad_name');
|
|
464
|
+
expect(extension).toBeUndefined();
|
|
402
465
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid extension name: "bad_name"'));
|
|
403
466
|
consoleSpy.mockRestore();
|
|
404
467
|
});
|
|
405
468
|
describe('id generation', () => {
|
|
406
|
-
it('should generate id from source for non-github git urls', () => {
|
|
407
|
-
|
|
469
|
+
it('should generate id from source for non-github git urls', async () => {
|
|
470
|
+
createExtension({
|
|
408
471
|
extensionsDir: userExtensionsDir,
|
|
409
472
|
name: 'my-ext',
|
|
410
473
|
version: '1.0.0',
|
|
@@ -413,11 +476,12 @@ describe('extension tests', () => {
|
|
|
413
476
|
source: 'http://somehost.com/foo/bar',
|
|
414
477
|
},
|
|
415
478
|
});
|
|
416
|
-
const
|
|
479
|
+
const extensions = await extensionManager.loadExtensions();
|
|
480
|
+
const extension = extensions.find((e) => e.name === 'my-ext');
|
|
417
481
|
expect(extension?.id).toBe(hashValue('http://somehost.com/foo/bar'));
|
|
418
482
|
});
|
|
419
|
-
it('should generate id from owner/repo for github http urls', () => {
|
|
420
|
-
|
|
483
|
+
it('should generate id from owner/repo for github http urls', async () => {
|
|
484
|
+
createExtension({
|
|
421
485
|
extensionsDir: userExtensionsDir,
|
|
422
486
|
name: 'my-ext',
|
|
423
487
|
version: '1.0.0',
|
|
@@ -426,11 +490,12 @@ describe('extension tests', () => {
|
|
|
426
490
|
source: 'http://github.com/foo/bar',
|
|
427
491
|
},
|
|
428
492
|
});
|
|
429
|
-
const
|
|
493
|
+
const extensions = await extensionManager.loadExtensions();
|
|
494
|
+
const extension = extensions.find((e) => e.name === 'my-ext');
|
|
430
495
|
expect(extension?.id).toBe(hashValue('https://github.com/foo/bar'));
|
|
431
496
|
});
|
|
432
|
-
it('should generate id from owner/repo for github ssh urls', () => {
|
|
433
|
-
|
|
497
|
+
it('should generate id from owner/repo for github ssh urls', async () => {
|
|
498
|
+
createExtension({
|
|
434
499
|
extensionsDir: userExtensionsDir,
|
|
435
500
|
name: 'my-ext',
|
|
436
501
|
version: '1.0.0',
|
|
@@ -439,11 +504,12 @@ describe('extension tests', () => {
|
|
|
439
504
|
source: 'git@github.com:foo/bar',
|
|
440
505
|
},
|
|
441
506
|
});
|
|
442
|
-
const
|
|
507
|
+
const extensions = await extensionManager.loadExtensions();
|
|
508
|
+
const extension = extensions.find((e) => e.name === 'my-ext');
|
|
443
509
|
expect(extension?.id).toBe(hashValue('https://github.com/foo/bar'));
|
|
444
510
|
});
|
|
445
|
-
it('should generate id from source for github-release extension', () => {
|
|
446
|
-
|
|
511
|
+
it('should generate id from source for github-release extension', async () => {
|
|
512
|
+
createExtension({
|
|
447
513
|
extensionsDir: userExtensionsDir,
|
|
448
514
|
name: 'my-ext',
|
|
449
515
|
version: '1.0.0',
|
|
@@ -452,11 +518,12 @@ describe('extension tests', () => {
|
|
|
452
518
|
source: 'https://github.com/foo/bar',
|
|
453
519
|
},
|
|
454
520
|
});
|
|
455
|
-
const
|
|
521
|
+
const extensions = await extensionManager.loadExtensions();
|
|
522
|
+
const extension = extensions.find((e) => e.name === 'my-ext');
|
|
456
523
|
expect(extension?.id).toBe(hashValue('https://github.com/foo/bar'));
|
|
457
524
|
});
|
|
458
|
-
it('should generate id from the original source for local extension', () => {
|
|
459
|
-
|
|
525
|
+
it('should generate id from the original source for local extension', async () => {
|
|
526
|
+
createExtension({
|
|
460
527
|
extensionsDir: userExtensionsDir,
|
|
461
528
|
name: 'local-ext-name',
|
|
462
529
|
version: '1.0.0',
|
|
@@ -465,7 +532,8 @@ describe('extension tests', () => {
|
|
|
465
532
|
source: '/some/path',
|
|
466
533
|
},
|
|
467
534
|
});
|
|
468
|
-
const
|
|
535
|
+
const extensions = await extensionManager.loadExtensions();
|
|
536
|
+
const extension = extensions.find((e) => e.name === 'local-ext-name');
|
|
469
537
|
expect(extension?.id).toBe(hashValue('/some/path'));
|
|
470
538
|
});
|
|
471
539
|
it('should generate id from the original source for linked extensions', async () => {
|
|
@@ -475,20 +543,24 @@ describe('extension tests', () => {
|
|
|
475
543
|
name: 'link-ext-name',
|
|
476
544
|
version: '1.0.0',
|
|
477
545
|
});
|
|
478
|
-
|
|
546
|
+
await extensionManager.loadExtensions();
|
|
547
|
+
await extensionManager.installOrUpdateExtension({
|
|
479
548
|
type: 'link',
|
|
480
549
|
source: actualExtensionDir,
|
|
481
550
|
});
|
|
482
|
-
const extension = extensionManager
|
|
551
|
+
const extension = extensionManager
|
|
552
|
+
.getExtensions()
|
|
553
|
+
.find((e) => e.name === 'link-ext-name');
|
|
483
554
|
expect(extension?.id).toBe(hashValue(actualExtensionDir));
|
|
484
555
|
});
|
|
485
|
-
it('should generate id from name for extension with no install metadata', () => {
|
|
486
|
-
|
|
556
|
+
it('should generate id from name for extension with no install metadata', async () => {
|
|
557
|
+
createExtension({
|
|
487
558
|
extensionsDir: userExtensionsDir,
|
|
488
559
|
name: 'no-meta-name',
|
|
489
560
|
version: '1.0.0',
|
|
490
561
|
});
|
|
491
|
-
const
|
|
562
|
+
const extensions = await extensionManager.loadExtensions();
|
|
563
|
+
const extension = extensions.find((e) => e.name === 'no-meta-name');
|
|
492
564
|
expect(extension?.id).toBe(hashValue('no-meta-name'));
|
|
493
565
|
});
|
|
494
566
|
});
|
|
@@ -502,6 +574,7 @@ describe('extension tests', () => {
|
|
|
502
574
|
});
|
|
503
575
|
const targetExtDir = path.join(userExtensionsDir, 'my-local-extension');
|
|
504
576
|
const metadataPath = path.join(targetExtDir, INSTALL_METADATA_FILENAME);
|
|
577
|
+
await extensionManager.loadExtensions();
|
|
505
578
|
await extensionManager.installOrUpdateExtension({
|
|
506
579
|
source: sourceExtDir,
|
|
507
580
|
type: 'local',
|
|
@@ -521,6 +594,7 @@ describe('extension tests', () => {
|
|
|
521
594
|
name: 'my-local-extension',
|
|
522
595
|
version: '1.0.0',
|
|
523
596
|
});
|
|
597
|
+
await extensionManager.loadExtensions();
|
|
524
598
|
await extensionManager.installOrUpdateExtension({
|
|
525
599
|
source: sourceExtDir,
|
|
526
600
|
type: 'local',
|
|
@@ -582,6 +656,7 @@ describe('extension tests', () => {
|
|
|
582
656
|
failureReason: 'no release data',
|
|
583
657
|
type: 'github-release',
|
|
584
658
|
});
|
|
659
|
+
await extensionManager.loadExtensions();
|
|
585
660
|
await extensionManager.installOrUpdateExtension({
|
|
586
661
|
source: gitUrl,
|
|
587
662
|
type: 'git',
|
|
@@ -603,6 +678,7 @@ describe('extension tests', () => {
|
|
|
603
678
|
const targetExtDir = path.join(userExtensionsDir, 'my-linked-extension');
|
|
604
679
|
const metadataPath = path.join(targetExtDir, INSTALL_METADATA_FILENAME);
|
|
605
680
|
const configPath = path.join(targetExtDir, EXTENSIONS_CONFIG_FILENAME);
|
|
681
|
+
await extensionManager.loadExtensions();
|
|
606
682
|
await extensionManager.installOrUpdateExtension({
|
|
607
683
|
source: sourceExtDir,
|
|
608
684
|
type: 'link',
|
|
@@ -625,6 +701,7 @@ describe('extension tests', () => {
|
|
|
625
701
|
name: 'my-local-extension',
|
|
626
702
|
version: '1.1.0',
|
|
627
703
|
});
|
|
704
|
+
await extensionManager.loadExtensions();
|
|
628
705
|
if (isUpdate) {
|
|
629
706
|
await extensionManager.installOrUpdateExtension({
|
|
630
707
|
source: sourceExtDir,
|
|
@@ -686,10 +763,13 @@ describe('extension tests', () => {
|
|
|
686
763
|
},
|
|
687
764
|
},
|
|
688
765
|
});
|
|
766
|
+
await extensionManager.loadExtensions();
|
|
689
767
|
await expect(extensionManager.installOrUpdateExtension({
|
|
690
768
|
source: sourceExtDir,
|
|
691
769
|
type: 'local',
|
|
692
|
-
})).resolves.
|
|
770
|
+
})).resolves.toMatchObject({
|
|
771
|
+
name: 'my-local-extension',
|
|
772
|
+
});
|
|
693
773
|
expect(mockRequestConsent).toHaveBeenCalledWith(`Installing extension "my-local-extension".
|
|
694
774
|
${INSTALL_WARNING_MESSAGE}
|
|
695
775
|
This extension will run the following MCP servers:
|
|
@@ -708,10 +788,11 @@ This extension will run the following MCP servers:
|
|
|
708
788
|
},
|
|
709
789
|
},
|
|
710
790
|
});
|
|
791
|
+
await extensionManager.loadExtensions();
|
|
711
792
|
await expect(extensionManager.installOrUpdateExtension({
|
|
712
793
|
source: sourceExtDir,
|
|
713
794
|
type: 'local',
|
|
714
|
-
})).resolves.
|
|
795
|
+
})).resolves.toMatchObject({ name: 'my-local-extension' });
|
|
715
796
|
});
|
|
716
797
|
it('should cancel installation if user declines prompt for local extension with mcp servers', async () => {
|
|
717
798
|
const sourceExtDir = createExtension({
|
|
@@ -726,6 +807,7 @@ This extension will run the following MCP servers:
|
|
|
726
807
|
},
|
|
727
808
|
});
|
|
728
809
|
mockRequestConsent.mockResolvedValue(false);
|
|
810
|
+
await extensionManager.loadExtensions();
|
|
729
811
|
await expect(extensionManager.installOrUpdateExtension({
|
|
730
812
|
source: sourceExtDir,
|
|
731
813
|
type: 'local',
|
|
@@ -739,6 +821,7 @@ This extension will run the following MCP servers:
|
|
|
739
821
|
});
|
|
740
822
|
const targetExtDir = path.join(userExtensionsDir, 'my-local-extension');
|
|
741
823
|
const metadataPath = path.join(targetExtDir, INSTALL_METADATA_FILENAME);
|
|
824
|
+
await extensionManager.loadExtensions();
|
|
742
825
|
await extensionManager.installOrUpdateExtension({
|
|
743
826
|
source: sourceExtDir,
|
|
744
827
|
type: 'local',
|
|
@@ -766,6 +849,7 @@ This extension will run the following MCP servers:
|
|
|
766
849
|
},
|
|
767
850
|
},
|
|
768
851
|
});
|
|
852
|
+
await extensionManager.loadExtensions();
|
|
769
853
|
// Install it with hard coded consent first.
|
|
770
854
|
await extensionManager.installOrUpdateExtension({
|
|
771
855
|
source: sourceExtDir,
|
|
@@ -775,7 +859,7 @@ This extension will run the following MCP servers:
|
|
|
775
859
|
// Now update it without changing anything.
|
|
776
860
|
await expect(extensionManager.installOrUpdateExtension({ source: sourceExtDir, type: 'local' },
|
|
777
861
|
// Provide its own existing config as the previous config.
|
|
778
|
-
await extensionManager.loadExtensionConfig(sourceExtDir))).resolves.
|
|
862
|
+
await extensionManager.loadExtensionConfig(sourceExtDir))).resolves.toMatchObject({ name: 'my-local-extension' });
|
|
779
863
|
// Still only called once
|
|
780
864
|
expect(mockRequestConsent).toHaveBeenCalledOnce();
|
|
781
865
|
});
|
|
@@ -792,6 +876,7 @@ This extension will run the following MCP servers:
|
|
|
792
876
|
},
|
|
793
877
|
],
|
|
794
878
|
});
|
|
879
|
+
await extensionManager.loadExtensions();
|
|
795
880
|
await extensionManager.installOrUpdateExtension({
|
|
796
881
|
source: sourceExtDir,
|
|
797
882
|
type: 'local',
|
|
@@ -815,8 +900,9 @@ This extension will run the following MCP servers:
|
|
|
815
900
|
workspaceDir: tempWorkspaceDir,
|
|
816
901
|
requestConsent: mockRequestConsent,
|
|
817
902
|
requestSetting: null,
|
|
818
|
-
|
|
903
|
+
settings: loadSettings(tempWorkspaceDir).merged,
|
|
819
904
|
});
|
|
905
|
+
await extensionManager.loadExtensions();
|
|
820
906
|
await extensionManager.installOrUpdateExtension({
|
|
821
907
|
source: sourceExtDir,
|
|
822
908
|
type: 'local',
|
|
@@ -837,6 +923,7 @@ This extension will run the following MCP servers:
|
|
|
837
923
|
],
|
|
838
924
|
});
|
|
839
925
|
mockPromptForSettings.mockResolvedValueOnce('old-api-key');
|
|
926
|
+
await extensionManager.loadExtensions();
|
|
840
927
|
// Install it so it exists in the userExtensionsDir
|
|
841
928
|
await extensionManager.installOrUpdateExtension({
|
|
842
929
|
source: oldSourceExtDir,
|
|
@@ -890,6 +977,7 @@ This extension will run the following MCP servers:
|
|
|
890
977
|
},
|
|
891
978
|
],
|
|
892
979
|
});
|
|
980
|
+
await extensionManager.loadExtensions();
|
|
893
981
|
await extensionManager.installOrUpdateExtension({
|
|
894
982
|
source: oldSourceExtDir,
|
|
895
983
|
type: 'local',
|
|
@@ -954,6 +1042,7 @@ This extension will run the following MCP servers:
|
|
|
954
1042
|
version: '1.0.0',
|
|
955
1043
|
});
|
|
956
1044
|
vi.spyOn(ExtensionStorage, 'createTmpDir').mockResolvedValue(join(tempDir, extensionName));
|
|
1045
|
+
await extensionManager.loadExtensions();
|
|
957
1046
|
await extensionManager.installOrUpdateExtension({
|
|
958
1047
|
source: gitUrl,
|
|
959
1048
|
type: 'github-release',
|
|
@@ -975,6 +1064,7 @@ This extension will run the following MCP servers:
|
|
|
975
1064
|
errorMessage: 'download failed',
|
|
976
1065
|
type: 'github-release',
|
|
977
1066
|
});
|
|
1067
|
+
await extensionManager.loadExtensions();
|
|
978
1068
|
await extensionManager.installOrUpdateExtension({ source: gitUrl, type: 'github-release' });
|
|
979
1069
|
// It gets called once to ask for a git clone, and once to consent to
|
|
980
1070
|
// the actual extension features.
|
|
@@ -992,6 +1082,7 @@ This extension will run the following MCP servers:
|
|
|
992
1082
|
type: 'github-release',
|
|
993
1083
|
});
|
|
994
1084
|
mockRequestConsent.mockResolvedValue(false);
|
|
1085
|
+
await extensionManager.loadExtensions();
|
|
995
1086
|
await expect(extensionManager.installOrUpdateExtension({
|
|
996
1087
|
source: gitUrl,
|
|
997
1088
|
type: 'github-release',
|
|
@@ -1005,6 +1096,7 @@ This extension will run the following MCP servers:
|
|
|
1005
1096
|
failureReason: 'no release data',
|
|
1006
1097
|
type: 'github-release',
|
|
1007
1098
|
});
|
|
1099
|
+
await extensionManager.loadExtensions();
|
|
1008
1100
|
await extensionManager.installOrUpdateExtension({
|
|
1009
1101
|
source: gitUrl,
|
|
1010
1102
|
type: 'git',
|
|
@@ -1024,6 +1116,7 @@ This extension will run the following MCP servers:
|
|
|
1024
1116
|
errorMessage: 'No release data found',
|
|
1025
1117
|
type: 'github-release',
|
|
1026
1118
|
});
|
|
1119
|
+
await extensionManager.loadExtensions();
|
|
1027
1120
|
await extensionManager.installOrUpdateExtension({ source: gitUrl, type: 'github-release' });
|
|
1028
1121
|
expect(mockRequestConsent).toHaveBeenCalledWith(expect.stringContaining('Would you like to attempt to install via "git clone" instead?'));
|
|
1029
1122
|
expect(mockGit.clone).toHaveBeenCalled();
|
|
@@ -1037,6 +1130,7 @@ This extension will run the following MCP servers:
|
|
|
1037
1130
|
name: 'my-local-extension',
|
|
1038
1131
|
version: '1.0.0',
|
|
1039
1132
|
});
|
|
1133
|
+
await extensionManager.loadExtensions();
|
|
1040
1134
|
await extensionManager.uninstallExtension('my-local-extension', false);
|
|
1041
1135
|
expect(fs.existsSync(sourceExtDir)).toBe(false);
|
|
1042
1136
|
});
|
|
@@ -1051,12 +1145,14 @@ This extension will run the following MCP servers:
|
|
|
1051
1145
|
name: 'other-extension',
|
|
1052
1146
|
version: '1.0.0',
|
|
1053
1147
|
});
|
|
1148
|
+
await extensionManager.loadExtensions();
|
|
1054
1149
|
await extensionManager.uninstallExtension('my-local-extension', false);
|
|
1055
1150
|
expect(fs.existsSync(sourceExtDir)).toBe(false);
|
|
1056
|
-
expect(extensionManager.
|
|
1151
|
+
expect(extensionManager.getExtensions()).toHaveLength(1);
|
|
1057
1152
|
expect(fs.existsSync(otherExtDir)).toBe(true);
|
|
1058
1153
|
});
|
|
1059
1154
|
it('should throw an error if the extension does not exist', async () => {
|
|
1155
|
+
await extensionManager.loadExtensions();
|
|
1060
1156
|
await expect(extensionManager.uninstallExtension('nonexistent-extension', false)).rejects.toThrow('Extension not found.');
|
|
1061
1157
|
});
|
|
1062
1158
|
describe.each([true, false])('with isUpdate: %s', (isUpdate) => {
|
|
@@ -1070,6 +1166,7 @@ This extension will run the following MCP servers:
|
|
|
1070
1166
|
type: 'local',
|
|
1071
1167
|
},
|
|
1072
1168
|
});
|
|
1169
|
+
await extensionManager.loadExtensions();
|
|
1073
1170
|
await extensionManager.uninstallExtension('my-local-extension', isUpdate);
|
|
1074
1171
|
if (isUpdate) {
|
|
1075
1172
|
expect(mockLogExtensionUninstall).not.toHaveBeenCalled();
|
|
@@ -1088,6 +1185,7 @@ This extension will run the following MCP servers:
|
|
|
1088
1185
|
});
|
|
1089
1186
|
const enablementManager = new ExtensionEnablementManager();
|
|
1090
1187
|
enablementManager.enable('test-extension', true, '/some/scope');
|
|
1188
|
+
await extensionManager.loadExtensions();
|
|
1091
1189
|
await extensionManager.uninstallExtension('test-extension', isUpdate);
|
|
1092
1190
|
const config = enablementManager.readConfig()['test-extension'];
|
|
1093
1191
|
if (isUpdate) {
|
|
@@ -1110,6 +1208,7 @@ This extension will run the following MCP servers:
|
|
|
1110
1208
|
type: 'git',
|
|
1111
1209
|
},
|
|
1112
1210
|
});
|
|
1211
|
+
await extensionManager.loadExtensions();
|
|
1113
1212
|
await extensionManager.uninstallExtension(gitUrl, false);
|
|
1114
1213
|
expect(fs.existsSync(sourceExtDir)).toBe(false);
|
|
1115
1214
|
expect(mockLogExtensionUninstall).toHaveBeenCalled();
|
|
@@ -1122,28 +1221,31 @@ This extension will run the following MCP servers:
|
|
|
1122
1221
|
version: '1.0.0',
|
|
1123
1222
|
// No installMetadata provided
|
|
1124
1223
|
});
|
|
1224
|
+
await extensionManager.loadExtensions();
|
|
1125
1225
|
await expect(extensionManager.uninstallExtension('https://github.com/google/no-metadata-extension', false)).rejects.toThrow('Extension not found.');
|
|
1126
1226
|
});
|
|
1127
1227
|
});
|
|
1128
1228
|
describe('disableExtension', () => {
|
|
1129
|
-
it('should disable an extension at the user scope', () => {
|
|
1229
|
+
it('should disable an extension at the user scope', async () => {
|
|
1130
1230
|
createExtension({
|
|
1131
1231
|
extensionsDir: userExtensionsDir,
|
|
1132
1232
|
name: 'my-extension',
|
|
1133
1233
|
version: '1.0.0',
|
|
1134
1234
|
});
|
|
1235
|
+
await extensionManager.loadExtensions();
|
|
1135
1236
|
extensionManager.disableExtension('my-extension', SettingScope.User);
|
|
1136
1237
|
expect(isEnabled({
|
|
1137
1238
|
name: 'my-extension',
|
|
1138
1239
|
enabledForPath: tempWorkspaceDir,
|
|
1139
1240
|
})).toBe(false);
|
|
1140
1241
|
});
|
|
1141
|
-
it('should disable an extension at the workspace scope', () => {
|
|
1242
|
+
it('should disable an extension at the workspace scope', async () => {
|
|
1142
1243
|
createExtension({
|
|
1143
1244
|
extensionsDir: userExtensionsDir,
|
|
1144
1245
|
name: 'my-extension',
|
|
1145
1246
|
version: '1.0.0',
|
|
1146
1247
|
});
|
|
1248
|
+
await extensionManager.loadExtensions();
|
|
1147
1249
|
extensionManager.disableExtension('my-extension', SettingScope.Workspace);
|
|
1148
1250
|
expect(isEnabled({
|
|
1149
1251
|
name: 'my-extension',
|
|
@@ -1154,12 +1256,13 @@ This extension will run the following MCP servers:
|
|
|
1154
1256
|
enabledForPath: tempWorkspaceDir,
|
|
1155
1257
|
})).toBe(false);
|
|
1156
1258
|
});
|
|
1157
|
-
it('should handle disabling the same extension twice', () => {
|
|
1259
|
+
it('should handle disabling the same extension twice', async () => {
|
|
1158
1260
|
createExtension({
|
|
1159
1261
|
extensionsDir: userExtensionsDir,
|
|
1160
1262
|
name: 'my-extension',
|
|
1161
1263
|
version: '1.0.0',
|
|
1162
1264
|
});
|
|
1265
|
+
await extensionManager.loadExtensions();
|
|
1163
1266
|
extensionManager.disableExtension('my-extension', SettingScope.User);
|
|
1164
1267
|
extensionManager.disableExtension('my-extension', SettingScope.User);
|
|
1165
1268
|
expect(isEnabled({
|
|
@@ -1167,10 +1270,10 @@ This extension will run the following MCP servers:
|
|
|
1167
1270
|
enabledForPath: tempWorkspaceDir,
|
|
1168
1271
|
})).toBe(false);
|
|
1169
1272
|
});
|
|
1170
|
-
it('should throw an error if you request system scope', () => {
|
|
1171
|
-
expect(() => extensionManager.disableExtension('my-extension', SettingScope.System)).toThrow('System and SystemDefaults scopes are not supported.');
|
|
1273
|
+
it('should throw an error if you request system scope', async () => {
|
|
1274
|
+
await expect(async () => await extensionManager.disableExtension('my-extension', SettingScope.System)).rejects.toThrow('System and SystemDefaults scopes are not supported.');
|
|
1172
1275
|
});
|
|
1173
|
-
it('should log a disable event', () => {
|
|
1276
|
+
it('should log a disable event', async () => {
|
|
1174
1277
|
createExtension({
|
|
1175
1278
|
extensionsDir: userExtensionsDir,
|
|
1176
1279
|
name: 'ext1',
|
|
@@ -1180,6 +1283,7 @@ This extension will run the following MCP servers:
|
|
|
1180
1283
|
type: 'local',
|
|
1181
1284
|
},
|
|
1182
1285
|
});
|
|
1286
|
+
await extensionManager.loadExtensions();
|
|
1183
1287
|
extensionManager.disableExtension('ext1', SettingScope.Workspace);
|
|
1184
1288
|
expect(mockLogExtensionDisable).toHaveBeenCalled();
|
|
1185
1289
|
expect(ExtensionDisableEvent).toHaveBeenCalledWith(hashValue('ext1'), hashValue(userExtensionsDir), SettingScope.Workspace);
|
|
@@ -1190,38 +1294,40 @@ This extension will run the following MCP servers:
|
|
|
1190
1294
|
vi.restoreAllMocks();
|
|
1191
1295
|
});
|
|
1192
1296
|
const getActiveExtensions = () => {
|
|
1193
|
-
const extensions = extensionManager.
|
|
1297
|
+
const extensions = extensionManager.getExtensions();
|
|
1194
1298
|
return extensions.filter((e) => e.isActive);
|
|
1195
1299
|
};
|
|
1196
|
-
it('should enable an extension at the user scope', () => {
|
|
1300
|
+
it('should enable an extension at the user scope', async () => {
|
|
1197
1301
|
createExtension({
|
|
1198
1302
|
extensionsDir: userExtensionsDir,
|
|
1199
1303
|
name: 'ext1',
|
|
1200
1304
|
version: '1.0.0',
|
|
1201
1305
|
});
|
|
1306
|
+
await extensionManager.loadExtensions();
|
|
1202
1307
|
extensionManager.disableExtension('ext1', SettingScope.User);
|
|
1203
1308
|
let activeExtensions = getActiveExtensions();
|
|
1204
1309
|
expect(activeExtensions).toHaveLength(0);
|
|
1205
|
-
extensionManager.enableExtension('ext1', SettingScope.User);
|
|
1206
|
-
activeExtensions = getActiveExtensions();
|
|
1310
|
+
await extensionManager.enableExtension('ext1', SettingScope.User);
|
|
1311
|
+
activeExtensions = await getActiveExtensions();
|
|
1207
1312
|
expect(activeExtensions).toHaveLength(1);
|
|
1208
1313
|
expect(activeExtensions[0].name).toBe('ext1');
|
|
1209
1314
|
});
|
|
1210
|
-
it('should enable an extension at the workspace scope', () => {
|
|
1315
|
+
it('should enable an extension at the workspace scope', async () => {
|
|
1211
1316
|
createExtension({
|
|
1212
1317
|
extensionsDir: userExtensionsDir,
|
|
1213
1318
|
name: 'ext1',
|
|
1214
1319
|
version: '1.0.0',
|
|
1215
1320
|
});
|
|
1321
|
+
await extensionManager.loadExtensions();
|
|
1216
1322
|
extensionManager.disableExtension('ext1', SettingScope.Workspace);
|
|
1217
1323
|
let activeExtensions = getActiveExtensions();
|
|
1218
1324
|
expect(activeExtensions).toHaveLength(0);
|
|
1219
|
-
extensionManager.enableExtension('ext1', SettingScope.Workspace);
|
|
1220
|
-
activeExtensions = getActiveExtensions();
|
|
1325
|
+
await extensionManager.enableExtension('ext1', SettingScope.Workspace);
|
|
1326
|
+
activeExtensions = await getActiveExtensions();
|
|
1221
1327
|
expect(activeExtensions).toHaveLength(1);
|
|
1222
1328
|
expect(activeExtensions[0].name).toBe('ext1');
|
|
1223
1329
|
});
|
|
1224
|
-
it('should log an enable event', () => {
|
|
1330
|
+
it('should log an enable event', async () => {
|
|
1225
1331
|
createExtension({
|
|
1226
1332
|
extensionsDir: userExtensionsDir,
|
|
1227
1333
|
name: 'ext1',
|
|
@@ -1231,6 +1337,7 @@ This extension will run the following MCP servers:
|
|
|
1231
1337
|
type: 'local',
|
|
1232
1338
|
},
|
|
1233
1339
|
});
|
|
1340
|
+
await extensionManager.loadExtensions();
|
|
1234
1341
|
extensionManager.disableExtension('ext1', SettingScope.Workspace);
|
|
1235
1342
|
extensionManager.enableExtension('ext1', SettingScope.Workspace);
|
|
1236
1343
|
expect(mockLogExtensionEnable).toHaveBeenCalled();
|