From 0f9b6c4c72bc9ec03d114e9c871821e03421e240 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 17 Aug 2026 11:20:51 +0100 Subject: [PATCH 1/5] feat(cli,webapp): default new projects to node-24 --- .changeset/node-24-project-default.md | 5 ++++ .../default-new-project-runtime-node-24.md | 6 +++++ apps/webapp/app/models/project.server.ts | 1 + .../app/models/runtimeEnvironment.server.ts | 2 ++ .../api.v1.projects.$projectRef.$env.ts | 3 ++- .../app/routes/api.v1.projects.$projectRef.ts | 3 ++- .../services/initializeDeployment.server.ts | 2 +- .../projectEnvironmentCredentialRoute.test.ts | 2 ++ .../migration.sql | 2 ++ .../database/prisma/schema.prisma | 27 ++++++++++--------- packages/cli-v3/src/commands/deploy.ts | 11 +++++++- packages/cli-v3/src/commands/init.ts | 6 ++--- packages/cli-v3/src/config.test.ts | 20 +++++++++++++- packages/cli-v3/src/config.ts | 17 +++++++----- packages/cli-v3/src/utilities/session.ts | 1 + packages/core/src/v3/auth/environment.ts | 1 + packages/core/src/v3/schemas/api.ts | 3 +++ 17 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 .changeset/node-24-project-default.md create mode 100644 .server-changes/default-new-project-runtime-node-24.md create mode 100644 internal-packages/database/prisma/migrations/20260817111521_add_project_default_runtime/migration.sql diff --git a/.changeset/node-24-project-default.md b/.changeset/node-24-project-default.md new file mode 100644 index 00000000000..ad43e554c74 --- /dev/null +++ b/.changeset/node-24-project-default.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": patch +--- + +New projects created with `trigger init` use Node.js 24 by default. Deployments whose config omits `runtime` now use their project's configured default runtime. diff --git a/.server-changes/default-new-project-runtime-node-24.md b/.server-changes/default-new-project-runtime-node-24.md new file mode 100644 index 00000000000..a75b4bda0e8 --- /dev/null +++ b/.server-changes/default-new-project-runtime-node-24.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: feature +--- + +New projects use Node.js 24 when their deployment config does not specify a runtime. diff --git a/apps/webapp/app/models/project.server.ts b/apps/webapp/app/models/project.server.ts index 2ed317fe879..f309ae3212d 100644 --- a/apps/webapp/app/models/project.server.ts +++ b/apps/webapp/app/models/project.server.ts @@ -113,6 +113,7 @@ export async function createProject( // for historical rows; the V1->V2 upgrade guards on worker-register / deploy // stay in place to migrate existing legacy projects. engine: "V2", + defaultRuntime: "node-24", onboardingData, }, include: { diff --git a/apps/webapp/app/models/runtimeEnvironment.server.ts b/apps/webapp/app/models/runtimeEnvironment.server.ts index 8dc5a68b63f..e71d6985231 100644 --- a/apps/webapp/app/models/runtimeEnvironment.server.ts +++ b/apps/webapp/app/models/runtimeEnvironment.server.ts @@ -6,6 +6,7 @@ import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver. import { logger } from "~/services/logger.server"; import { getUsername } from "~/utils/username"; import { hashApiKey } from "~/utils/apiKeys"; +import { BuildRuntime } from "@trigger.dev/core/v3"; import { isAdditionalApiKey } from "@trigger.dev/core/v3/apiKeys"; import { isDefaultDevBranch, sanitizeBranchName } from "@trigger.dev/core/v3/utils/gitBranch"; import { scopesGrantFullAccess } from "@trigger.dev/rbac"; @@ -77,6 +78,7 @@ export function toAuthenticated( defaultWorkerGroupId: env.project.defaultWorkerGroupId, organizationId: env.project.organizationId, builderProjectId: env.project.builderProjectId, + defaultRuntime: BuildRuntime.nullable().parse(env.project.defaultRuntime), }, organization: { id: env.organization.id, diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts index c7090ce4721..c6a3e61875e 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts @@ -1,5 +1,5 @@ import { json, type LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { type GetProjectEnvResponse } from "@trigger.dev/core/v3"; +import { BuildRuntime, type GetProjectEnvResponse } from "@trigger.dev/core/v3"; import { z } from "zod"; import { env as processEnv } from "~/env.server"; import { @@ -65,6 +65,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { name: environment.project.name, apiUrl: processEnv.API_ORIGIN ?? processEnv.APP_ORIGIN, projectId: environment.project.id, + defaultRuntime: BuildRuntime.nullable().parse(environment.project.defaultRuntime ?? null), }; return json(result); diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.ts index c1fa0acc917..4f3b9b70647 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.ts @@ -1,5 +1,5 @@ import { json } from "@remix-run/server-runtime"; -import type { GetProjectResponseBody } from "@trigger.dev/core/v3"; +import { BuildRuntime, type GetProjectResponseBody } from "@trigger.dev/core/v3"; import { z } from "zod"; import { prisma } from "~/db.server"; import { DeleteProjectService } from "~/services/deleteProject.server"; @@ -53,6 +53,7 @@ export const loader = createLoaderPATApiRoute( slug: project.slug, createdAt: project.createdAt, defaultRegion: project.defaultWorkerGroup?.name ?? null, + defaultRuntime: BuildRuntime.nullable().parse(project.defaultRuntime ?? null), organization: { id: project.organization.id, title: project.organization.title, diff --git a/apps/webapp/app/v3/services/initializeDeployment.server.ts b/apps/webapp/app/v3/services/initializeDeployment.server.ts index abb99082dd6..fb8ffd259e4 100644 --- a/apps/webapp/app/v3/services/initializeDeployment.server.ts +++ b/apps/webapp/app/v3/services/initializeDeployment.server.ts @@ -253,7 +253,7 @@ export class InitializeDeploymentService extends BaseService { imagePlatform: env.DEPLOY_IMAGE_PLATFORM, git: payload.gitMeta ?? undefined, commitSHA: payload.gitMeta?.commitSha ?? undefined, - runtime: payload.runtime ?? undefined, + runtime: payload.runtime ?? environment.project.defaultRuntime ?? undefined, triggeredVia: payload.triggeredVia ?? undefined, startedAt: initialStatus === "BUILDING" ? new Date() : undefined, }; diff --git a/apps/webapp/test/projectEnvironmentCredentialRoute.test.ts b/apps/webapp/test/projectEnvironmentCredentialRoute.test.ts index 9a24883a85a..e50363c71dc 100644 --- a/apps/webapp/test/projectEnvironmentCredentialRoute.test.ts +++ b/apps/webapp/test/projectEnvironmentCredentialRoute.test.ts @@ -37,6 +37,7 @@ const environment = { project: { id: "proj_123", name: "Example project", + defaultRuntime: "node-24", }, }; @@ -82,6 +83,7 @@ describe("project environment credential response", () => { await expect(responseJson(response)).resolves.toMatchObject({ apiKey: "tr_prod_sk_presented", projectId: "proj_123", + defaultRuntime: "node-24", }); expect(mocks.authorizePatEnvironmentAccess).not.toHaveBeenCalled(); }); diff --git a/internal-packages/database/prisma/migrations/20260817111521_add_project_default_runtime/migration.sql b/internal-packages/database/prisma/migrations/20260817111521_add_project_default_runtime/migration.sql new file mode 100644 index 00000000000..899dd5921a4 --- /dev/null +++ b/internal-packages/database/prisma/migrations/20260817111521_add_project_default_runtime/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "defaultRuntime" TEXT; diff --git a/internal-packages/database/prisma/schema.prisma b/internal-packages/database/prisma/schema.prisma index 44392406aae..9c45d7fa7c0 100644 --- a/internal-packages/database/prisma/schema.prisma +++ b/internal-packages/database/prisma/schema.prisma @@ -474,6 +474,9 @@ model Project { /// Set the first time the CLI `init` command completes against this project. Drives the dev onboarding progress. initializedAt DateTime? + /// Runtime used when a deployment config does not specify one. Null preserves the legacy Node 20 fallback. + defaultRuntime String? + version ProjectVersion @default(V2) engine RunEngineVersion @default(V1) @@ -790,12 +793,12 @@ model WebhookEndpoint { source String // provider tag e.g. "stripe","slack","github" handlerWebhookId String // declared webhook() id (string ref, GOLDEN LAW, no relation) - routingTarget Json // RoutingTarget tagged union ({ type: "task" } | { type: "session" }) - verifierArtifact Json // VerifierArtifact tagged union (config|preset in P1) + routingTarget Json // RoutingTarget tagged union ({ type: "task" } | { type: "session" }) + verifierArtifact Json // VerifierArtifact tagged union (config|preset in P1) filter String? // source filter DSL string (display/round-trip) - filterAst Json? // compiled FilterAst, evaluated at ingest; null = route all - filterAstVersion Int? // re-parse `filter` on a format bump - metadata Json @default("{}") // arbitrary user metadata; flows into the webhook task + filterAst Json? // compiled FilterAst, evaluated at ingest; null = route all + filterAstVersion Int? // re-parse `filter` on a format bump + metadata Json @default("{}") // arbitrary user metadata; flows into the webhook task // who supplies the secret/key; drives the Connect UI (paste vs generate). From the source. secretProvisioning String @default("either") // "provider" | "integrator" | "either" @@ -803,13 +806,13 @@ model WebhookEndpoint { /// SecretReference.key string. Plain String, NO @relation -> no FK to SecretReference. signingSecretKey String? - status WebhookEndpointStatus @default(ACTIVE) + status WebhookEndpointStatus @default(ACTIVE) /// When an operator disabled the endpoint via the dashboard/API. Null means the declarative sync /// owns the status: a redeploy that re-declares a previously-removed (auto-deactivated) webhook /// reactivates it. Non-null means the operator disabled it, so the sync leaves the status alone. manuallyDeactivatedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@unique([runtimeEnvironmentId, handlerWebhookId, endpointTenantId, endpointExternalRef]) // deploy-sync key @@index([runtimeEnvironmentId, source]) @@ -842,8 +845,8 @@ model WebhookDelivery { /// Set from the x-trigger-test ingress header; marks console/test-send deliveries so the list can filter them. isTest Boolean @default(false) - parsedEvent Json? // size-capped snapshot of the verified event (full event lives in ClickHouse) - headers Json? // inbound request headers, surfaced to the webhook task via onEvent({ headers }) + parsedEvent Json? // size-capped snapshot of the verified event (full event lives in ClickHouse) + headers Json? // inbound request headers, surfaced to the webhook task via onEvent({ headers }) rawBodyHash String? // sha256 of raw bytes; cheap P2 replay anchor errorMessage String? filterReason String? // why a FILTERED delivery was not routed (failing clause + actual value) @@ -2370,8 +2373,8 @@ model TaskSchedule { timezone String @default("UTC") // Cron spread - windowDurationSeconds Int? - windowPercentage Int? + windowDurationSeconds Int? + windowPercentage Int? ///Can be provided by the user then accessed inside a run externalId String? diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index f82942d4e43..92073f8d26f 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -300,7 +300,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { logger.debug("Using project ref from env", { ref: envVars.TRIGGER_PROJECT_REF }); } - const resolvedConfig = await loadConfig({ + let resolvedConfig = await loadConfig({ cwd: projectPath, overrides: { project: options.projectRef ?? envVars.TRIGGER_PROJECT_REF }, configFile: options.config, @@ -364,6 +364,15 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { throw new Error("Failed to get project client"); } + if (projectClient.defaultRuntime) { + resolvedConfig = await loadConfig({ + cwd: projectPath, + overrides: { project: options.projectRef ?? envVars.TRIGGER_PROJECT_REF }, + configFile: options.config, + defaultRuntime: projectClient.defaultRuntime, + }); + } + if (options.nativeBuildServer) { await handleNativeBuildServerDeploy({ apiClient: projectClient.client, diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts index a6ae5af60cb..46aeee5f5e0 100644 --- a/packages/cli-v3/src/commands/init.ts +++ b/packages/cli-v3/src/commands/init.ts @@ -50,7 +50,7 @@ const InitCommandOptions = CommonCommandOptions.extend({ overrideConfig: z.boolean().default(false), tag: z.string().default(cliVersion), skipPackageInstall: z.boolean().default(false), - runtime: z.string().default("node"), + runtime: z.string().default("node-24"), pkgArgs: z.string().optional(), gitRef: z.string().default("main"), javascript: z.boolean().default(false), @@ -94,8 +94,8 @@ Examples: ) .option( "-r, --runtime ", - "Which runtime to use for the project. Supported: node, node-22, bun", - "node" + "Which runtime to use for the project. Supported: node, node-22, node-24, node-26, bun", + "node-24" ) .option("--skip-package-install", "Skip installing the @trigger.dev/sdk package") .option("--override-config", "Override the existing config file if it exists") diff --git a/packages/cli-v3/src/config.test.ts b/packages/cli-v3/src/config.test.ts index cafee4b9a3d..0936f8b3305 100644 --- a/packages/cli-v3/src/config.test.ts +++ b/packages/cli-v3/src/config.test.ts @@ -45,7 +45,25 @@ describe("loadConfig runtime", () => { await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({ runtime: expected }); }); - it("keeps node as the default", async () => { + it("uses the project default when runtime is omitted", async () => { + const cwd = await createProject(); + + await expect( + loadConfig({ cwd, defaultRuntime: "node-24", warn: false }) + ).resolves.toMatchObject({ + runtime: "node-24", + }); + }); + + it("prefers an explicit runtime over the project default", async () => { + const cwd = await createProject("node-22"); + + await expect( + loadConfig({ cwd, defaultRuntime: "node-24", warn: false }) + ).resolves.toMatchObject({ runtime: "node-22" }); + }); + + it("keeps node as the legacy default when runtime is omitted", async () => { const cwd = await createProject(); await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({ runtime: "node" }); diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts index 2e7ffdee509..710108f2b6c 100644 --- a/packages/cli-v3/src/config.ts +++ b/packages/cli-v3/src/config.ts @@ -34,6 +34,7 @@ export type ResolveConfigOptions = { cwd?: string; overrides?: Partial; configFile?: string; + defaultRuntime?: BuildRuntime; warn?: boolean; }; @@ -41,6 +42,7 @@ export async function loadConfig({ cwd = process.cwd(), overrides, configFile, + defaultRuntime, warn = true, }: ResolveConfigOptions = {}): Promise { const result = await c12.loadConfig({ @@ -50,7 +52,7 @@ export async function loadConfig({ jitiOptions: { debug: logger.loggerLevel === "debug" }, }); - return await resolveConfig(cwd, result, overrides, warn); + return await resolveConfig(cwd, result, overrides, defaultRuntime, warn); } type ResolveWatchConfigOptions = ResolveConfigOptions & { @@ -72,6 +74,7 @@ export async function watchConfig({ ignoreInitial = true, overrides, configFile, + defaultRuntime, }: ResolveWatchConfigOptions): Promise { const result = await c12.watchConfig({ name: "trigger", @@ -81,13 +84,13 @@ export async function watchConfig({ chokidarOptions: { ignoreInitial }, jitiOptions: { debug: logger.loggerLevel === "debug" }, onUpdate: async ({ newConfig }) => { - const resolvedConfig = await resolveConfig(cwd, newConfig, overrides, false); + const resolvedConfig = await resolveConfig(cwd, newConfig, overrides, defaultRuntime, false); onUpdate(resolvedConfig); }, }); - const config = await resolveConfig(cwd, result, overrides); + const config = await resolveConfig(cwd, result, overrides, defaultRuntime); return { config, @@ -156,6 +159,7 @@ async function resolveConfig( cwd: string, result: c12.ResolvedConfig, overrides?: Partial, + defaultRuntime?: BuildRuntime, warn = true ): Promise { // `trigger.config` is the fallback value set by c12. Bail out with actionable guidance before @@ -181,8 +185,9 @@ async function resolveConfig( const features = featuresFromCompatibilityFlags( ["run_engine_v2" as const].concat(config.compatibilityFlags ?? []) ); - const defaultRuntime: BuildRuntime = features.run_engine_v2 ? "node" : DEFAULT_RUNTIME; - const configuredRuntime = overrides?.runtime ?? config.runtime ?? defaultRuntime; + const legacyDefaultRuntime: BuildRuntime = features.run_engine_v2 ? "node" : DEFAULT_RUNTIME; + const configuredRuntime = + overrides?.runtime ?? config.runtime ?? defaultRuntime ?? legacyDefaultRuntime; const runtime = resolveBuildRuntime(configuredRuntime); if (warn && isDeprecatedConfigRuntime(configuredRuntime)) { @@ -224,7 +229,7 @@ async function resolveConfig( config, { dirs, - runtime: defaultRuntime, + runtime: defaultRuntime ?? legacyDefaultRuntime, tsconfig: tsconfigPath, build: { jsx: { diff --git a/packages/cli-v3/src/utilities/session.ts b/packages/cli-v3/src/utilities/session.ts index b7583f83c9f..2500cf1f368 100644 --- a/packages/cli-v3/src/utilities/session.ts +++ b/packages/cli-v3/src/utilities/session.ts @@ -112,6 +112,7 @@ export async function getProjectClient(options: GetEnvOptions) { return { id: projectEnv.data.projectId, name: projectEnv.data.name, + defaultRuntime: projectEnv.data.defaultRuntime, client, }; } diff --git a/packages/core/src/v3/auth/environment.ts b/packages/core/src/v3/auth/environment.ts index 498393722fe..f61a23be28d 100644 --- a/packages/core/src/v3/auth/environment.ts +++ b/packages/core/src/v3/auth/environment.ts @@ -67,6 +67,7 @@ export type AuthenticatedEnvironment = { // Build-server bookkeeping. Read by remote-image-builder when // creating Depot builds. builderProjectId: string | null; + defaultRuntime?: string | null; }; organization: { diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 6cd100f7c3c..12e991cfef9 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -11,6 +11,7 @@ import { BackgroundWorkerMetadata } from "./resources.js"; import { DequeuedMessage, MachineResources } from "./runEngine.js"; import { QueueTypeName } from "./queues.js"; import { ScheduleWindow } from "./schemas.js"; +import { BuildRuntime } from "./build.js"; export const RunEngineVersion = z.union([z.literal("V1"), z.literal("V2")]); @@ -43,6 +44,7 @@ export const GetProjectResponseBody = z.object({ // (the project falls back to the global platform default). Optional so a // newer client still parses responses from an older server that omits it. defaultRegion: z.string().nullable().optional(), + defaultRuntime: BuildRuntime.nullable().optional(), organization: z.object({ id: z.string(), title: z.string(), @@ -98,6 +100,7 @@ export const GetProjectEnvResponse = z.object({ name: z.string(), apiUrl: z.string(), projectId: z.string(), + defaultRuntime: BuildRuntime.nullable().optional(), }); export type GetProjectEnvResponse = z.infer; From 6e2004c1fbb756037d410ef73aeb253c1d04f5b5 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 17 Aug 2026 12:20:38 +0100 Subject: [PATCH 2/5] fix(webapp): ignore unrecognized project runtime defaults --- .server-changes/default-new-project-runtime-node-24.md | 6 ------ apps/webapp/app/models/runtimeEnvironment.server.ts | 2 +- apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts | 3 ++- apps/webapp/app/routes/api.v1.projects.$projectRef.ts | 3 ++- 4 files changed, 5 insertions(+), 9 deletions(-) delete mode 100644 .server-changes/default-new-project-runtime-node-24.md diff --git a/.server-changes/default-new-project-runtime-node-24.md b/.server-changes/default-new-project-runtime-node-24.md deleted file mode 100644 index a75b4bda0e8..00000000000 --- a/.server-changes/default-new-project-runtime-node-24.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: feature ---- - -New projects use Node.js 24 when their deployment config does not specify a runtime. diff --git a/apps/webapp/app/models/runtimeEnvironment.server.ts b/apps/webapp/app/models/runtimeEnvironment.server.ts index e71d6985231..e7cf10f3e02 100644 --- a/apps/webapp/app/models/runtimeEnvironment.server.ts +++ b/apps/webapp/app/models/runtimeEnvironment.server.ts @@ -78,7 +78,7 @@ export function toAuthenticated( defaultWorkerGroupId: env.project.defaultWorkerGroupId, organizationId: env.project.organizationId, builderProjectId: env.project.builderProjectId, - defaultRuntime: BuildRuntime.nullable().parse(env.project.defaultRuntime), + defaultRuntime: BuildRuntime.nullable().safeParse(env.project.defaultRuntime).data ?? null, }, organization: { id: env.organization.id, diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts index c6a3e61875e..8083ba1a0a4 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts @@ -65,7 +65,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { name: environment.project.name, apiUrl: processEnv.API_ORIGIN ?? processEnv.APP_ORIGIN, projectId: environment.project.id, - defaultRuntime: BuildRuntime.nullable().parse(environment.project.defaultRuntime ?? null), + defaultRuntime: + BuildRuntime.nullable().safeParse(environment.project.defaultRuntime ?? null).data ?? null, }; return json(result); diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.ts index 4f3b9b70647..824ab1199a8 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.ts @@ -53,7 +53,8 @@ export const loader = createLoaderPATApiRoute( slug: project.slug, createdAt: project.createdAt, defaultRegion: project.defaultWorkerGroup?.name ?? null, - defaultRuntime: BuildRuntime.nullable().parse(project.defaultRuntime ?? null), + defaultRuntime: + BuildRuntime.nullable().safeParse(project.defaultRuntime ?? null).data ?? null, organization: { id: project.organization.id, title: project.organization.title, From 32f19ffb7e1cf9adaefe7f4b28173fdc636cc690 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 17 Aug 2026 13:43:46 +0100 Subject: [PATCH 3/5] fix(cli): avoid duplicate runtime warnings --- packages/cli-v3/src/commands/deploy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 92073f8d26f..0cbb3fdf102 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -370,6 +370,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { overrides: { project: options.projectRef ?? envVars.TRIGGER_PROJECT_REF }, configFile: options.config, defaultRuntime: projectClient.defaultRuntime, + warn: false, }); } From 4ed4c87008d0c01ce665d7a024293c8caa8ff13b Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 17 Aug 2026 15:16:08 +0100 Subject: [PATCH 4/5] refactor(cli): avoid reloading config during deploy --- packages/cli-v3/src/commands/deploy.ts | 10 ++----- packages/cli-v3/src/config.test.ts | 22 ++++++++-------- packages/cli-v3/src/config.ts | 36 +++++++++++++++----------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 0cbb3fdf102..41c48330836 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -364,14 +364,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { throw new Error("Failed to get project client"); } - if (projectClient.defaultRuntime) { - resolvedConfig = await loadConfig({ - cwd: projectPath, - overrides: { project: options.projectRef ?? envVars.TRIGGER_PROJECT_REF }, - configFile: options.config, - defaultRuntime: projectClient.defaultRuntime, - warn: false, - }); + if (!resolvedConfig.runtimeWasExplicit && projectClient.defaultRuntime) { + resolvedConfig.runtime = projectClient.defaultRuntime; } if (options.nativeBuildServer) { diff --git a/packages/cli-v3/src/config.test.ts b/packages/cli-v3/src/config.test.ts index 0936f8b3305..6a77348f876 100644 --- a/packages/cli-v3/src/config.test.ts +++ b/packages/cli-v3/src/config.test.ts @@ -45,22 +45,22 @@ describe("loadConfig runtime", () => { await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({ runtime: expected }); }); - it("uses the project default when runtime is omitted", async () => { - const cwd = await createProject(); + it("tracks whether runtime was explicitly configured", async () => { + const cwd = await createProject("node-22"); - await expect( - loadConfig({ cwd, defaultRuntime: "node-24", warn: false }) - ).resolves.toMatchObject({ - runtime: "node-24", + await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({ + runtime: "node-22", + runtimeWasExplicit: true, }); }); - it("prefers an explicit runtime over the project default", async () => { - const cwd = await createProject("node-22"); + it("tracks an omitted runtime separately from the legacy default", async () => { + const cwd = await createProject(); - await expect( - loadConfig({ cwd, defaultRuntime: "node-24", warn: false }) - ).resolves.toMatchObject({ runtime: "node-22" }); + await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({ + runtime: "node", + runtimeWasExplicit: false, + }); }); it("keeps node as the legacy default when runtime is omitted", async () => { diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts index 710108f2b6c..9f3c13413bf 100644 --- a/packages/cli-v3/src/config.ts +++ b/packages/cli-v3/src/config.ts @@ -34,17 +34,19 @@ export type ResolveConfigOptions = { cwd?: string; overrides?: Partial; configFile?: string; - defaultRuntime?: BuildRuntime; warn?: boolean; }; +export type LoadedConfig = ResolvedConfig & { + runtimeWasExplicit: boolean; +}; + export async function loadConfig({ cwd = process.cwd(), overrides, configFile, - defaultRuntime, warn = true, -}: ResolveConfigOptions = {}): Promise { +}: ResolveConfigOptions = {}): Promise { const result = await c12.loadConfig({ name: "trigger", cwd, @@ -52,17 +54,17 @@ export async function loadConfig({ jitiOptions: { debug: logger.loggerLevel === "debug" }, }); - return await resolveConfig(cwd, result, overrides, defaultRuntime, warn); + return await resolveConfig(cwd, result, overrides, warn); } type ResolveWatchConfigOptions = ResolveConfigOptions & { - onUpdate: (config: ResolvedConfig) => void; + onUpdate: (config: LoadedConfig) => void; debounce?: number; ignoreInitial?: boolean; }; type ResolveWatchConfigResult = { - config: ResolvedConfig; + config: LoadedConfig; files: string[]; stop: () => Promise; }; @@ -74,7 +76,6 @@ export async function watchConfig({ ignoreInitial = true, overrides, configFile, - defaultRuntime, }: ResolveWatchConfigOptions): Promise { const result = await c12.watchConfig({ name: "trigger", @@ -84,13 +85,13 @@ export async function watchConfig({ chokidarOptions: { ignoreInitial }, jitiOptions: { debug: logger.loggerLevel === "debug" }, onUpdate: async ({ newConfig }) => { - const resolvedConfig = await resolveConfig(cwd, newConfig, overrides, defaultRuntime, false); + const resolvedConfig = await resolveConfig(cwd, newConfig, overrides, false); onUpdate(resolvedConfig); }, }); - const config = await resolveConfig(cwd, result, overrides, defaultRuntime); + const config = await resolveConfig(cwd, result, overrides); return { config, @@ -159,9 +160,8 @@ async function resolveConfig( cwd: string, result: c12.ResolvedConfig, overrides?: Partial, - defaultRuntime?: BuildRuntime, warn = true -): Promise { +): Promise { // `trigger.config` is the fallback value set by c12. Bail out with actionable guidance before // touching the filesystem: the pkg-types resolvers below throw raw errors when run outside a // project (e.g. `dev` before `init`), which would mask this message. @@ -186,8 +186,7 @@ async function resolveConfig( ["run_engine_v2" as const].concat(config.compatibilityFlags ?? []) ); const legacyDefaultRuntime: BuildRuntime = features.run_engine_v2 ? "node" : DEFAULT_RUNTIME; - const configuredRuntime = - overrides?.runtime ?? config.runtime ?? defaultRuntime ?? legacyDefaultRuntime; + const configuredRuntime = overrides?.runtime ?? config.runtime ?? legacyDefaultRuntime; const runtime = resolveBuildRuntime(configuredRuntime); if (warn && isDeprecatedConfigRuntime(configuredRuntime)) { @@ -229,7 +228,7 @@ async function resolveConfig( config, { dirs, - runtime: defaultRuntime ?? legacyDefaultRuntime, + runtime: legacyDefaultRuntime, tsconfig: tsconfigPath, build: { jsx: { @@ -246,12 +245,19 @@ async function resolveConfig( } ) as ResolvedConfig; // TODO: For some reason, without this, there is a weird type error complaining about tsconfigPath being string | nullish, which can't be assigned to string | undefined - return { + const resolvedConfig = { ...mergedConfig, dirs: Array.from(new Set(dirs)), instrumentedPackageNames: getInstrumentedPackageNames(mergedConfig), runtime, }; + + Object.defineProperty(resolvedConfig, "runtimeWasExplicit", { + value: overrides?.runtime !== undefined || config.runtime !== undefined, + enumerable: false, + }); + + return resolvedConfig as LoadedConfig; } function resolveTriggerDir(dir: string, workingDir: string): string { From b13b2789f8a0703ae4b2e2339e33251f7e2a6620 Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 17 Aug 2026 17:27:34 +0100 Subject: [PATCH 5/5] update changeset --- .changeset/node-24-project-default.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/node-24-project-default.md b/.changeset/node-24-project-default.md index ad43e554c74..551a7d5a23f 100644 --- a/.changeset/node-24-project-default.md +++ b/.changeset/node-24-project-default.md @@ -2,4 +2,4 @@ "trigger.dev": patch --- -New projects created with `trigger init` use Node.js 24 by default. Deployments whose config omits `runtime` now use their project's configured default runtime. +New projects created with `trigger init` use Node.js 24 by default. Deployments without explicit `runtime` now use their project's configured default runtime.