From 2d406ffc755f79dc903c6993aae03150e1678dc0 Mon Sep 17 00:00:00 2001 From: hritan <48129645+hritan@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:00:28 +0000 Subject: [PATCH] fix(cli): uninstall extensions using their source URL (#8692) Co-authored-by: Taneja Hriday --- .../cli/src/commands/extensions/uninstall.ts | 4 +-- packages/cli/src/config/extension.test.ts | 36 ++++++++++++++++++- packages/cli/src/config/extension.ts | 17 +++++---- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/commands/extensions/uninstall.ts b/packages/cli/src/commands/extensions/uninstall.ts index ff93b79723..d7c131962b 100644 --- a/packages/cli/src/commands/extensions/uninstall.ts +++ b/packages/cli/src/commands/extensions/uninstall.ts @@ -9,7 +9,7 @@ import { uninstallExtension } from '../../config/extension.js'; import { getErrorMessage } from '../../utils/errors.js'; interface UninstallArgs { - name: string; + name: string; // can be extension name or source URL. } export async function handleUninstall(args: UninstallArgs) { @@ -28,7 +28,7 @@ export const uninstallCommand: CommandModule = { builder: (yargs) => yargs .positional('name', { - describe: 'The name of the extension to uninstall.', + describe: 'The name or source path of the extension to uninstall.', type: 'string', }) .check((argv) => { diff --git a/packages/cli/src/config/extension.test.ts b/packages/cli/src/config/extension.test.ts index fda8e58279..28ea16bf1c 100644 --- a/packages/cli/src/config/extension.test.ts +++ b/packages/cli/src/config/extension.test.ts @@ -715,7 +715,7 @@ describe('extension tests', () => { it('should throw an error if the extension does not exist', async () => { await expect(uninstallExtension('nonexistent-extension')).rejects.toThrow( - 'Extension "nonexistent-extension" not found.', + 'Extension not found.', ); }); @@ -733,6 +733,40 @@ describe('extension tests', () => { new ExtensionUninstallEvent('my-local-extension', 'success'), ); }); + + it('should uninstall an extension by its source URL', async () => { + const gitUrl = 'https://github.com/google/gemini-sql-extension.git'; + const sourceExtDir = createExtension({ + extensionsDir: userExtensionsDir, + name: 'gemini-sql-extension', + version: '1.0.0', + installMetadata: { + source: gitUrl, + type: 'git', + }, + }); + + await uninstallExtension(gitUrl); + + expect(fs.existsSync(sourceExtDir)).toBe(false); + const logger = ClearcutLogger.getInstance({} as Config); + expect(logger?.logExtensionUninstallEvent).toHaveBeenCalledWith( + new ExtensionUninstallEvent('gemini-sql-extension', 'success'), + ); + }); + + it('should fail to uninstall by URL if an extension has no install metadata', async () => { + createExtension({ + extensionsDir: userExtensionsDir, + name: 'no-metadata-extension', + version: '1.0.0', + // No installMetadata provided + }); + + await expect( + uninstallExtension('https://github.com/google/no-metadata-extension'), + ).rejects.toThrow('Extension not found.'); + }); }); describe('performWorkspaceExtensionMigration', () => { diff --git a/packages/cli/src/config/extension.ts b/packages/cli/src/config/extension.ts index c187bcb38e..8205efa011 100644 --- a/packages/cli/src/config/extension.ts +++ b/packages/cli/src/config/extension.ts @@ -573,17 +573,20 @@ export async function loadExtensionConfig( } export async function uninstallExtension( - extensionName: string, + extensionIdentifier: string, cwd: string = process.cwd(), ): Promise { const logger = getClearcutLogger(cwd); const installedExtensions = loadUserExtensions(); - if ( - !installedExtensions.some( - (installed) => installed.config.name === extensionName, - ) - ) { - throw new Error(`Extension "${extensionName}" not found.`); + const extensionName = installedExtensions.find( + (installed) => + installed.config.name.toLowerCase() === + extensionIdentifier.toLowerCase() || + installed.installMetadata?.source.toLowerCase() === + extensionIdentifier.toLowerCase(), + )?.config.name; + if (!extensionName) { + throw new Error(`Extension not found.`); } const manager = new ExtensionEnablementManager( ExtensionStorage.getUserExtensionsDir(),