feat(core,cli): add node-24 and node-26 runtimes, deprecate experimental aliases (#4337)

## Summary

Adds `node-24` and `node-26` as first-class `runtime` options in
`trigger.config.ts`. Previously these Node versions were only reachable
via the `experimental-node-24` / `experimental-node-26` names.

Those experimental names are now **deprecated aliases**: they still
resolve to `node-24` / `node-26` for backwards compatibility, but
loading a config that uses them prints a deprecation warning pointing at
the new name.

```ts
export default defineConfig({
  runtime: "node-24",
  project: "<your-project-ref>",
});
```

## Details

- `ConfigRuntime` (the public config schema) now accepts `node-24` and
`node-26` directly; the internal `BuildRuntime` already supported them,
so base images and the deploy path are unchanged.
- `resolveBuildRuntime` passes the new names straight through and keeps
mapping the experimental aliases to their replacements.
- Renamed the runtime helper from `isExperimentalConfigRuntime` to
`isDeprecatedConfigRuntime` and added `deprecatedRuntimeReplacement` so
the CLI can name the replacement in its warning.
- Docs snippet updated to list the new versions and flag the deprecated
names.
This commit is contained in:
Chris Arderne
2026-07-22 14:10:27 +01:00
committed by GitHub
parent 14fa90672b
commit 55a3bf2858
7 changed files with 70 additions and 28 deletions
+15
View File
@@ -0,0 +1,15 @@
---
"@trigger.dev/core": patch
"trigger.dev": patch
---
Add `node-24` and `node-26` as supported `runtime` options in `trigger.config.ts`. The `experimental-node-24` and `experimental-node-26` names are now deprecated aliases and emit a deprecation warning; switch to `node-24` / `node-26` instead.
```ts
import { defineConfig } from "@trigger.dev/sdk";
export default defineConfig({
runtime: "node-24",
project: "<your-project-ref>",
});
```
+4 -8
View File
@@ -1,13 +1,9 @@
Trigger.dev runs your tasks on specific Node.js versions:
### v3
- Node.js `21.7.3`
### v4
- Node.js `21.7.3` (default)
- Node.js `22.16.0` (`node-22`)
- Node.js `24.18.0` (`node-24`)
- Node.js `26.4.0` (`node-26`)
- Bun `1.3.3` (`bun`)
You can change the runtime by setting the `runtime` field in your `trigger.config.ts` file.
@@ -16,8 +12,8 @@ You can change the runtime by setting the `runtime` field in your `trigger.confi
import { defineConfig } from "@trigger.dev/sdk";
export default defineConfig({
// "node", "node-22" or "bun"
runtime: "node-22",
// "node", "node-22", "node-24", "node-26" or "bun"
runtime: "node-24",
project: "<your-project-ref>",
});
```
+5 -1
View File
@@ -33,6 +33,10 @@ async function createProject(runtime?: string) {
describe("loadConfig runtime", () => {
it.each([
["node", "node"],
["node-22", "node-22"],
["node-24", "node-24"],
["node-26", "node-26"],
["experimental-node-24", "node-24"],
["experimental-node-26", "node-26"],
] as const)("normalizes %s before returning the resolved config", async (runtime, expected) => {
@@ -47,7 +51,7 @@ describe("loadConfig runtime", () => {
await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({ runtime: "node" });
});
it.each(["node-24", "node-26", "node-23"])(
it.each(["node-23"])(
"rejects unsupported public runtime %s while loading config",
async (runtime) => {
const cwd = await createProject(runtime);
+6 -3
View File
@@ -8,7 +8,8 @@ import type {
import type { ResolvedConfig } from "@trigger.dev/core/v3/build";
import {
DEFAULT_RUNTIME,
isExperimentalConfigRuntime,
deprecatedRuntimeReplacement,
isDeprecatedConfigRuntime,
resolveBuildRuntime,
} from "@trigger.dev/core/v3/build";
import * as c12 from "c12";
@@ -184,9 +185,11 @@ async function resolveConfig(
const configuredRuntime = overrides?.runtime ?? config.runtime ?? defaultRuntime;
const runtime = resolveBuildRuntime(configuredRuntime);
if (warn && isExperimentalConfigRuntime(configuredRuntime)) {
if (warn && isDeprecatedConfigRuntime(configuredRuntime)) {
prettyWarning(
`The "${configuredRuntime}" runtime is experimental and may change before general availability.`
`The "${configuredRuntime}" runtime is deprecated. Use "${deprecatedRuntimeReplacement(
configuredRuntime
)}" instead.`
);
}
+19 -12
View File
@@ -1,31 +1,38 @@
import { describe, expect, it } from "vitest";
import { BuildManifest, BuildRuntime, ConfigRuntime, WorkerManifest } from "../schemas/build.js";
import { isExperimentalConfigRuntime, resolveBuildRuntime } from "./runtime.js";
import { isDeprecatedConfigRuntime, resolveBuildRuntime } from "./runtime.js";
describe("runtime configuration", () => {
it.each(["node", "node-22", "experimental-node-24", "experimental-node-26", "bun"] as const)(
"accepts %s as a public config runtime",
(runtime) => {
expect(ConfigRuntime.parse(runtime)).toBe(runtime);
}
);
it.each([
"node",
"node-22",
"node-24",
"node-26",
"experimental-node-24",
"experimental-node-26",
"bun",
] as const)("accepts %s as a public config runtime", (runtime) => {
expect(ConfigRuntime.parse(runtime)).toBe(runtime);
});
it.each([
["experimental-node-24", "node-24"],
["experimental-node-26", "node-26"],
["node", "node"],
["node-22", "node-22"],
["node-24", "node-24"],
["node-26", "node-26"],
["bun", "bun"],
] as const)("normalizes %s to %s", (runtime, expected) => {
expect(resolveBuildRuntime(runtime)).toBe(expected);
});
it.each(["node-24", "node-26"] as const)(
"keeps internal runtime %s out of the public config schema",
"accepts internal runtime %s in both public and internal schemas",
(runtime) => {
expect(ConfigRuntime.safeParse(runtime).success).toBe(false);
expect(ConfigRuntime.safeParse(runtime).success).toBe(true);
expect(BuildRuntime.safeParse(runtime).success).toBe(true);
expect(() => resolveBuildRuntime(runtime)).toThrowError(/Unsupported runtime/);
expect(resolveBuildRuntime(runtime)).toBe(runtime);
}
);
@@ -36,12 +43,12 @@ describe("runtime configuration", () => {
});
it.each(["experimental-node-24", "experimental-node-26"] as const)(
"keeps %s out of internal runtime schemas",
"treats %s as a deprecated alias kept out of internal runtime schemas",
(runtime) => {
expect(BuildRuntime.safeParse(runtime).success).toBe(false);
expect(BuildManifest.shape.runtime.safeParse(runtime).success).toBe(false);
expect(WorkerManifest.shape.runtime.safeParse(runtime).success).toBe(false);
expect(isExperimentalConfigRuntime(runtime)).toBe(true);
expect(isDeprecatedConfigRuntime(runtime)).toBe(true);
}
);
});
+18 -4
View File
@@ -6,14 +6,28 @@ import { homedir } from "node:os";
export const DEFAULT_RUNTIME = "node" satisfies BuildRuntime;
export type ExperimentalConfigRuntime = "experimental-node-24" | "experimental-node-26";
export type DeprecatedConfigRuntime = "experimental-node-24" | "experimental-node-26";
export function isExperimentalConfigRuntime(
runtime: unknown
): runtime is ExperimentalConfigRuntime {
export function isDeprecatedConfigRuntime(runtime: unknown): runtime is DeprecatedConfigRuntime {
return runtime === "experimental-node-24" || runtime === "experimental-node-26";
}
/** Maps a deprecated runtime alias to the runtime that should be used instead. */
export function deprecatedRuntimeReplacement(runtime: DeprecatedConfigRuntime): BuildRuntime {
switch (runtime) {
case "experimental-node-24":
return "node-24";
case "experimental-node-26":
return "node-26";
}
}
/** @deprecated Renamed to {@link DeprecatedConfigRuntime}. */
export type ExperimentalConfigRuntime = DeprecatedConfigRuntime;
/** @deprecated Renamed to {@link isDeprecatedConfigRuntime}. */
export const isExperimentalConfigRuntime = isDeprecatedConfigRuntime;
export function resolveBuildRuntime(runtime: unknown): BuildRuntime {
const parsedRuntime = ConfigRuntime.safeParse(runtime);
+3
View File
@@ -16,6 +16,9 @@ export type BuildTarget = z.infer<typeof BuildTarget>;
export const ConfigRuntime = z.enum([
"node",
"node-22",
"node-24",
"node-26",
// Deprecated aliases, kept for backwards compatibility. Use "node-24"/"node-26" instead.
"experimental-node-24",
"experimental-node-26",
"bun",