From 28f10bd193ab3e9ae186c884ee1b81da78f89b2f Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 11 Aug 2026 15:17:48 -0700 Subject: [PATCH 1/5] vsfan_ext_uninstall_prompt --- src/commands/ext-uninstall.ts | 45 +++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index bc2f2959c50..bfa6bbb6d77 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -6,25 +6,66 @@ import { logPrefix, } from "../extensions/extensionsHelper"; import { requirePermissions } from "../requirePermissions"; -import { logLabeledWarning } from "../utils"; +import { logLabeledBullet, logLabeledWarning, logLabeledSuccess, logLabeledError } from "../utils"; import * as manifest from "../extensions/manifest"; +import { deleteInstance } from "../extensions/extensionsApi"; import { Options } from "../options"; +import { needProjectId } from "../projectUtils"; +import { confirm } from "../prompt"; export const command = new Command("ext:uninstall ") .description("uninstall an extension that is installed in your Firebase project by instance ID") .option("--local", "deprecated") + .option("--immediate", "") .withForce() .before(requirePermissions, ["firebaseextensions.instances.delete"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) - .action((instanceId: string, options: Options) => { + .action(async (instanceId: string, options: Options) => { if (options.local) { logLabeledWarning( logPrefix, "As of firebase-tools@11.0.0, the `--local` flag is no longer required, as it is the default behavior.", ); } + if (options.immediate) { + const projectId = needProjectId(options); + let config; + try { + config = manifest.loadConfig(options); + } catch (err: any) { + logLabeledBullet( + logPrefix, + "No firebase.json found. Proceeding to immediate extension instance teardown.", + ); + } + if (config) { + manifest.removeFromManifest(instanceId, config); + } + + if ( + !(await confirm({ + message: `About to delete Extensions instance ${projectId}/${instanceId}, its associated resources, and service account. Continue?`, + nonInteractive: options.nonInteractive, + force: options.force, + default: true, + })) + ) { + return; + } + try { + await deleteInstance(projectId, instanceId); + } catch (err: any) { + logLabeledError( + logPrefix, + `Error when attempting deletion: ${err instanceof Error ? err.message : err.toString()}`, + ); + return; + } + logLabeledSuccess(logPrefix, `Deleted Extensions instance ${projectId}/${instanceId}.`); + return; + } const config = manifest.loadConfig(options); manifest.removeFromManifest(instanceId, config); }); From 15c4c7d3207e51f2b32b93ffcfaa4f3ba7f635e8 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 11 Aug 2026 15:20:37 -0700 Subject: [PATCH 2/5] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f84f1bd06..ce8e3b87056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,4 @@ - Added web app support for Crashlytics MCP tools and prompts. - Added support for forwarding custom HTTP headers (`Mcp-Param-*`) to remote MCP tools when defined in tool parameter input schemas (`x-mcp-header`), per [SEP-2243](https://modelcontextprotocol.io/seps/2243-http-standardization). - Improved function parameter prompting clarity for multi-codebase deploys (#10897) +- Adds --immediate flag to ext:uninstall (#10921) From 338c91ff35cbdbbed6721bb252e0e22cb4260e75 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 11 Aug 2026 15:27:20 -0700 Subject: [PATCH 3/5] slop --- src/commands/ext-uninstall.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index bfa6bbb6d77..c2139ddf4b1 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -6,17 +6,18 @@ import { logPrefix, } from "../extensions/extensionsHelper"; import { requirePermissions } from "../requirePermissions"; -import { logLabeledBullet, logLabeledWarning, logLabeledSuccess, logLabeledError } from "../utils"; +import { logLabeledBullet, logLabeledWarning, logLabeledSuccess } from "../utils"; import * as manifest from "../extensions/manifest"; import { deleteInstance } from "../extensions/extensionsApi"; import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { confirm } from "../prompt"; +import { FirebaseError } from "../error"; export const command = new Command("ext:uninstall ") .description("uninstall an extension that is installed in your Firebase project by instance ID") .option("--local", "deprecated") - .option("--immediate", "") + .option("--immediate", "immediately destroy GCP resources instead of waiting on next deploy. Can be run outside a firebase project directory.") .withForce() .before(requirePermissions, ["firebaseextensions.instances.delete"]) .before(ensureExtensionsApiEnabled) @@ -34,13 +35,13 @@ export const command = new Command("ext:uninstall ") let config; try { config = manifest.loadConfig(options); - } catch (err: any) { + } catch { logLabeledBullet( logPrefix, "No firebase.json found. Proceeding to immediate extension instance teardown.", ); } - if (config) { + if (config && manifest.instanceExists(instanceId, config)) { manifest.removeFromManifest(instanceId, config); } @@ -56,10 +57,10 @@ export const command = new Command("ext:uninstall ") } try { await deleteInstance(projectId, instanceId); - } catch (err: any) { - logLabeledError( - logPrefix, - `Error when attempting deletion: ${err instanceof Error ? err.message : err.toString()}`, + } catch (err: unknown) { + throw new FirebaseError( + `Error when attempting deletion: ${err instanceof Error ? err.message : String(err)}`, + { original: err instanceof Error ? err : undefined } ); return; } From fd1d971ee7f121500bb12f92e7b70e9024382a92 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 11 Aug 2026 16:11:09 -0700 Subject: [PATCH 4/5] lint --- src/commands/ext-uninstall.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index c2139ddf4b1..4dffd79c7ea 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -17,7 +17,10 @@ import { FirebaseError } from "../error"; export const command = new Command("ext:uninstall ") .description("uninstall an extension that is installed in your Firebase project by instance ID") .option("--local", "deprecated") - .option("--immediate", "immediately destroy GCP resources instead of waiting on next deploy. Can be run outside a firebase project directory.") + .option( + "--immediate", + "immediately destroy GCP resources instead of waiting on next deploy. Can be run outside a firebase project directory.", + ) .withForce() .before(requirePermissions, ["firebaseextensions.instances.delete"]) .before(ensureExtensionsApiEnabled) @@ -60,12 +63,10 @@ export const command = new Command("ext:uninstall ") } catch (err: unknown) { throw new FirebaseError( `Error when attempting deletion: ${err instanceof Error ? err.message : String(err)}`, - { original: err instanceof Error ? err : undefined } + { original: err instanceof Error ? err : undefined }, ); - return; } logLabeledSuccess(logPrefix, `Deleted Extensions instance ${projectId}/${instanceId}.`); - return; } const config = manifest.loadConfig(options); manifest.removeFromManifest(instanceId, config); From 51fa8f7df5443e7a4b1014a818858f1080492711 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 11 Aug 2026 16:18:19 -0700 Subject: [PATCH 5/5] oops --- src/commands/ext-uninstall.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 4dffd79c7ea..c47adb7b88b 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -67,6 +67,7 @@ export const command = new Command("ext:uninstall ") ); } logLabeledSuccess(logPrefix, `Deleted Extensions instance ${projectId}/${instanceId}.`); + return; } const config = manifest.loadConfig(options); manifest.removeFromManifest(instanceId, config);