Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion src/apphosting/backend.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,43 @@ describe("apphosting setup functions", () => {
labels: deploymentTool.labels(),
serviceAccount: "custom-service-account",
appId: webAppId,
runtime: undefined,
};
expect(createBackendStub).to.be.calledWith(projectId, location, backendInput);
expect(createBackendStub).to.be.calledWith(projectId, location, backendInput, backendId);
});

const runtimes = ["nodejs22", ""];
for (const runtime of runtimes) {
it(`should create a new backend with runtime ${runtime}`, async () => {
createBackendStub.resolves(op);
pollOperationStub.resolves(completeBackend);

await createBackend(
projectId,
location,
backendId,
"custom-service-account",
cloudBuildConnRepo,
webAppId,
"/",
runtime,
);

const backendInput: Omit<apphosting.Backend, apphosting.BackendOutputOnlyFields> = {
servingLocality: "GLOBAL_ACCESS",
codebase: {
repository: cloudBuildConnRepo.name,
rootDirectory: "/",
},
labels: deploymentTool.labels(),
serviceAccount: "custom-service-account",
appId: webAppId,
runtime: runtime ? { value: runtime } : undefined,
};
expect(createBackendStub).to.be.calledWith(projectId, location, backendInput, backendId);
});
}

it("should set default rollout policy to 100% all at once", async () => {
const completeTraffic: apphosting.Traffic = {
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`,
Expand Down
21 changes: 21 additions & 0 deletions src/apphosting/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import fetch from "node-fetch";
import { orchestrateRollout } from "./rollout";
import * as fuzzy from "fuzzy";

const DEFAULT_RUNTIME = "nodejs";

const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";

const apphostingPollerOptions: Omit<poller.OperationPollerOptions, "operationResourceName"> = {
Expand Down Expand Up @@ -79,6 +81,7 @@ export async function doSetup(
serviceAccount?: string,
primaryRegion?: string,
rootDir?: string,
runtime?: string,
): Promise<void> {
await ensureRequiredApisEnabled(projectId);

Expand Down Expand Up @@ -125,6 +128,21 @@ export async function doSetup(
throw new FirebaseError("Internal error: location or backendId is not defined.");
}

if (!runtime) {
if (nonInteractive) {
runtime = DEFAULT_RUNTIME;
} else {
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" },
],
default: DEFAULT_RUNTIME,
});
}
}

const webApp = await webApps.getOrCreateWebApp(
projectId,
webAppName ? webAppName : null,
Expand All @@ -143,6 +161,7 @@ export async function doSetup(
gitRepositoryLink,
webApp?.id,
rootDir,
runtime,
);
createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);

Expand Down Expand Up @@ -352,6 +371,7 @@ export async function createBackend(
repository: GitRepositoryLink | undefined,
webAppId: string | undefined,
rootDir = "/",
runtime?: string,
): Promise<Backend> {
const defaultServiceAccount = defaultComputeServiceAccountEmail(projectId);
const backendReqBody: Omit<Backend, BackendOutputOnlyFields> = {
Expand All @@ -365,6 +385,7 @@ export async function createBackend(
labels: deploymentTool.labels(),
serviceAccount: serviceAccount || defaultServiceAccount,
appId: webAppId,
runtime: runtime ? { value: runtime } : undefined,
};

async function createBackendAndPoll(): Promise<apphosting.Backend> {
Expand Down
2 changes: 2 additions & 0 deletions src/commands/apphosting-backends-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const command = new Command("apphosting:backends:create")
"specify the primary region for the backend. Required with --non-interactive.",
)
.option("--root-dir <rootDir>", "specify the root directory for the backend.")
.option("--runtime <runtime>", "specify the runtime for the backend (e.g., nodejs, nodejs22)")
.before(requireAuth)
.before(ensureApiEnabled)
.before(requireTosAcceptance(APPHOSTING_TOS_ID))
Expand All @@ -45,5 +46,6 @@ export const command = new Command("apphosting:backends:create")
options.serviceAccount as string | undefined,
options.primaryRegion as string | undefined,
options.rootDir as string | undefined,
options.runtime as string | undefined,
);
});
5 changes: 5 additions & 0 deletions src/gcp/apphosting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ interface Codebase {
*/
export type ServingLocality = "GLOBAL_ACCESS" | "REGIONAL_STRICT";

export interface Runtime {
value: string;
}

/** A Backend, the primary resource of Frameworks. */
export interface Backend {
name: string;
Expand All @@ -43,6 +47,7 @@ export interface Backend {
serviceAccount?: string;
appId?: string;
managedResources?: ManagedResource[];
runtime?: Runtime;
}

export interface ManagedResource {
Expand Down
Loading