Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added support for the nodejs26 (in preview) runtime for Cloud Functions. (#11393)
3 changes: 3 additions & 0 deletions schema/firebase-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"nodejs20",
"nodejs22",
"nodejs24",
"nodejs26",
"python310",
"python311",
"python312",
Expand Down Expand Up @@ -1001,6 +1002,7 @@
"nodejs20",
"nodejs22",
"nodejs24",
"nodejs26",
"python310",
"python311",
"python312",
Expand Down Expand Up @@ -1089,6 +1091,7 @@
"nodejs20",
"nodejs22",
"nodejs24",
"nodejs26",
"python310",
"python311",
"python312",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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" } });

Expand Down
42 changes: 28 additions & 14 deletions src/deploy/functions/runtimes/supported/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,21 @@ export function latest<T extends Language>(
language: T,
runtimes: Runtime[] = Object.keys(RUNTIMES) as Runtime[],
): RuntimeOf<T> & 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(
Expand All @@ -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.`,
);
}
Comment on lines +67 to +79

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

We can improve the maintainability and readability of this guard check by destructuring the friendly property from RUNTIMES[runtime] alongside the other properties. Additionally, we can combine the beta and experimental status checks to reduce duplication of the warning message string.

  const { friendly, deprecationDate, decommissionDate, status } = RUNTIMES[runtime];

  if (status === "beta" || status === "experimental") {
    const label = status === "beta" ? "is in Preview" : "is experimental";
    utils.logLabeledWarning(
      "functions",
      `Runtime ${friendly} ${label}. Pre-release runtimes may not be suitable for production workloads.`,
    );
  }
References
  1. Reduce nesting and simplify branching where possible to keep code clean and maintainable. (link)


const decommission = new Date(decommissionDate);
if (now >= decommission) {
Expand Down
24 changes: 24 additions & 0 deletions src/deploy/functions/runtimes/supported/supported.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
Expand All @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions src/deploy/functions/runtimes/supported/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading