feat(cli,webapp): default new projects to node-24 (#4649)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
New projects created with `trigger init` use Node.js 24 by default. Deployments without explicit `runtime` now use their project's configured default runtime.
|
||||
@@ -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: {
|
||||
|
||||
@@ -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().safeParse(env.project.defaultRuntime).data ?? null,
|
||||
},
|
||||
organization: {
|
||||
id: env.organization.id,
|
||||
|
||||
@@ -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,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().safeParse(environment.project.defaultRuntime ?? null).data ?? null,
|
||||
};
|
||||
|
||||
return json(result);
|
||||
|
||||
@@ -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,8 @@ export const loader = createLoaderPATApiRoute(
|
||||
slug: project.slug,
|
||||
createdAt: project.createdAt,
|
||||
defaultRegion: project.defaultWorkerGroup?.name ?? null,
|
||||
defaultRuntime:
|
||||
BuildRuntime.nullable().safeParse(project.defaultRuntime ?? null).data ?? null,
|
||||
organization: {
|
||||
id: project.organization.id,
|
||||
title: project.organization.title,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "defaultRuntime" TEXT;
|
||||
@@ -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?
|
||||
|
||||
@@ -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,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
throw new Error("Failed to get project client");
|
||||
}
|
||||
|
||||
if (!resolvedConfig.runtimeWasExplicit && projectClient.defaultRuntime) {
|
||||
resolvedConfig.runtime = projectClient.defaultRuntime;
|
||||
}
|
||||
|
||||
if (options.nativeBuildServer) {
|
||||
await handleNativeBuildServerDeploy({
|
||||
apiClient: projectClient.client,
|
||||
|
||||
@@ -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 <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")
|
||||
|
||||
@@ -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("tracks whether runtime was explicitly configured", async () => {
|
||||
const cwd = await createProject("node-22");
|
||||
|
||||
await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({
|
||||
runtime: "node-22",
|
||||
runtimeWasExplicit: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("tracks an omitted runtime separately from the legacy default", async () => {
|
||||
const cwd = await createProject();
|
||||
|
||||
await expect(loadConfig({ cwd, warn: false })).resolves.toMatchObject({
|
||||
runtime: "node",
|
||||
runtimeWasExplicit: false,
|
||||
});
|
||||
});
|
||||
|
||||
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" });
|
||||
|
||||
@@ -37,12 +37,16 @@ export type ResolveConfigOptions = {
|
||||
warn?: boolean;
|
||||
};
|
||||
|
||||
export type LoadedConfig = ResolvedConfig & {
|
||||
runtimeWasExplicit: boolean;
|
||||
};
|
||||
|
||||
export async function loadConfig({
|
||||
cwd = process.cwd(),
|
||||
overrides,
|
||||
configFile,
|
||||
warn = true,
|
||||
}: ResolveConfigOptions = {}): Promise<ResolvedConfig> {
|
||||
}: ResolveConfigOptions = {}): Promise<LoadedConfig> {
|
||||
const result = await c12.loadConfig<TriggerConfig>({
|
||||
name: "trigger",
|
||||
cwd,
|
||||
@@ -54,13 +58,13 @@ export async function loadConfig({
|
||||
}
|
||||
|
||||
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<void>;
|
||||
};
|
||||
@@ -157,7 +161,7 @@ async function resolveConfig(
|
||||
result: c12.ResolvedConfig<TriggerConfig>,
|
||||
overrides?: Partial<TriggerConfig>,
|
||||
warn = true
|
||||
): Promise<ResolvedConfig> {
|
||||
): Promise<LoadedConfig> {
|
||||
// `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.
|
||||
@@ -181,8 +185,8 @@ 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 ?? legacyDefaultRuntime;
|
||||
const runtime = resolveBuildRuntime(configuredRuntime);
|
||||
|
||||
if (warn && isDeprecatedConfigRuntime(configuredRuntime)) {
|
||||
@@ -224,7 +228,7 @@ async function resolveConfig(
|
||||
config,
|
||||
{
|
||||
dirs,
|
||||
runtime: defaultRuntime,
|
||||
runtime: legacyDefaultRuntime,
|
||||
tsconfig: tsconfigPath,
|
||||
build: {
|
||||
jsx: {
|
||||
@@ -241,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 {
|
||||
|
||||
@@ -112,6 +112,7 @@ export async function getProjectClient(options: GetEnvOptions) {
|
||||
return {
|
||||
id: projectEnv.data.projectId,
|
||||
name: projectEnv.data.name,
|
||||
defaultRuntime: projectEnv.data.defaultRuntime,
|
||||
client,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<typeof GetProjectEnvResponse>;
|
||||
|
||||
Reference in New Issue
Block a user