mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
test(cli): add tests for builtin extension collisions and migration
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
{ "name": "my-extension", "version": "1.0.0", "mcpServers": {} }
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
getRealPath,
|
getRealPath,
|
||||||
type CustomTheme,
|
type CustomTheme,
|
||||||
IntegrityDataStatus,
|
IntegrityDataStatus,
|
||||||
|
coreEvents,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
|
|
||||||
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
||||||
@@ -697,4 +698,97 @@ describe('ExtensionManager', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Builtin Extensions', () => {
|
||||||
|
let builtinExtensionsDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
builtinExtensionsDir = fs.mkdtempSync(
|
||||||
|
path.join(tempHomeDir, 'gemini-cli-test-builtin-'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should warn and prefer builtin when names collide', async () => {
|
||||||
|
// 1. Create a manual extension named 'test-ext'
|
||||||
|
createExtension({
|
||||||
|
extensionsDir: userExtensionsDir,
|
||||||
|
name: 'test-ext',
|
||||||
|
version: '1.0.0-manual',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Create a builtin extension named 'test-ext'
|
||||||
|
createExtension({
|
||||||
|
extensionsDir: builtinExtensionsDir,
|
||||||
|
name: 'test-ext',
|
||||||
|
version: '1.0.0-builtin',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emitSpy = vi.spyOn(coreEvents, 'emitFeedback');
|
||||||
|
|
||||||
|
// Create a FRESH manager to ensure loadExtensions actually runs
|
||||||
|
const manager = new ExtensionManager({
|
||||||
|
settings: createTestMergedSettings(),
|
||||||
|
workspaceDir: tempWorkspaceDir,
|
||||||
|
requestConsent: vi.fn().mockResolvedValue(true),
|
||||||
|
requestSetting: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const extensions = await manager.loadExtensions(builtinExtensionsDir);
|
||||||
|
|
||||||
|
// Verify builtin took precedence
|
||||||
|
const testExt = extensions.find((e) => e.name === 'test-ext');
|
||||||
|
expect(testExt).toBeDefined();
|
||||||
|
expect(testExt?.version).toBe('1.0.0-builtin');
|
||||||
|
|
||||||
|
// Verify warning was shown
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(
|
||||||
|
'warning',
|
||||||
|
expect.stringContaining('Extension "test-ext" is now built-in'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should warn when legacy conductor is found while loading sdd builtin', async () => {
|
||||||
|
// 1. Create a manual extension named 'conductor'
|
||||||
|
createExtension({
|
||||||
|
extensionsDir: userExtensionsDir,
|
||||||
|
name: 'conductor',
|
||||||
|
version: '0.1.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Create a builtin extension named 'sdd'
|
||||||
|
createExtension({
|
||||||
|
extensionsDir: builtinExtensionsDir,
|
||||||
|
name: 'sdd',
|
||||||
|
version: '1.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emitSpy = vi.spyOn(coreEvents, 'emitFeedback');
|
||||||
|
|
||||||
|
// Create a FRESH manager
|
||||||
|
const manager = new ExtensionManager({
|
||||||
|
settings: createTestMergedSettings(),
|
||||||
|
workspaceDir: tempWorkspaceDir,
|
||||||
|
requestConsent: vi.fn().mockResolvedValue(true),
|
||||||
|
requestSetting: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const extensions = await manager.loadExtensions(builtinExtensionsDir);
|
||||||
|
|
||||||
|
// Verify both are loaded (logic currently loads both but warns)
|
||||||
|
expect(extensions.find((e) => e.name === 'conductor')).toBeDefined();
|
||||||
|
expect(extensions.find((e) => e.name === 'sdd')).toBeDefined();
|
||||||
|
|
||||||
|
// Verify warning was shown
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(
|
||||||
|
'warning',
|
||||||
|
expect.stringContaining(
|
||||||
|
'The "conductor" extension has been renamed to "sdd"',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(
|
||||||
|
'warning',
|
||||||
|
expect.stringContaining('.gemini/specs/'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
|||||||
/**
|
/**
|
||||||
* Loads all installed extensions, should only be called once.
|
* Loads all installed extensions, should only be called once.
|
||||||
*/
|
*/
|
||||||
async loadExtensions(): Promise<GeminiCLIExtension[]> {
|
async loadExtensions(builtinDir?: string): Promise<GeminiCLIExtension[]> {
|
||||||
if (this.loadedExtensions) {
|
if (this.loadedExtensions) {
|
||||||
throw new Error('Extensions already loaded, only load extensions once.');
|
throw new Error('Extensions already loaded, only load extensions once.');
|
||||||
}
|
}
|
||||||
@@ -637,26 +637,30 @@ Would you like to attempt to install via "git clone" instead?`,
|
|||||||
(ext): ext is GeminiCLIExtension => ext !== null,
|
(ext): ext is GeminiCLIExtension => ext !== null,
|
||||||
);
|
);
|
||||||
|
|
||||||
let builtinExtensionsDir = path.join(
|
let builtinExtensionsDir = builtinDir;
|
||||||
path.dirname(fileURLToPath(import.meta.url)),
|
if (!builtinExtensionsDir) {
|
||||||
'extensions',
|
|
||||||
'builtin',
|
|
||||||
);
|
|
||||||
if (!fs.existsSync(builtinExtensionsDir)) {
|
|
||||||
builtinExtensionsDir = path.join(
|
builtinExtensionsDir = path.join(
|
||||||
path.dirname(fileURLToPath(import.meta.url)),
|
path.dirname(fileURLToPath(import.meta.url)),
|
||||||
'..',
|
|
||||||
'..',
|
|
||||||
'..',
|
|
||||||
'..',
|
|
||||||
'core',
|
|
||||||
'src',
|
|
||||||
'extensions',
|
'extensions',
|
||||||
'builtin',
|
'builtin',
|
||||||
);
|
);
|
||||||
|
if (!fs.existsSync(builtinExtensionsDir)) {
|
||||||
|
builtinExtensionsDir = path.join(
|
||||||
|
path.dirname(fileURLToPath(import.meta.url)),
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'core',
|
||||||
|
'src',
|
||||||
|
'extensions',
|
||||||
|
'builtin',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env['VITEST'] && fs.existsSync(builtinExtensionsDir)) {
|
const loadBuiltins = builtinDir || !process.env['VITEST'];
|
||||||
|
if (loadBuiltins && fs.existsSync(builtinExtensionsDir)) {
|
||||||
const builtinSubdirs =
|
const builtinSubdirs =
|
||||||
await fs.promises.readdir(builtinExtensionsDir);
|
await fs.promises.readdir(builtinExtensionsDir);
|
||||||
const builtinPromises = builtinSubdirs.map((subdir) => {
|
const builtinPromises = builtinSubdirs.map((subdir) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user