[extensions] Add extensions uninstall command (#6877)

This commit is contained in:
christine betts
2025-08-25 17:40:15 +00:00
committed by GitHub
parent 0bd496bd51
commit ade703944d
5 changed files with 142 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ import {
annotateActiveExtensions,
installExtension,
loadExtensions,
uninstallExtension,
} from './extension.js';
import { execSync } from 'child_process';
import { SimpleGit, simpleGit } from 'simple-git';
@@ -280,6 +281,65 @@ describe('installExtension', () => {
});
});
describe('uninstallExtension', () => {
let tempHomeDir: string;
let userExtensionsDir: string;
beforeEach(() => {
tempHomeDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
);
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
userExtensionsDir = path.join(tempHomeDir, '.gemini', 'extensions');
// Clean up before each test
fs.rmSync(userExtensionsDir, { recursive: true, force: true });
fs.mkdirSync(userExtensionsDir, { recursive: true });
vi.mocked(execSync).mockClear();
});
afterEach(() => {
fs.rmSync(tempHomeDir, { recursive: true, force: true });
});
it('should uninstall an extension by name', async () => {
const sourceExtDir = createExtension(
userExtensionsDir,
'my-local-extension',
'1.0.0',
);
await uninstallExtension('my-local-extension');
expect(fs.existsSync(sourceExtDir)).toBe(false);
});
it('should uninstall an extension by name and retain existing extensions', async () => {
const sourceExtDir = createExtension(
userExtensionsDir,
'my-local-extension',
'1.0.0',
);
const otherExtDir = createExtension(
userExtensionsDir,
'other-extension',
'1.0.0',
);
await uninstallExtension('my-local-extension');
expect(fs.existsSync(sourceExtDir)).toBe(false);
expect(loadExtensions(tempHomeDir)).toHaveLength(1);
expect(fs.existsSync(otherExtDir)).toBe(true);
});
it('should throw an error if the extension does not exist', async () => {
await expect(uninstallExtension('nonexistent-extension')).rejects.toThrow(
'Error: Extension "nonexistent-extension" not found.',
);
});
});
function createExtension(
extensionsDir: string,
name: string,

View File

@@ -338,3 +338,19 @@ export async function installExtension(
return newExtensionName;
}
export async function uninstallExtension(extensionName: string): Promise<void> {
const installedExtensions = loadUserExtensions();
if (
!installedExtensions.some(
(installed) => installed.config.name === extensionName,
)
) {
throw new Error(`Error: Extension "${extensionName}" not found.`);
}
const storage = new ExtensionStorage(extensionName);
return await fs.promises.rm(storage.getExtensionDir(), {
recursive: true,
force: true,
});
}