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
11 changes: 11 additions & 0 deletions .changeset/zod-4-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@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 support. You can now use zod 3.25+ or zod 4 (the minimum zod 3 version is now 3.25.0). `zod` is now a peer dependency of `@trigger.dev/core`, `@trigger.dev/sdk`, `@trigger.dev/redis-worker`, and `@trigger.dev/schema-to-json`, so it shares a single copy with your project; if you depend on `@trigger.dev/core` directly, add `zod` to your own dependencies.

The published type declarations are built against zod 4. If you are still on zod 3, keep `skipLibCheck: true` in your tsconfig (the default in Trigger.dev's templates).
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)

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.

🚩 Feedback dialog close condition changed from allErrors to form.errors

At apps/webapp/app/components/Feedback.tsx:56, the condition changed from Object.keys(form.allErrors).length === 0 to (form.errors === undefined || form.errors.length === 0). In conform v1 (used with @conform-to/zod), form.allErrors was an object mapping field names to error arrays. In the newer conform API used with @conform-to/zod/v4, form.errors is a flat string array of form-level errors. This means the new check only looks at form-level errors, not field-level errors. If a field has a validation error but the form itself doesn't, the dialog would close prematurely. However, this may be intentional — the dialog submits to a server action, and field errors would prevent successful submission, so the navigation.state === "loading" check (indicating the server accepted the form) may be sufficient to gate the close.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

) {
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 @@ -36,7 +36,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 { RectangleStackIcon } from "@heroicons/react/20/solid";
import { DialogClose } from "@radix-ui/react-dialog";
import { Form, useActionData, useNavigation, useParams, useSubmit } from "@remix-run/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 @@ -129,7 +129,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 @@ -1004,6 +1004,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 @@ -251,7 +250,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 @@ -265,7 +264,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 @@ -477,7 +477,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
@@ -1,5 +1,5 @@
import { getFormProps, useForm, type FieldMetadata, type FormMetadata } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { parseWithZod } from "@conform-to/zod/v4";
import {
LockClosedIcon,
LockOpenIcon,
Expand Down Expand Up @@ -87,7 +87,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 @@ -221,14 +221,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 @@ -410,15 +408,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, useSearchParams } from "@remix-run/react";
import { json } from "@remix-run/server-runtime";
import React, { useCallback, useEffect, useRef, useState } from "react";
Expand Down
Loading
Loading