From c9d201161219a80d3e771f61e4e404146cb4b11f Mon Sep 17 00:00:00 2001 From: demolaf Date: Tue, 2 Jun 2026 23:16:33 +0100 Subject: [PATCH 1/3] fix(hosting): route "run" rewrites to local functions emulator --- src/hosting/cloudRunProxy.spec.ts | 58 ++++++++++++++++++++++++++++++- src/hosting/cloudRunProxy.ts | 16 +++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/hosting/cloudRunProxy.spec.ts b/src/hosting/cloudRunProxy.spec.ts index 75e21819372..0af6f591326 100644 --- a/src/hosting/cloudRunProxy.spec.ts +++ b/src/hosting/cloudRunProxy.spec.ts @@ -1,3 +1,4 @@ +import { cloneDeep } from "lodash"; import { expect } from "chai"; import * as express from "express"; import * as nock from "nock"; @@ -6,16 +7,21 @@ import * as supertest from "supertest"; import { cloudRunApiOrigin } from "../api"; import cloudRunProxy, { CloudRunProxyOptions, CloudRunProxyRewrite } from "./cloudRunProxy"; +import { EmulatorRegistry } from "../emulator/registry"; +import { Emulators } from "../emulator/types"; +import { FakeEmulator } from "../emulator/testing/fakeEmulator"; describe("cloudRunProxy", () => { const fakeOptions: CloudRunProxyOptions = { project: "project-foo", + targets: [], }; const fakeRewrite: CloudRunProxyRewrite = { run: { serviceId: "helloworld" } }; const cloudRunServiceOrigin = "https://helloworld-hash-uc.a.run.app"; - afterEach(() => { + afterEach(async () => { nock.cleanAll(); + await EmulatorRegistry.stopAll(); }); it("should error when not provided a valid Cloud Run service ID", async () => { @@ -330,4 +336,54 @@ describe("cloudRunProxy", () => { expect(spyMw.calledOnce).to.be.true; }); }); + + it("should route to the local functions emulator when targets includes 'functions'", async () => { + const fakeFunctionsEmulator = new FakeEmulator(Emulators.FUNCTIONS, [ + { address: "127.0.0.1", family: "IPv4", port: 7778 }, + ]); + await EmulatorRegistry.start(fakeFunctionsEmulator); + + nock("http://127.0.0.1:7778") + .get("/project-foo/us-central1/helloworld/") + .reply(200, "local version"); + + const options = cloneDeep(fakeOptions); + options.targets = ["functions"]; + + const mwGenerator = cloudRunProxy(options); + const mw = await mwGenerator(fakeRewrite); + const spyMw = sinon.spy(mw); + + return supertest(spyMw) + .get("/") + .expect(200, "local version") + .then(() => { + expect(spyMw.calledOnce).to.be.true; + }); + }); + + it("should route to the local functions emulator in another region", async () => { + const fakeFunctionsEmulator = new FakeEmulator(Emulators.FUNCTIONS, [ + { address: "127.0.0.1", family: "IPv4", port: 7778 }, + ]); + await EmulatorRegistry.start(fakeFunctionsEmulator); + + nock("http://127.0.0.1:7778") + .get("/project-foo/asia-southeast1/helloworld/") + .reply(200, "local version"); + + const options = cloneDeep(fakeOptions); + options.targets = ["functions"]; + + const mwGenerator = cloudRunProxy(options); + const mw = await mwGenerator({ run: { serviceId: "helloworld", region: "asia-southeast1" } }); + const spyMw = sinon.spy(mw); + + return supertest(spyMw) + .get("/") + .expect(200, "local version") + .then(() => { + expect(spyMw.calledOnce).to.be.true; + }); + }); }); diff --git a/src/hosting/cloudRunProxy.ts b/src/hosting/cloudRunProxy.ts index c0bbb7ccb2b..cfcf67a254b 100644 --- a/src/hosting/cloudRunProxy.ts +++ b/src/hosting/cloudRunProxy.ts @@ -1,3 +1,4 @@ +import { includes } from "lodash"; import { RequestHandler } from "express"; import { Client } from "../apiv2"; @@ -6,9 +7,13 @@ import { errorRequestHandler, proxyRequestHandler } from "./proxy"; import { FirebaseError } from "../error"; import { logger } from "../logger"; import { needProjectId } from "../projectUtils"; +import { EmulatorRegistry } from "../emulator/registry"; +import { Emulators } from "../emulator/types"; +import { FunctionsEmulator } from "../emulator/functionsEmulator"; export interface CloudRunProxyOptions { project?: string; + targets?: string[]; } export interface CloudRunProxyRewrite { @@ -70,6 +75,17 @@ export default function ( logger.info(`[hosting] Cloud Run rewrite ${JSON.stringify(rewrite)} triggered`); const textIdentifier = `Cloud Run service "${rewrite.run.serviceId}" for region "${rewrite.run.region}"`; + + if (includes(options.targets, "functions") && EmulatorRegistry.isRunning(Emulators.FUNCTIONS)) { + const projectId = needProjectId(options); + const url = FunctionsEmulator.getHttpFunctionUrl( + projectId, + rewrite.run.serviceId, + rewrite.run.region, + ); + return proxyRequestHandler(url, `local ${textIdentifier}`); + } + return getCloudRunUrl(rewrite, needProjectId(options)) .then((url) => proxyRequestHandler(url, textIdentifier)) .catch(errorRequestHandler); From 5af507753587613ff2585e4cd188399bf8b694bd Mon Sep 17 00:00:00 2001 From: demolaf Date: Tue, 2 Jun 2026 23:25:13 +0100 Subject: [PATCH 2/3] updates --- src/hosting/cloudRunProxy.spec.ts | 7 ++----- src/hosting/cloudRunProxy.ts | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/hosting/cloudRunProxy.spec.ts b/src/hosting/cloudRunProxy.spec.ts index 0af6f591326..aa88a8bc8bc 100644 --- a/src/hosting/cloudRunProxy.spec.ts +++ b/src/hosting/cloudRunProxy.spec.ts @@ -1,4 +1,3 @@ -import { cloneDeep } from "lodash"; import { expect } from "chai"; import * as express from "express"; import * as nock from "nock"; @@ -347,8 +346,7 @@ describe("cloudRunProxy", () => { .get("/project-foo/us-central1/helloworld/") .reply(200, "local version"); - const options = cloneDeep(fakeOptions); - options.targets = ["functions"]; + const options: CloudRunProxyOptions = { ...fakeOptions, targets: ["functions"] }; const mwGenerator = cloudRunProxy(options); const mw = await mwGenerator(fakeRewrite); @@ -372,8 +370,7 @@ describe("cloudRunProxy", () => { .get("/project-foo/asia-southeast1/helloworld/") .reply(200, "local version"); - const options = cloneDeep(fakeOptions); - options.targets = ["functions"]; + const options: CloudRunProxyOptions = { ...fakeOptions, targets: ["functions"] }; const mwGenerator = cloudRunProxy(options); const mw = await mwGenerator({ run: { serviceId: "helloworld", region: "asia-southeast1" } }); diff --git a/src/hosting/cloudRunProxy.ts b/src/hosting/cloudRunProxy.ts index cfcf67a254b..dde3aa0d162 100644 --- a/src/hosting/cloudRunProxy.ts +++ b/src/hosting/cloudRunProxy.ts @@ -1,4 +1,3 @@ -import { includes } from "lodash"; import { RequestHandler } from "express"; import { Client } from "../apiv2"; @@ -76,7 +75,7 @@ export default function ( const textIdentifier = `Cloud Run service "${rewrite.run.serviceId}" for region "${rewrite.run.region}"`; - if (includes(options.targets, "functions") && EmulatorRegistry.isRunning(Emulators.FUNCTIONS)) { + if (options.targets?.includes("functions") && EmulatorRegistry.isRunning(Emulators.FUNCTIONS)) { const projectId = needProjectId(options); const url = FunctionsEmulator.getHttpFunctionUrl( projectId, From 61613859eecd31a9c570a6807f02465e33139fe3 Mon Sep 17 00:00:00 2001 From: demolaf Date: Thu, 11 Jun 2026 18:54:06 +0100 Subject: [PATCH 3/3] chore: update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e01532047..25bff018be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed an issue where Cloud Run rewrites in the Hosting emulator would always hit the live Cloud Run API instead of routing to the local functions emulator. (#10588) - Updated the Firebase Data Connect local toolkit to v3.4.11, which includes the following changes: - [changed] Updated the Golang dependency version to 1.25.11. - Fixed issue where `apptesting:execute` command rejects documented `--test-username`, `--test-password`, and `--test-password-file` options.