fix(cli): uninstall extensions using their source URL (#8692)

Co-authored-by: Taneja Hriday <hridayt@google.com>
This commit is contained in:
hritan
2025-09-18 16:00:28 +00:00
committed by GitHub
parent 0534ca74d4
commit 2d406ffc75
3 changed files with 47 additions and 10 deletions

View File

@@ -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) => {

View File

@@ -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', () => {

View File

@@ -573,17 +573,20 @@ export async function loadExtensionConfig(
}
export async function uninstallExtension(
extensionName: string,
extensionIdentifier: string,
cwd: string = process.cwd(),
): Promise<void> {
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(),