From 71136f4c2f56bde54324060bc16343f76623c439 Mon Sep 17 00:00:00 2001 From: Aryan Falahatpisheh Date: Thu, 19 Mar 2026 11:48:28 -0700 Subject: [PATCH 1/2] Implement ListSupportedRuntime usage for runtimes (dynamically populate them) --- src/apphosting/backend.ts | 31 ++++++++++++++++++++----------- src/gcp/apphosting.spec.ts | 12 ++++++++++++ src/gcp/apphosting.ts | 24 ++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/apphosting/backend.ts b/src/apphosting/backend.ts index db2228fb8b8..de6cf3de411 100644 --- a/src/apphosting/backend.ts +++ b/src/apphosting/backend.ts @@ -134,12 +134,21 @@ export async function doSetup( if (nonInteractive) { runtime = DEFAULT_RUNTIME; } else { + const choices: Choice[] = [{ name: "Node.js (default)", value: DEFAULT_RUNTIME }]; + try { + const supportedRuntimes = await apphosting.listSupportedRuntimes(projectId, location); + for (const r of supportedRuntimes) { + if (r.runtimeId !== DEFAULT_RUNTIME) { + choices.push({ name: r.runtimeId, value: r.runtimeId }); + } + } + } catch (err) { + logWarning("Failed to list supported runtimes. Falling back to default."); + } + runtime = await select({ message: "Which runtime do you want to use?", - choices: [ - { name: "Node.js (default)", value: DEFAULT_RUNTIME }, - { name: "Node.js 22", value: "nodejs22" }, - ], + choices: choices, default: DEFAULT_RUNTIME, }); } @@ -382,9 +391,9 @@ export async function createBackend( servingLocality: "GLOBAL_ACCESS", codebase: repository ? { - repository: `${repository.name}`, - rootDirectory: rootDir, - } + repository: `${repository.name}`, + rootDirectory: rootDir, + } : undefined, labels: deploymentTool.labels(), serviceAccount: serviceAccount || defaultServiceAccount, @@ -566,7 +575,7 @@ export async function chooseBackends( if (unreachable && unreachable.length !== 0) { logWarning( `The following locations are currently unreachable: ${unreachable.join(",")}.\n` + - "If your backend is in one of these regions, please try again later.", + "If your backend is in one of these regions, please try again later.", ); } backends = backends.filter( @@ -624,7 +633,7 @@ export async function getBackendForAmbiguousLocation( if (unreachable && unreachable.length !== 0) { logWarning( `The following locations are currently unreachable: ${unreachable.join(", ")}.\n` + - "If your backend is in one of these regions, please try again later.", + "If your backend is in one of these regions, please try again later.", ); } backends = backends.filter( @@ -669,7 +678,7 @@ export async function getBackend( const locations = backends.map((b) => apphosting.parseBackendName(b.name).location); throw new FirebaseError( `You have multiple backends with the same ${backendId} ID in regions: ${locations.join(", ")}. This is not allowed until we can support more locations. ` + - "Please delete and recreate any backends that share an ID with another backend.", + "Please delete and recreate any backends that share an ID with another backend.", ); } if (backends.length === 1) { @@ -678,7 +687,7 @@ export async function getBackend( if (unreachable && unreachable.length !== 0) { logWarning( `Backends with the following primary regions are unreachable: ${unreachable.join(", ")}.\n` + - "If your backend is in one of these regions, please try again later.", + "If your backend is in one of these regions, please try again later.", ); } throw new FirebaseError(`No backend named ${backendId} found.`); diff --git a/src/gcp/apphosting.spec.ts b/src/gcp/apphosting.spec.ts index f2d4ab97184..6840e81657b 100644 --- a/src/gcp/apphosting.spec.ts +++ b/src/gcp/apphosting.spec.ts @@ -278,5 +278,17 @@ describe("apphosting", () => { queryParams: { pageToken: "2" }, }); }); + + it("lists supported runtimes", async () => { + get.resolves({ + body: { + supportedRuntimes: [{ runtimeId: "nodejs22" }], + }, + }); + await expect(apphosting.listSupportedRuntimes("p", "l")).to.eventually.deep.equal([ + { runtimeId: "nodejs22" }, + ]); + expect(get).to.have.been.calledWithMatch("projects/p/locations/l/supportedRuntimes"); + }); }); }); diff --git a/src/gcp/apphosting.ts b/src/gcp/apphosting.ts index 9392d985443..d007a9250a2 100644 --- a/src/gcp/apphosting.ts +++ b/src/gcp/apphosting.ts @@ -58,6 +58,18 @@ export interface Backend { automaticBaseImageUpdatesDisabled?: boolean; } +export interface SupportedRuntime { + name: string; + runtimeId: string; + automaticBaseImageUpdatesSupported: boolean; + deprecateTime?: string; + decommissionTime?: string; +} + +export interface ListSupportedRuntimesResponse { + supportedRuntimes: SupportedRuntime[]; +} + export interface ManagedResource { runService: { service: string }; } @@ -711,6 +723,18 @@ export async function listLocations(projectId: string): Promise { return locations; } +/** + * Lists supported runtimes for a given project and location. + */ +export async function listSupportedRuntimes( + projectId: string, + location: string, +): Promise { + const name = `projects/${projectId}/locations/${location}/supportedRuntimes`; + const res = await client.get(name); + return res.body.supportedRuntimes || []; +} + /** * Ensure that the App Hosting API is enabled on the project. */ From 579e78a2acd11e1d2eb3d098994689e72d825a02 Mon Sep 17 00:00:00 2001 From: Aryan Falahatpisheh Date: Fri, 20 Mar 2026 12:33:36 -0700 Subject: [PATCH 2/2] Automatic format fixes --- src/apphosting/backend.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/apphosting/backend.ts b/src/apphosting/backend.ts index de6cf3de411..056f2786a32 100644 --- a/src/apphosting/backend.ts +++ b/src/apphosting/backend.ts @@ -391,9 +391,9 @@ export async function createBackend( servingLocality: "GLOBAL_ACCESS", codebase: repository ? { - repository: `${repository.name}`, - rootDirectory: rootDir, - } + repository: `${repository.name}`, + rootDirectory: rootDir, + } : undefined, labels: deploymentTool.labels(), serviceAccount: serviceAccount || defaultServiceAccount, @@ -575,7 +575,7 @@ export async function chooseBackends( if (unreachable && unreachable.length !== 0) { logWarning( `The following locations are currently unreachable: ${unreachable.join(",")}.\n` + - "If your backend is in one of these regions, please try again later.", + "If your backend is in one of these regions, please try again later.", ); } backends = backends.filter( @@ -633,7 +633,7 @@ export async function getBackendForAmbiguousLocation( if (unreachable && unreachable.length !== 0) { logWarning( `The following locations are currently unreachable: ${unreachable.join(", ")}.\n` + - "If your backend is in one of these regions, please try again later.", + "If your backend is in one of these regions, please try again later.", ); } backends = backends.filter( @@ -678,7 +678,7 @@ export async function getBackend( const locations = backends.map((b) => apphosting.parseBackendName(b.name).location); throw new FirebaseError( `You have multiple backends with the same ${backendId} ID in regions: ${locations.join(", ")}. This is not allowed until we can support more locations. ` + - "Please delete and recreate any backends that share an ID with another backend.", + "Please delete and recreate any backends that share an ID with another backend.", ); } if (backends.length === 1) { @@ -687,7 +687,7 @@ export async function getBackend( if (unreachable && unreachable.length !== 0) { logWarning( `Backends with the following primary regions are unreachable: ${unreachable.join(", ")}.\n` + - "If your backend is in one of these regions, please try again later.", + "If your backend is in one of these regions, please try again later.", ); } throw new FirebaseError(`No backend named ${backendId} found.`);