diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..03959eb4a32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Added support for the nodejs26 (in preview) runtime for Cloud Functions. (#11393) \ No newline at end of file diff --git a/schema/firebase-config.json b/schema/firebase-config.json index 1338124e049..a5a357b2466 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -9,6 +9,7 @@ "nodejs20", "nodejs22", "nodejs24", + "nodejs26", "python310", "python311", "python312", @@ -1001,6 +1002,7 @@ "nodejs20", "nodejs22", "nodejs24", + "nodejs26", "python310", "python311", "python312", @@ -1089,6 +1091,7 @@ "nodejs20", "nodejs22", "nodejs24", + "nodejs26", "python310", "python311", "python312", diff --git a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.spec.ts b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.spec.ts index 737b7737956..e9c05f21bd7 100644 --- a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.spec.ts +++ b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.spec.ts @@ -36,6 +36,10 @@ describe("getRuntimeChoice", () => { it("should return node 16 if runtime field is set to node 16", () => { expect(runtime.getRuntimeChoice("path/to/source", "nodejs16")).to.equal("nodejs16"); }); + + it("should return node 26 if runtime field is set to node 26", () => { + expect(runtime.getRuntimeChoice("path/to/source", "nodejs26")).to.equal("nodejs26"); + }); }); context("when the runtime is not set in firebase.json", () => { @@ -63,6 +67,12 @@ describe("getRuntimeChoice", () => { expect(runtime.getRuntimeChoice("path/to/source")).to.equal("nodejs16"); }); + it("should return node 26 if engines field is set to node 26", () => { + cjsonStub.returns({ engines: { node: "26" } }); + + expect(runtime.getRuntimeChoice("path/to/source")).to.equal("nodejs26"); + }); + it("should print warning when firebase-functions version is below 2.0.0", () => { cjsonStub.returns({ engines: { node: "16" } }); diff --git a/src/deploy/functions/runtimes/supported/index.ts b/src/deploy/functions/runtimes/supported/index.ts index f5cbbb8fb3b..d8b52ba284b 100644 --- a/src/deploy/functions/runtimes/supported/index.ts +++ b/src/deploy/functions/runtimes/supported/index.ts @@ -24,19 +24,21 @@ export function latest( language: T, runtimes: Runtime[] = Object.keys(RUNTIMES) as Runtime[], ): RuntimeOf & Runtime { - const sorted = runtimes - .filter((s) => runtimeIsLanguage(s, language)) - // node8 is less than node20 - .sort((left, right) => { - const leftVersion = +left.substring(language.length); - const rightVersion = +right.substring(language.length); - if (isNaN(leftVersion) || isNaN(rightVersion)) { - throw new FirebaseError("Internal error. Runtime or language names are malformed", { - exit: 1, - }); - } - return leftVersion - rightVersion; - }); + const filtered = runtimes.filter((s) => runtimeIsLanguage(s, language)); + // Prefer GA runtimes over preview/experimental runtimes so preview runtimes + // are not chosen as the default. If no GA runtimes exist (e.g. Dart), fall back. + const gaOnly = filtered.filter((s) => RUNTIMES[s]?.status === "GA"); + const candidates = gaOnly.length > 0 ? gaOnly : filtered; + const sorted = candidates.sort((left, right) => { + const leftVersion = +left.substring(language.length); + const rightVersion = +right.substring(language.length); + if (isNaN(leftVersion) || isNaN(rightVersion)) { + throw new FirebaseError("Internal error. Runtime or language names are malformed", { + exit: 1, + }); + } + return leftVersion - rightVersion; + }); const latest = utils.last(sorted); if (!latest) { throw new FirebaseError( @@ -62,7 +64,19 @@ export function isDecommissioned(runtime: Runtime, now: Date = new Date()): bool * increase testability. */ export function guardVersionSupport(runtime: Runtime, now: Date = new Date()): void { - const { deprecationDate, decommissionDate } = RUNTIMES[runtime]; + const { deprecationDate, decommissionDate, status } = RUNTIMES[runtime]; + + if (status === "beta") { + utils.logLabeledWarning( + "functions", + `Runtime ${RUNTIMES[runtime].friendly} is in Preview. Pre-release runtimes may not be suitable for production workloads.`, + ); + } else if (status === "experimental") { + utils.logLabeledWarning( + "functions", + `Runtime ${RUNTIMES[runtime].friendly} is experimental. Pre-release runtimes may not be suitable for production workloads.`, + ); + } const decommission = new Date(decommissionDate); if (now >= decommission) { diff --git a/src/deploy/functions/runtimes/supported/supported.spec.ts b/src/deploy/functions/runtimes/supported/supported.spec.ts index a099b406fe9..48b40408cec 100644 --- a/src/deploy/functions/runtimes/supported/supported.spec.ts +++ b/src/deploy/functions/runtimes/supported/supported.spec.ts @@ -9,6 +9,14 @@ describe("supported runtimes", () => { expect(supported.latest("nodejs")).to.not.equal("nodejs8"); }); + it("does not choose a preview runtime as latest when GA runtimes exist", () => { + expect(supported.latest("nodejs")).to.equal("nodejs24"); + }); + + it("falls back to experimental/beta runtime when no GA runtime exists", () => { + expect(supported.latest("dart")).to.equal("dart3"); + }); + it("identifies decommissioned runtimes", () => { expect(supported.isDecommissioned("nodejs8")).to.be.true; }); @@ -33,6 +41,22 @@ describe("supported runtimes", () => { logLabeledWarning.restore(); }); + it("warns for a preview runtime", () => { + supported.guardVersionSupport("nodejs26"); + expect(logLabeledWarning).to.have.been.calledWith( + "functions", + "Runtime Node.js 26 is in Preview. Pre-release runtimes may not be suitable for production workloads.", + ); + }); + + it("warns for an experimental runtime", () => { + supported.guardVersionSupport("dart3"); + expect(logLabeledWarning).to.have.been.calledWith( + "functions", + "Runtime Dart 3 is experimental. Pre-release runtimes may not be suitable for production workloads.", + ); + }); + it("throws an error for decommissioned runtimes", () => { expect(() => supported.guardVersionSupport("nodejs8")).to.throw( FirebaseError, diff --git a/src/deploy/functions/runtimes/supported/types.ts b/src/deploy/functions/runtimes/supported/types.ts index eee207d4f71..c77e6cad6e4 100644 --- a/src/deploy/functions/runtimes/supported/types.ts +++ b/src/deploy/functions/runtimes/supported/types.ts @@ -95,6 +95,13 @@ export const RUNTIMES = runtimes({ deprecationDate: "2028-04-30", decommissionDate: "2028-10-31", }, + // Note: Node.js 26 is currently in Preview on Cloud Run functions. + nodejs26: { + friendly: "Node.js 26", + status: "beta", + deprecationDate: "2029-04-30", + decommissionDate: "2029-10-31", + }, python310: { friendly: "Python 3.10", status: "GA",