Skip to content
Draft
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
9 changes: 9 additions & 0 deletions .changeset/zod-4-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@trigger.dev/core": minor
"@trigger.dev/sdk": minor
"trigger.dev": minor
"@trigger.dev/redis-worker": minor
"@trigger.dev/schema-to-json": minor
---

Add zod v4 compatibility. The `zod` peer dependency is widened to `^3.25.0 || ^4.0.0`, so projects can use zod 3.25+ or zod 4. Internal code was updated for zod v4 API changes (`ZodError.errors` → `.issues`, single-arg `z.record` → keyed, unified `error` option, `z.ZodSchema`/`z.AnyZodObject` → `z.ZodType`/`z.ZodObject`, `z.any()` object fields made `.optional()` to preserve v3 inference). No runtime behavior change for existing zod 3 users.
6 changes: 0 additions & 6 deletions .server-changes/conform-v1.md

This file was deleted.

2 changes: 1 addition & 1 deletion apps/supervisor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"prom-client": "^15.1.0",
"socket.io": "4.7.4",
"std-env": "^3.8.0",
"zod": "3.25.76"
"zod": "4.4.3"
},
"devDependencies": {
"@internal/testcontainers": "workspace:*",
Expand Down
8 changes: 4 additions & 4 deletions apps/webapp/app/components/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
getTextareaProps,
useForm,
} from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid";
import { EnvelopeIcon, ShieldCheckIcon } from "@heroicons/react/24/solid";
import { Form, useActionData, useLocation, useNavigation, useSearchParams } from "@remix-run/react";
Expand Down Expand Up @@ -53,11 +53,11 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
if (
navigation.formAction === "/resources/feedback" &&
navigation.state === "loading" &&
Object.keys(form.allErrors).length === 0
(form.errors === undefined || form.errors.length === 0)
) {
setOpen(false);
}
}, [navigation.formAction, navigation.state, form.allErrors]);
}, [navigation, form]);

// Handle URL param functionality
useEffect(() => {
Expand Down Expand Up @@ -104,7 +104,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
<Fieldset className="max-w-full gap-y-3">
<input
value={location.pathname}
{...getInputProps(fields.path, { type: "hidden" })}
{...getInputProps(fields.path, { type: "hidden", value: false })}
/>
<InputGroup className="max-w-full">
Comment thread
carderne marked this conversation as resolved.
{type === "feature" && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm, type SubmissionResult } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { PlusIcon, TrashIcon } from "@heroicons/react/20/solid";
import { Form, useActionData, useSearchParams } from "@remix-run/react";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getFormProps, useForm, type SubmissionResult } from "@conform-to/react";

import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { Form, useActionData } from "@remix-run/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { z } from "zod";
Expand Down Expand Up @@ -37,7 +37,7 @@ export const billingLimitFormSchema = z.discriminatedUnion("mode", [
z.object({
mode: z.literal("custom"),
amount: z.coerce
.number({ invalid_type_error: "Not a valid amount" })
.number({ error: "Not a valid amount" })
.positive("Amount must be greater than 0"),
cancelInProgressRuns: z
.preprocess((v) => v === "on" || v === true || v === "true", z.boolean())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, useForm, type SubmissionResult } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { z } from "zod";
Expand All @@ -21,7 +21,7 @@ export const billingLimitRecoveryFormSchema = z
.object({
action: z.enum(["increase", "remove"]),
newAmount: z.coerce
.number({ invalid_type_error: "Not a valid amount" })
.number({ error: "Not a valid amount" })
.positive("Amount must be greater than 0")
.optional(),
resumeMode: z.enum(["queue", "new_only"]),
Expand Down Expand Up @@ -87,7 +87,7 @@ export function BillingLimitRecoveryPanel({
},
defaultValue: {
action: "increase",
newAmount: suggestedNewLimitDollars,
newAmount: String(suggestedNewLimitDollars),
resumeMode: "queue",
},
});
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import {
EnvelopeIcon,
GlobeAltIcon,
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/metrics/QueryWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const chartConfigOptions = {
sortByColumn: z.string().nullable(),
sortDirection: SortDirection,
aggregation: AggregationType,
seriesColors: z.record(z.string()).optional(),
seriesColors: z.record(z.string(), z.string()).optional(),
};

const ChartConfiguration = z.object({ ...chartConfigOptions });
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/runs/v3/ReplayRunDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, getSelectProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { DialogClose } from "@radix-ui/react-dialog";
import { Form, useActionData, useNavigation, useParams, useSubmit } from "@remix-run/react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { EnvelopeIcon } from "@heroicons/react/20/solid";
import { DialogClose } from "@radix-ui/react-dialog";
import { useFetcher } from "@remix-run/react";
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/models/orgIntegration.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const SlackSecretSchema = z.object({
refreshToken: z.string().optional(),
botScopes: z.array(z.string()).optional(),
userScopes: z.array(z.string()).optional(),
raw: z.record(z.any()).optional(),
raw: z.record(z.string(), z.any()).optional(),
});

type SlackSecret = z.infer<typeof SlackSecretSchema>;
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/models/vercelIntegration.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const VercelSecretSchema = z.object({
teamId: z.string().nullable().optional(),
userId: z.string().optional(),
installationId: z.string().optional(),
raw: z.record(z.any()).optional(),
raw: z.record(z.string(), z.any()).optional(),
});

export type VercelSecret = z.infer<typeof VercelSecretSchema>;
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/models/vercelSdkRecovery.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ export const VercelSchemas = {
.union([
z
.object({
envs: z.array(z.record(z.unknown())),
envs: z.array(z.record(z.string(), z.unknown())),
pagination: z.unknown().optional(),
})
.passthrough(),
z.array(z.record(z.unknown())),
z.array(z.record(z.string(), z.unknown())),
])
.transform((val) => (Array.isArray(val) ? { envs: val } : val)),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const DashboardLayout = z.discriminatedUnion("version", [
z.object({
version: z.literal("1"),
layout: z.array(LayoutItem),
widgets: z.record(Widget),
widgets: z.record(z.string(), Widget),
}),
]);

Expand Down
3 changes: 3 additions & 0 deletions apps/webapp/app/presenters/v3/SpanPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,9 @@ export class SpanPresenter extends BasePresenter {
filePath: run.lockedBy?.filePath ?? "",
},
run: {
// zod v4 types the run-context `context` (z.any) field as required; it was
// never populated here (optional under v3), so undefined preserves behavior.
context: undefined,
id: run.friendlyId,
createdAt: run.createdAt,
tags: run.runTags,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import {
ArrowUpCircleIcon,
EnvelopeIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getFormProps, getInputProps, getSelectProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { HashtagIcon, LockClosedIcon } from "@heroicons/react/20/solid";
import { Form, useActionData, useNavigate, useNavigation } from "@remix-run/react";
import { type LoaderFunctionArgs } from "@remix-run/router";
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { SlackIcon } from "@trigger.dev/companyicons";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { InlineCode } from "~/components/code/InlineCode";
Expand Down Expand Up @@ -217,7 +217,6 @@ export default function Page() {
const project = useProject();
const environment = useEnvironment();
const [currentAlertChannel, setCurrentAlertChannel] = useState<string | null>(option ?? "EMAIL");
const formRef = useRef<HTMLFormElement>(null);

const [selectedSlackChannelValue, setSelectedSlackChannelValue] = useState<string | undefined>();

Expand Down Expand Up @@ -248,7 +247,7 @@ export default function Page() {
if (navigation.state !== "idle") return;
if (lastSubmission !== undefined) return;

formRef.current?.reset();
form.reset();
}, [navigation.state, lastSubmission]);

return (
Expand All @@ -262,7 +261,7 @@ export default function Page() {
>
<DialogContent>
<DialogHeader>New alert</DialogHeader>
<Form ref={formRef} method="post" {...getFormProps(form)}>
<Form method="post" {...getFormProps(form)}>
<Fieldset className="mt-2">
<InputGroup fullWidth>
<SegmentedControl
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import {
BellAlertIcon,
BellSlashIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { ArrowUpCircleIcon, CheckIcon, EnvelopeIcon, PlusIcon } from "@heroicons/react/20/solid";
import { BookOpenIcon } from "@heroicons/react/24/solid";
import { DialogClose } from "@radix-ui/react-dialog";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import {
ArrowDownIcon,
EnvelopeIcon,
Expand Down Expand Up @@ -476,7 +476,7 @@ function Upgradable({
</TableRow>
</TableBody>
</Table>
<FormError id={formEnvironments.id}>{formEnvironments.errors}</FormError>
<FormError id={formEnvironments.errorId}>{formEnvironments.errors}</FormError>
</div>
<Form className="flex flex-col gap-2" method="post" {...getFormProps(form)} id="allocate">
<input type="hidden" name="action" value="allocate" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
getInputProps,
useForm,
} from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import {
LockClosedIcon,
LockOpenIcon,
Expand Down Expand Up @@ -93,7 +93,7 @@ const schema = z.object({

return;
},
z.array(z.string(), { required_error: "At least one environment is required" })
z.array(z.string(), { error: "At least one environment is required" })
),
variables: z.preprocess((i) => {
if (!Array.isArray(i)) {
Expand Down Expand Up @@ -227,14 +227,12 @@ export default function Page() {
const [selectedEnvironmentIds, setSelectedEnvironmentIds] = useState<Set<string>>(new Set());
const [selectedBranchId, setSelectedBranchId] = useState<string | undefined>(undefined);

// TODO for no we only support branch-specific env vars for Preview environments
// Mostly to keep the UX for setting consistent env-vars across Dev/Staging/Prod easier
const previewBranches = environments.filter(
(env) => env.type === "PREVIEW" && env.parentEnvironmentId !== null
);
const nonBranchEnvironments = environments.filter((env) => env.parentEnvironmentId === null);
const branchEnvironments = environments.filter((env) => env.branchName);
const nonBranchEnvironments = environments.filter((env) => !env.branchName);
const selectedEnvironments = environments.filter((env) => selectedEnvironmentIds.has(env.id));
const previewIsSelected = selectedEnvironments.some((env) => env.type === "PREVIEW");
const previewIsSelected = selectedEnvironments.some(
(env) => env.branchName !== null || env.type === "PREVIEW"
);

const isLoading = navigation.state !== "idle" && navigation.formMethod === "post";

Expand Down Expand Up @@ -416,15 +414,15 @@ export default function Page() {
value={selectedBranchId ?? "all"}
setValue={handleBranchChange}
placeholder="All branches"
items={[{ id: "all", branchName: "All branches" }, ...previewBranches]}
items={[{ id: "all", branchName: "All branches" }, ...branchEnvironments]}
className="w-fit min-w-52"
filter={{
keys: [
(item) => item.branchName?.replace(/\//g, " ").replace(/_/g, " ") ?? "",
],
}}
text={(val) =>
val ? previewBranches.find((b) => b.id === val)?.branchName : null
val ? branchEnvironments.find((b) => b.id === val)?.branchName : null
}
dropdownIcon
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import {
BookOpenIcon,
InformationCircleIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { BellAlertIcon } from "@heroicons/react/20/solid";
import { type MetaFunction, useFetcher, useRevalidator } from "@remix-run/react";
import { type ActionFunctionArgs, json, type LoaderFunctionArgs } from "@remix-run/server-runtime";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { Outlet, useNavigate } from "@remix-run/react";
import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { useCallback } from "react";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
if (!parsed.success) {
return typedjson(
{
error: parsed.error.errors.map((e) => e.message).join(", "),
error: parsed.error.issues.map((e) => e.message).join(", "),
rows: null,
columns: null,
stats: null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { useLocation } from "@remix-run/react";
import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { conformZodMessage, parseWithZod } from "@conform-to/zod";
import { conformZodMessage, parseWithZod } from "@conform-to/zod/v4";
import { ExclamationTriangleIcon, FolderIcon, TrashIcon } from "@heroicons/react/20/solid";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { type ActionFunction, json } from "@remix-run/server-runtime";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { json } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData, useTypedFetcher } from "remix-typedjson";
Expand Down
Loading
Loading