diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a9de9e9842..bc28d5dc4e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Added `*_EMULATOR_VERSION` env variables to allow overriding specific versions of downloadable emulators - Updated the functions.config deprecation notice from March 2026 to March 2027 (#9941) - Detects when App Hosting fails to deploy, returning an error. (#8866) diff --git a/src/downloadUtils.ts b/src/downloadUtils.ts index 99e63eded9d..9e311108785 100644 --- a/src/downloadUtils.ts +++ b/src/downloadUtils.ts @@ -27,7 +27,9 @@ export async function downloadToTmp(remoteUrl: string, auth: boolean = false): P resolveOnHTTPError: true, }); if (res.status !== 200) { - throw new FirebaseError(`download failed, status ${res.status}: ${await res.response.text()}`); + throw new FirebaseError(`download failed, status ${res.status}: ${await res.response.text()}`, { + status: res.status, + }); } const total = parseInt(res.response.headers.get("content-length") || "0", 10); diff --git a/src/emulator/download.ts b/src/emulator/download.ts index 76bd0a26008..062f827a930 100644 --- a/src/emulator/download.ts +++ b/src/emulator/download.ts @@ -22,6 +22,14 @@ export async function downloadEmulator(name: DownloadableEmulators): Promise { emulatorUpdateDetails.dataconnect.darwin_arm64.remoteUrl, ); }); + + it("should override emulator version when PUBSUB_EMULATOR_VERSION is set", () => { + const fakeVersion = "1.2.3"; + sandbox.stub(process, "env").value({ + ...process.env, + PUBSUB_EMULATOR_VERSION: fakeVersion, + }); + + const pubsubEmulatorDetails = downloadableEmulators.getDownloadDetails(Emulators.PUBSUB); + expect(pubsubEmulatorDetails.version).to.equal(fakeVersion); + expect(pubsubEmulatorDetails.downloadPath).to.contain(fakeVersion); + expect(pubsubEmulatorDetails.opts.remoteUrl).to.contain(fakeVersion); + expect(pubsubEmulatorDetails.opts.skipChecksumAndSize).to.be.true; + + expect(downloadableEmulators.emulatorVersionOverride(Emulators.FIRESTORE)).to.be.undefined; + expect(downloadableEmulators.emulatorVersionOverride(Emulators.DATABASE)).to.be.undefined; + expect(downloadableEmulators.emulatorVersionOverride(Emulators.PUBSUB)).to.equal(fakeVersion); + }); }); diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 68a9f3925b8..d459e4b8b8d 100755 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -66,9 +66,11 @@ function generateDownloadDetails(emulator: DownloadableEmulators): EmulatorDownl ? EMULATOR_UPDATE_DETAILS.dataconnect.win32 : EMULATOR_UPDATE_DETAILS.dataconnect.linux; + let details: EmulatorDownloadDetails; + const overrideVersion = emulatorVersionOverride(emulator); switch (emulator) { case "database": - return { + details = { downloadPath: path.join( CACHE_DIR, EMULATOR_UPDATE_DETAILS.database.downloadPathRelativeToCacheDir, @@ -80,8 +82,9 @@ function generateDownloadDetails(emulator: DownloadableEmulators): EmulatorDownl namePrefix: "firebase-database-emulator", }, }; + break; case "firestore": - return { + details = { downloadPath: path.join( CACHE_DIR, EMULATOR_UPDATE_DETAILS.firestore.downloadPathRelativeToCacheDir, @@ -93,8 +96,9 @@ function generateDownloadDetails(emulator: DownloadableEmulators): EmulatorDownl namePrefix: "cloud-firestore-emulator", }, }; + break; case "storage": - return { + details = { downloadPath: path.join( CACHE_DIR, EMULATOR_UPDATE_DETAILS.storage.downloadPathRelativeToCacheDir, @@ -106,8 +110,9 @@ function generateDownloadDetails(emulator: DownloadableEmulators): EmulatorDownl namePrefix: "cloud-storage-rules-emulator", }, }; + break; case "ui": - return { + details = { version: emulatorUiDetails.version, downloadPath: path.join(CACHE_DIR, emulatorUiDetails.downloadPathRelativeToCacheDir), unzipDir: path.join(CACHE_DIR, `ui-v${emulatorUiDetails.version}`), @@ -120,8 +125,9 @@ function generateDownloadDetails(emulator: DownloadableEmulators): EmulatorDownl namePrefix: "ui", }, }; + break; case "pubsub": - return { + details = { downloadPath: path.join( CACHE_DIR, EMULATOR_UPDATE_DETAILS.pubsub.downloadPathRelativeToCacheDir, @@ -138,8 +144,9 @@ function generateDownloadDetails(emulator: DownloadableEmulators): EmulatorDownl namePrefix: "pubsub-emulator", }, }; + break; case "dataconnect": - return { + details = { downloadPath: path.join(CACHE_DIR, dataconnectDetails.downloadPathRelativeToCacheDir), version: dataconnectDetails.version, binaryPath: path.join(CACHE_DIR, dataconnectDetails.downloadPathRelativeToCacheDir), @@ -151,9 +158,29 @@ function generateDownloadDetails(emulator: DownloadableEmulators): EmulatorDownl auth: false, }, }; + break; default: throw new Error(`Invalid downloadable emulator: ${emulator}`); } + + if (overrideVersion && overrideVersion !== details.version) { + const oldVersion = details.version; + const replaceVersion = (s: string) => s.split(oldVersion).join(overrideVersion); + + details.version = overrideVersion; + details.downloadPath = replaceVersion(details.downloadPath); + if (details.unzipDir) { + details.unzipDir = replaceVersion(details.unzipDir); + } + if (details.binaryPath) { + details.binaryPath = replaceVersion(details.binaryPath); + } + + details.opts.remoteUrl = replaceVersion(details.opts.remoteUrl); + details.opts.skipChecksumAndSize = true; + } + + return details; } const EmulatorDetails: { [s in DownloadableEmulators]: DownloadableEmulatorDetails } = { @@ -631,3 +658,7 @@ export function isIncomaptibleArchError(err: unknown): boolean { process.platform === "darwin" ); } + +export function emulatorVersionOverride(emulator: DownloadableEmulators) { + return process.env[`${emulator.toUpperCase()}_EMULATOR_VERSION`]; +}