Compare commits

...

9 Commits

Author SHA1 Message Date
Matt Aitken 70f9bd0d70 Latest lockfile 2024-02-12 13:11:14 +00:00
github-actions[bot] a1860dbaae chore: Update version for release (#899)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-12 13:00:14 +00:00
Matt Aitken d69e4e712d OpenAI integration: retry 400 status code because OpenAI returns this sometimes for no reason (#898) 2024-02-12 12:48:48 +00:00
Matt Aitken 7fae67c47d Fix/autoyield canceled run (#890)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / units (push) Failing after 16s
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 20s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
* Todo for the fix

* If the run has been canceled during execution, don’t do any further processing of the run
2024-02-09 11:06:19 +00:00
Matt Aitken 9ae0ca64af Fix edge cases with deleting last org/project (#892)
* If you have an org with no projects, it displays in the project dropdown with a “New project” button

* When creating a new org disable the button whilst it’s doing the request

* If an org already had any deleted projects it couldn’t be deleted…

* When selecting the best project, factor in deleted ones

* Don’t show the cancel button when creating a new org if there are no non-deleted projects

* If a project has already been deleted just return
2024-02-09 11:05:28 +00:00
Matt Aitken da69fa0613 Fixes #886. Removed “public” schema from the JobListPresenter query
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 4s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 20s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
2024-02-07 16:48:18 +00:00
Matt Aitken 336029b842 Organization renaming, deleting and one-click email unsubscribe (#885)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 10s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
* Added the org settings page to the sidebar

* Added loading states when renaming/deleting projects

* Don’t show deleted orgs in the app

* The actual db migration file

* The Org settings page with the actions working

* Don’t remove org members, just leave them

* Don’t show invites from orgs that are deleted

* Allow disabling IntegrationConnections

* Don’t refresh IntegrationConnections that are disabled

* Set all the integrations as disabled

* Updated the unsubscribe checkbox text

* Unsubscribe route

* Use the magic link secret, not the encryption key. Also make the error message more vague

* Only members of the org or project can rename them

* Don’t throw an error if the connection can’t be refreshed, return undefined instead
2024-02-07 10:52:50 +00:00
Matt Aitken 29edcd3df9 Rename and delete projects (#880)
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
* Side menu: selector title changed to project. Changed section headers and made it Slack + Discord

* Added project settings page. Renaming is working

* Added project.deletedAt column

* Deleting projects WIP

* Allow clearing the project from the session

* Don’t load projects that are deleted

* Improved the project selection logic

* Clear the session project id when deleting a project

* Deleting the last project in an org now works. The new project page doesn’t have the sidebar anymore.

* Removed false code comment

* Added the JobRun index back in, using Prisma. Otherwise it tries to remove it on each migration

* Only return environments where the project isn’t deleted. This will block API calls

* Do the project deletedAt check in code, not SQL

* Made it clearer what the code does with a comment and applied the same logic to the public api key
2024-02-05 13:14:33 +00:00
Matt Aitken a9ff32418e Fix for when there are no jobs…
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
2024-01-30 17:35:04 +00:00
91 changed files with 1467 additions and 251 deletions
@@ -4,7 +4,6 @@ import {
ArrowRightOnRectangleIcon,
ChartBarIcon,
CursorArrowRaysIcon,
EllipsisHorizontalIcon,
ShieldCheckIcon,
} from "@heroicons/react/20/solid";
import { UserGroupIcon, UserPlusIcon } from "@heroicons/react/24/solid";
@@ -15,6 +14,7 @@ import { useFeatures } from "~/hooks/useFeatures";
import { MatchedOrganization } from "~/hooks/useOrganizations";
import { MatchedProject } from "~/hooks/useProject";
import { User } from "~/models/user.server";
import { useV3Enabled } from "~/root";
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
import { cn } from "~/utils/cn";
import {
@@ -26,6 +26,7 @@ import {
organizationBillingPath,
organizationIntegrationsPath,
organizationPath,
organizationSettingsPath,
organizationTeamPath,
personalAccessTokensPath,
projectEnvironmentsPath,
@@ -33,6 +34,7 @@ import {
projectHttpEndpointsPath,
projectPath,
projectRunsPath,
projectSettingsPath,
projectSetupPath,
projectTriggersPath,
} from "~/utils/pathBuilder";
@@ -56,9 +58,8 @@ import {
PopoverSectionHeader,
} from "../primitives/Popover";
import { StepNumber } from "../primitives/StepNumber";
import { MenuCount, SideMenuItem } from "./SideMenuItem";
import { SideMenuHeader } from "./SideMenuHeader";
import { useV3Enabled } from "~/root";
import { MenuCount, SideMenuItem } from "./SideMenuItem";
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
type SideMenuProject = Pick<
@@ -106,11 +107,7 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
showHeaderDivider ? " border-border" : "border-transparent"
)}
>
<ProjectSelector
organization={organization}
organizations={organizations}
project={project}
/>
<ProjectSelector organizations={organizations} project={project} />
<UserMenu user={user} />
</div>
<div
@@ -118,7 +115,7 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
ref={borderRef}
>
<div className="mb-6 flex flex-col gap-1 px-1">
<SideMenuHeader title={project.name || "No project found"}>
<SideMenuHeader title={"Project"}>
<PopoverMenuItem
to={projectSetupPath(organization, project)}
title="Framework setup"
@@ -168,9 +165,16 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
to={projectEnvironmentsPath(organization, project)}
data-action="environments & api keys"
/>
<SideMenuItem
name="Project settings"
icon="settings"
iconColor="text-teal-500"
to={projectSettingsPath(organization, project)}
data-action="project-settings"
/>
</div>
<div className="mb-1 flex flex-col gap-1 px-1">
<SideMenuHeader title={organization.title}>
<SideMenuHeader title={"Organization"}>
<PopoverMenuItem to={newProjectPath(organization)} title="New Project" icon="plus" />
<PopoverMenuItem
to={inviteTeamMemberPath(organization)}
@@ -206,10 +210,17 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
iconColor="text-green-600"
data-action="usage & billing"
/>
<SideMenuItem
name="Organization settings"
icon="settings"
iconColor="text-teal-500"
to={organizationSettingsPath(organization)}
data-action="organization-settings"
/>
</div>
</div>
<div className="flex flex-col gap-1 border-t border-border p-1">
{currentPlan?.subscription?.isPaying === true ? (
{currentPlan?.subscription?.isPaying === true && (
<Dialog>
<DialogTrigger asChild>
<Button
@@ -265,16 +276,14 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
</div>
</DialogContent>
</Dialog>
) : (
<SideMenuItem
name="Join our Discord"
icon={DiscordIcon}
to="https://trigger.dev/discord"
data-action="join our discord"
target="_blank"
/>
)}
<SideMenuItem
name="Join our Discord"
icon={DiscordIcon}
to="https://trigger.dev/discord"
data-action="join our discord"
target="_blank"
/>
<SideMenuItem
name="Documentation"
icon="docs"
@@ -317,11 +326,9 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
function ProjectSelector({
project,
organization,
organizations,
}: {
project: SideMenuProject;
organization: MatchedOrganization;
organizations: MatchedOrganization[];
}) {
const [isOrgMenuOpen, setOrgMenuOpen] = useState(false);
@@ -339,7 +346,7 @@ function ProjectSelector({
className="h-7 w-full justify-between overflow-hidden py-1 pl-2"
>
<LogoIcon className="relative -top-px mr-2 h-4 w-4 min-w-[1rem]" />
<span className="truncate">{organization.title ?? "Select an organization"}</span>
<span className="truncate">{project.name ?? "Select a project"}</span>
</PopoverArrowTrigger>
<PopoverContent
className="min-w-[16rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
@@ -350,23 +357,31 @@ function ProjectSelector({
<Fragment key={organization.id}>
<PopoverSectionHeader title={organization.title} />
<div className="flex flex-col gap-1 p-1">
{organization.projects.map((p) => {
const isSelected = p.id === project.id;
return (
<PopoverMenuItem
key={p.id}
to={projectPath(organization, p)}
title={
<div className="flex w-full items-center justify-between text-bright">
<span className="grow truncate text-left">{p.name}</span>
<MenuCount count={p.jobCount} />
</div>
}
isSelected={isSelected}
icon="folder"
/>
);
})}
{organization.projects.length > 0 ? (
organization.projects.map((p) => {
const isSelected = p.id === project.id;
return (
<PopoverMenuItem
key={p.id}
to={projectPath(organization, p)}
title={
<div className="flex w-full items-center justify-between text-bright">
<span className="grow truncate text-left">{p.name}</span>
<MenuCount count={p.jobCount} />
</div>
}
isSelected={isSelected}
icon="folder"
/>
);
})
) : (
<PopoverMenuItem
to={newProjectPath(organization)}
title="New project"
icon="plus"
/>
)}
</div>
</Fragment>
))}
+3
View File
@@ -130,6 +130,9 @@ export async function getUsersInvites({ email }: { email: string }) {
return await prisma.orgMemberInvite.findMany({
where: {
email,
organization: {
deletedAt: null,
},
},
include: {
organization: true,
@@ -14,6 +14,11 @@ export async function findEnvironmentByApiKey(apiKey: string) {
},
});
//don't return deleted projects
if (environment?.project.deletedAt !== null) {
return null;
}
return environment;
}
@@ -28,24 +33,10 @@ export async function findEnvironmentByPublicApiKey(apiKey: string) {
},
});
return environment;
}
export async function getEnvironmentForOrganization(organizationSlug: string, slug: string) {
const organization = await prisma.organization.findUnique({
where: {
slug: organizationSlug,
},
include: {
environments: true,
},
});
if (!organization) {
return;
//don't return deleted projects
if (environment?.project.deletedAt !== null) {
return null;
}
const environment = organization.environments.find((environment) => environment.slug === slug);
return environment;
}
@@ -98,27 +98,36 @@ export class JobListPresenter {
orderBy: [{ title: "asc" }],
});
const latestRuns = await this.#prismaClient.$queryRaw<
{
createdAt: Date;
status: JobRunStatus;
jobId: string;
rn: BigInt;
}[]
>`
SELECT * FROM (
SELECT
"id",
"createdAt",
"status",
"jobId",
ROW_NUMBER() OVER(PARTITION BY "jobId" ORDER BY "createdAt" DESC) as rn
FROM
"public"."JobRun"
WHERE
"jobId" IN (${Prisma.join(jobs.map((j) => j.id))})
) t
WHERE rn = 1;`;
let latestRuns = [] as {
createdAt: Date;
status: JobRunStatus;
jobId: string;
rn: BigInt;
}[];
if (jobs.length > 0) {
latestRuns = await this.#prismaClient.$queryRaw<
{
createdAt: Date;
status: JobRunStatus;
jobId: string;
rn: BigInt;
}[]
>`
SELECT * FROM (
SELECT
"id",
"createdAt",
"status",
"jobId",
ROW_NUMBER() OVER(PARTITION BY "jobId" ORDER BY "createdAt" DESC) as rn
FROM
"JobRun"
WHERE
"jobId" IN (${Prisma.join(jobs.map((j) => j.id))})
) t
WHERE rn = 1;`;
}
return jobs
.flatMap((job) => {
@@ -10,11 +10,16 @@ export class NewOrganizationPresenter {
public async call({ userId }: { userId: User["id"] }) {
const organizations = await this.#prismaClient.organization.findMany({
select: {
projects: {
where: { deletedAt: null },
},
},
where: { members: { some: { userId } } },
});
return {
hasOrganizations: organizations.length > 0,
hasOrganizations: organizations.filter((o) => o.projects.length > 0).length > 0,
};
}
}
@@ -2,6 +2,7 @@ import { PrismaClient } from "@trigger.dev/database";
import { redirect } from "remix-typedjson";
import { prisma } from "~/db.server";
import {
clearCurrentProjectId,
commitCurrentProjectSession,
getCurrentProjectId,
setCurrentProjectId,
@@ -9,6 +10,8 @@ import {
import { logger } from "~/services/logger.server";
import { newProjectPath } from "~/utils/pathBuilder";
import { ProjectPresenter } from "./ProjectPresenter.server";
import { redirectWithErrorMessage } from "~/models/message.server";
import { match } from "assert";
export class OrganizationsPresenter {
#prismaClient: PrismaClient;
@@ -54,7 +57,11 @@ export class OrganizationsPresenter {
});
if (!project) {
throw new Response("Project not found", { status: 404 });
throw redirectWithErrorMessage(
newProjectPath({ slug: organizationSlug }),
request,
"No projects found in organization"
);
}
return { organizations, organization, project };
@@ -75,16 +82,21 @@ export class OrganizationsPresenter {
//no project in session, let's set one
if (!sessionProjectId) {
//no session id and no project slug so we need to select the best project
if (!projectSlug) {
const bestProject = await this.#selectBestProjectForOrganization(organizationSlug, userId);
const bestProject = await this.#selectBestProjectForOrganization(
organizationSlug,
userId,
request
);
const session = await setCurrentProjectId(bestProject.id, request);
throw redirect(request.url, {
headers: { "Set-Cookie": await commitCurrentProjectSession(session) },
});
}
//use the project param to find the project
const project = await prisma.project.findFirst({
//get all the projects
const projects = await prisma.project.findMany({
select: {
id: true,
slug: true,
@@ -93,21 +105,37 @@ export class OrganizationsPresenter {
organization: {
slug: organizationSlug,
},
deletedAt: null,
slug: projectSlug,
},
orderBy: {
updatedAt: "desc",
},
});
if (!project) {
throw redirect(newProjectPath({ slug: organizationSlug }));
if (projects.length === 0) {
throw redirectWithErrorMessage(
newProjectPath({ slug: organizationSlug }),
request,
"No projects in this organization"
);
}
const session = await setCurrentProjectId(project.id, request);
//try get the project which matches the URL
let matchingProject = projects.find((p) => p.slug === projectSlug);
//if there's no matching project, just use the most recently updated one
if (!matchingProject) {
matchingProject = projects[0];
}
//set the session
const session = await setCurrentProjectId(matchingProject.id, request);
throw redirect(request.url, {
headers: { "Set-Cookie": await commitCurrentProjectSession(session) },
});
}
//no project slug, so just return the session id
if (!projectSlug) {
return sessionProjectId;
}
@@ -123,6 +151,7 @@ export class OrganizationsPresenter {
organization: {
slug: organizationSlug,
},
deletedAt: null,
},
});
@@ -142,7 +171,7 @@ export class OrganizationsPresenter {
async #getOrganizations(userId: string) {
const orgs = await this.#prismaClient.organization.findMany({
where: { members: { some: { userId } } },
where: { members: { some: { userId } }, deletedAt: null },
orderBy: { createdAt: "desc" },
select: {
id: true,
@@ -150,6 +179,7 @@ export class OrganizationsPresenter {
title: true,
runsEnabled: true,
projects: {
where: { deletedAt: null },
select: {
id: true,
slug: true,
@@ -202,14 +232,20 @@ export class OrganizationsPresenter {
});
}
async #selectBestProjectForOrganization(organizationSlug: string, userId: string) {
async #selectBestProjectForOrganization(
organizationSlug: string,
userId: string,
request: Request
) {
const projects = await this.#prismaClient.project.findMany({
select: {
id: true,
slug: true,
},
where: {
deletedAt: null,
organization: {
deletedAt: null,
slug: organizationSlug,
members: { some: { userId } },
},
@@ -223,8 +259,7 @@ export class OrganizationsPresenter {
});
if (projects.length === 0) {
logger.info("Didn't find a project in this org", { organizationSlug, projects });
throw new Response("Not Found", { status: 404 });
throw redirect(newProjectPath({ slug: organizationSlug }), request);
}
return projects[0];
@@ -23,6 +23,7 @@ export class ProjectPresenter {
organizationId: true,
createdAt: true,
updatedAt: true,
deletedAt: true,
_count: {
select: {
sources: {
@@ -53,7 +54,7 @@ export class ProjectPresenter {
},
},
},
where: { id, organization: { members: { some: { userId } } } },
where: { id, deletedAt: null, organization: { members: { some: { userId } } } },
});
if (!project) {
@@ -67,6 +68,7 @@ export class ProjectPresenter {
organizationId: project.organizationId,
createdAt: project.createdAt,
updatedAt: project.updatedAt,
deletedAt: project.deletedAt,
hasInactiveExternalTriggers: project._count.sources > 0,
jobCount: project._count.jobs,
httpEndpointCount: project._count.httpEndpoints,
@@ -14,7 +14,7 @@ export class SelectBestProjectPresenter {
const projectId = await getCurrentProjectId(request);
if (projectId) {
const project = await this.#prismaClient.project.findUnique({
where: { id: projectId, organization: { members: { some: { userId } } } },
where: { id: projectId, deletedAt: null, organization: { members: { some: { userId } } } },
include: { organization: true },
});
if (project) {
@@ -28,6 +28,7 @@ export class SelectBestProjectPresenter {
organization: true,
},
where: {
deletedAt: null,
organization: {
members: { some: { userId } },
},
@@ -0,0 +1,264 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { ActionFunction, json } from "@remix-run/server-runtime";
import { redirect } from "remix-typedjson";
import { z } from "zod";
import { InlineCode } from "~/components/code/InlineCode";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Button } from "~/components/primitives/Buttons";
import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { Header2 } from "~/components/primitives/Headers";
import { Hint } from "~/components/primitives/Hint";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { PageHeader, PageTitle, PageTitleRow } from "~/components/primitives/PageHeader";
import { prisma } from "~/db.server";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import {
clearCurrentProjectId,
commitCurrentProjectSession,
} from "~/services/currentProject.server";
import { DeleteProjectService } from "~/services/deleteProject.server";
import { logger } from "~/services/logger.server";
import { requireUserId } from "~/services/session.server";
import { organizationPath, projectPath } from "~/utils/pathBuilder";
export function createSchema(
constraints: {
getSlugMatch?: (slug: string) => { isMatch: boolean; projectSlug: string };
} = {}
) {
return z.discriminatedUnion("action", [
z.object({
action: z.literal("rename"),
projectName: z.string().min(3, "Project name must have at least 3 characters").max(50),
}),
z.object({
action: z.literal("delete"),
projectSlug: z.string().superRefine((slug, ctx) => {
if (constraints.getSlugMatch === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: conform.VALIDATION_UNDEFINED,
});
} else {
const { isMatch, projectSlug } = constraints.getSlugMatch(slug);
if (isMatch) {
return;
}
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The slug must match ${projectSlug}`,
});
}
}),
}),
]);
}
export const action: ActionFunction = async ({ request, params }) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam } = params;
if (!organizationSlug || !projectParam) {
return json({ errors: { body: "organizationSlug is required" } }, { status: 400 });
}
const formData = await request.formData();
const schema = createSchema({
getSlugMatch: (slug) => {
return { isMatch: slug === projectParam, projectSlug: projectParam };
},
});
const submission = parse(formData, { schema });
if (!submission.value || submission.intent !== "submit") {
return json(submission);
}
try {
switch (submission.value.action) {
case "rename": {
await prisma.project.update({
where: {
slug: projectParam,
organization: {
members: {
some: {
userId,
},
},
},
},
data: {
name: submission.value.projectName,
},
});
return redirectWithSuccessMessage(
projectPath({ slug: organizationSlug }, { slug: projectParam }),
request,
`Project renamed to ${submission.value.projectName}`
);
}
case "delete": {
const deleteProjectService = new DeleteProjectService();
try {
await deleteProjectService.call({ projectSlug: projectParam, userId });
//we need to clear the project from the session
const removeProjectIdSession = await clearCurrentProjectId(request);
return redirect(organizationPath({ slug: organizationSlug }), {
headers: { "Set-Cookie": await commitCurrentProjectSession(removeProjectIdSession) },
});
} catch (error: unknown) {
logger.error("Project could not be deleted", {
error: error instanceof Error ? error.message : JSON.stringify(error),
});
return redirectWithErrorMessage(
organizationPath({ slug: organizationSlug }),
request,
`Project ${projectParam} could not be deleted`
);
}
}
}
} catch (error: any) {
return json({ errors: { body: error.message } }, { status: 400 });
}
};
export default function Page() {
const project = useProject();
const lastSubmission = useActionData();
const navigation = useNavigation();
const [renameForm, { projectName }] = useForm({
id: "rename-project",
// TODO: type this
lastSubmission: lastSubmission as any,
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
schema: createSchema(),
});
},
});
const [deleteForm, { projectSlug }] = useForm({
id: "delete-project",
// TODO: type this
lastSubmission: lastSubmission as any,
shouldValidate: "onInput",
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
schema: createSchema({
getSlugMatch: (slug) => ({ isMatch: slug === project.slug, projectSlug: project.slug }),
}),
});
},
});
const isRenameLoading =
navigation.formData?.get("action") === "rename" &&
(navigation.state === "submitting" || navigation.state === "loading");
const isDeleteLoading =
navigation.formData?.get("action") === "delete" &&
(navigation.state === "submitting" || navigation.state === "loading");
return (
<PageContainer>
<PageHeader>
<PageTitleRow>
<PageTitle title={`${project.name} project settings`} />
</PageTitleRow>
</PageHeader>
<PageBody>
<div className="flex flex-col gap-4">
<div>
<Form method="post" {...renameForm.props} className="max-w-md">
<input type="hidden" name="action" value="rename" />
<Fieldset>
<InputGroup>
<Label htmlFor={projectName.id}>Rename your project</Label>
<Input
{...conform.input(projectName, { type: "text" })}
defaultValue={project.name}
placeholder="Your project name"
icon="folder"
autoFocus
/>
<FormError id={projectName.errorId}>{projectName.error}</FormError>
</InputGroup>
<FormButtons
confirmButton={
<Button
type="submit"
variant={"primary/small"}
disabled={isRenameLoading}
LeadingIcon={isRenameLoading ? "spinner-white" : undefined}
>
Rename project
</Button>
}
/>
</Fieldset>
</Form>
</div>
<div>
<Header2 spacing>Danger zone</Header2>
<Form
method="post"
{...deleteForm.props}
className="max-w-md rounded-sm border border-rose-500/40"
>
<input type="hidden" name="action" value="delete" />
<Fieldset className="p-4">
<InputGroup>
<Label htmlFor={projectSlug.id}>Delete project</Label>
<Input
{...conform.input(projectSlug, { type: "text" })}
placeholder="Your project slug"
icon="warning"
autoFocus
/>
<FormError id={projectSlug.errorId}>{projectSlug.error}</FormError>
<FormError>{deleteForm.error}</FormError>
<Hint>
This change is irreversible, so please be certain. Type in the Project slug
<InlineCode variant="extra-small">{project.slug}</InlineCode> and then press
Delete.
</Hint>
</InputGroup>
<FormButtons
confirmButton={
<Button
type="submit"
variant={"danger/small"}
LeadingIcon={isDeleteLoading ? "spinner-white" : "trash-can"}
leadingIconClassName="text-white"
disabled={isDeleteLoading}
>
Delete project
</Button>
}
/>
</Fieldset>
</Form>
</div>
</div>
</PageBody>
</PageContainer>
);
}
@@ -1,16 +1,9 @@
import { Outlet } from "@remix-run/react";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson } from "remix-typedjson";
import invariant from "tiny-invariant";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
import { BreadcrumbLink } from "~/components/navigation/Breadcrumb";
import { organizationMatchId, useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useTypedMatchData } from "~/hooks/useTypedMatchData";
import { ProjectPresenter } from "~/presenters/ProjectPresenter.server";
import { commitCurrentProjectSession, setCurrentProjectId } from "~/services/currentProject.server";
import { requireUserId } from "~/services/session.server";
import { telemetry } from "~/services/telemetry.server";
import { Handle } from "~/utils/handle";
import { projectPath } from "~/utils/pathBuilder";
import { loader as orgLoader } from "../_app.orgs.$organizationSlug/route";
@@ -0,0 +1,270 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { ActionFunction, json } from "@remix-run/server-runtime";
import { redirect } from "remix-typedjson";
import { r } from "tar";
import { z } from "zod";
import { InlineCode } from "~/components/code/InlineCode";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Button } from "~/components/primitives/Buttons";
import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { Header2 } from "~/components/primitives/Headers";
import { Hint } from "~/components/primitives/Hint";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { PageHeader, PageTitle, PageTitleRow } from "~/components/primitives/PageHeader";
import { prisma } from "~/db.server";
import { useOrganization } from "~/hooks/useOrganizations";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import {
clearCurrentProjectId,
commitCurrentProjectSession,
} from "~/services/currentProject.server";
import { DeleteOrganizationService } from "~/services/deleteOrganization.server";
import { logger } from "~/services/logger.server";
import { requireUserId } from "~/services/session.server";
import { organizationPath, organizationSettingsPath, rootPath } from "~/utils/pathBuilder";
export function createSchema(
constraints: {
getSlugMatch?: (slug: string) => { isMatch: boolean; organizationSlug: string };
} = {}
) {
return z.discriminatedUnion("action", [
z.object({
action: z.literal("rename"),
organizationName: z
.string()
.min(3, "Organization name must have at least 3 characters")
.max(50),
}),
z.object({
action: z.literal("delete"),
organizationSlug: z.string().superRefine((slug, ctx) => {
if (constraints.getSlugMatch === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: conform.VALIDATION_UNDEFINED,
});
} else {
const { isMatch, organizationSlug } = constraints.getSlugMatch(slug);
if (isMatch) {
return;
}
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The slug must match ${organizationSlug}`,
});
}
}),
}),
]);
}
export const action: ActionFunction = async ({ request, params }) => {
const userId = await requireUserId(request);
const { organizationSlug } = params;
if (!organizationSlug) {
return json({ errors: { body: "organizationSlug is required" } }, { status: 400 });
}
const formData = await request.formData();
const schema = createSchema({
getSlugMatch: (slug) => {
return { isMatch: slug === organizationSlug, organizationSlug };
},
});
const submission = parse(formData, { schema });
if (!submission.value || submission.intent !== "submit") {
return json(submission);
}
try {
switch (submission.value.action) {
case "rename": {
await prisma.organization.update({
where: {
slug: organizationSlug,
members: {
some: {
userId,
},
},
},
data: {
title: submission.value.organizationName,
},
});
return redirectWithSuccessMessage(
organizationPath({ slug: organizationSlug }),
request,
`Organization renamed to ${submission.value.organizationName}`
);
}
case "delete": {
const deleteOrganizationService = new DeleteOrganizationService();
try {
await deleteOrganizationService.call({ organizationSlug, userId, request });
//we need to clear the project from the session
const removeProjectIdSession = await clearCurrentProjectId(request);
return redirect(rootPath(), {
headers: {
"Set-Cookie": await commitCurrentProjectSession(removeProjectIdSession),
},
});
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
logger.error("Organization could not be deleted", {
error: errorMessage,
});
return redirectWithErrorMessage(
organizationSettingsPath({ slug: organizationSlug }),
request,
errorMessage
);
}
}
}
} catch (error: any) {
return json({ errors: { body: error.message } }, { status: 400 });
}
};
export default function Page() {
const organization = useOrganization();
const lastSubmission = useActionData();
const navigation = useNavigation();
const [renameForm, { organizationName }] = useForm({
id: "rename-organization",
// TODO: type this
lastSubmission: lastSubmission as any,
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
schema: createSchema(),
});
},
});
const [deleteForm, { organizationSlug }] = useForm({
id: "delete-organization",
// TODO: type this
lastSubmission: lastSubmission as any,
shouldValidate: "onInput",
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
schema: createSchema({
getSlugMatch: (slug) => ({
isMatch: slug === organization.slug,
organizationSlug: organization.slug,
}),
}),
});
},
});
const isRenameLoading =
navigation.formData?.get("action") === "rename" &&
(navigation.state === "submitting" || navigation.state === "loading");
const isDeleteLoading =
navigation.formData?.get("action") === "delete" &&
(navigation.state === "submitting" || navigation.state === "loading");
return (
<PageContainer>
<PageHeader>
<PageTitleRow>
<PageTitle title={`${organization.title} organization settings`} />
</PageTitleRow>
</PageHeader>
<PageBody>
<div className="flex flex-col gap-4">
<div>
<Form method="post" {...renameForm.props} className="max-w-md">
<input type="hidden" name="action" value="rename" />
<Fieldset>
<InputGroup>
<Label htmlFor={organizationName.id}>Rename your organization</Label>
<Input
{...conform.input(organizationName, { type: "text" })}
defaultValue={organization.title}
placeholder="Your organization name"
icon="folder"
autoFocus
/>
<FormError id={organizationName.errorId}>{organizationName.error}</FormError>
</InputGroup>
<FormButtons
confirmButton={
<Button
type="submit"
variant={"primary/small"}
disabled={isRenameLoading}
LeadingIcon={isRenameLoading ? "spinner-white" : undefined}
>
Rename organization
</Button>
}
/>
</Fieldset>
</Form>
</div>
<div>
<Header2 spacing>Danger zone</Header2>
<Form
method="post"
{...deleteForm.props}
className="max-w-md rounded-sm border border-rose-500/40"
>
<input type="hidden" name="action" value="delete" />
<Fieldset className="p-4">
<InputGroup>
<Label htmlFor={organizationSlug.id}>Delete organization</Label>
<Input
{...conform.input(organizationSlug, { type: "text" })}
placeholder="Your organization slug"
icon="warning"
autoFocus
/>
<FormError id={organizationSlug.errorId}>{organizationSlug.error}</FormError>
<FormError>{deleteForm.error}</FormError>
<Hint>
This change is irreversible, so please be certain. Type in the Organization slug
<InlineCode variant="extra-small">{organization.slug}</InlineCode> and then
press Delete.
</Hint>
</InputGroup>
<FormButtons
confirmButton={
<Button
type="submit"
variant={"danger/small"}
LeadingIcon={isDeleteLoading ? "spinner-white" : "trash-can"}
leadingIconClassName="text-white"
disabled={isDeleteLoading}
>
Delete organization
</Button>
}
/>
</Fieldset>
</Form>
</div>
</div>
</PageBody>
</PageContainer>
);
}
@@ -1,12 +1,14 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import type { ActionFunction } from "@remix-run/node";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { z } from "zod";
import { MainCenteredContainer } from "~/components/layout/AppLayout";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
@@ -14,11 +16,44 @@ import { FormTitle } from "~/components/primitives/FormTitle";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { useOrganization } from "~/hooks/useOrganizations";
import { prisma } from "~/db.server";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { createProject } from "~/models/project.server";
import { requireUserId } from "~/services/session.server";
import { organizationPath, projectPath } from "~/utils/pathBuilder";
import { OrganizationParamsSchema, organizationPath, projectPath } from "~/utils/pathBuilder";
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const { organizationSlug } = OrganizationParamsSchema.parse(params);
const organization = await prisma.organization.findUnique({
where: { slug: organizationSlug, members: { some: { userId } } },
select: {
id: true,
title: true,
_count: {
select: {
projects: {
where: { deletedAt: null },
},
},
},
},
});
if (!organization) {
throw new Response(null, { status: 404, statusText: "Organization not found" });
}
return typedjson({
organization: {
id: organization.id,
title: organization.title,
slug: organizationSlug,
projectsCount: organization._count.projects,
},
});
}
const schema = z.object({
projectName: z.string().min(3, "Project name must have at least 3 characters").max(50),
@@ -54,7 +89,7 @@ export const action: ActionFunction = async ({ request, params }) => {
};
export default function NewOrganizationPage() {
const organization = useOrganization();
const { organization } = useTypedLoaderData<typeof loader>();
const lastSubmission = useActionData();
const [form, { projectName }] = useForm({
@@ -71,10 +106,15 @@ export default function NewOrganizationPage() {
<div>
<FormTitle
LeadingIcon="folder"
title="Create a new Project"
description="Create a new Project to help you organize the Jobs you create."
title="Create a new project"
description={`This will create a new project in your "${organization.title}" organization. `}
/>
<Form method="post" {...form.props}>
{organization.projectsCount === 0 && (
<Callout variant="info" className="mb-4">
Organizations require at least one project, please create one to continue.
</Callout>
)}
<Fieldset>
<InputGroup>
<Label htmlFor={projectName.id}>Project name</Label>
@@ -93,9 +133,11 @@ export default function NewOrganizationPage() {
</Button>
}
cancelButton={
<LinkButton to={organizationPath(organization)} variant={"secondary/small"}>
Cancel
</LinkButton>
organization.projectsCount > 0 ? (
<LinkButton to={organizationPath(organization)} variant={"secondary/small"}>
Cancel
</LinkButton>
) : undefined
}
/>
</Fieldset>
+12 -3
View File
@@ -3,7 +3,7 @@ import { parse } from "@conform-to/zod";
import { RadioGroup } from "@radix-ui/react-radio-group";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { MainCenteredContainer } from "~/components/layout/AppLayout";
@@ -23,7 +23,7 @@ import { createOrganization } from "~/models/organization.server";
import { NewOrganizationPresenter } from "~/presenters/NewOrganizationPresenter.server";
import { commitCurrentProjectSession, setCurrentProjectId } from "~/services/currentProject.server";
import { requireUserId } from "~/services/session.server";
import { plansPath, projectPath, rootPath, selectPlanPath } from "~/utils/pathBuilder";
import { projectPath, rootPath, selectPlanPath } from "~/utils/pathBuilder";
const schema = z.object({
orgName: z.string().min(3).max(50),
@@ -86,6 +86,7 @@ export default function NewOrganizationPage() {
const { hasOrganizations } = useTypedLoaderData<typeof loader>();
const lastSubmission = useActionData();
const { isManagedCloud } = useFeatures();
const navigation = useNavigation();
const [form, { orgName, projectName }] = useForm({
id: "create-organization",
@@ -95,8 +96,11 @@ export default function NewOrganizationPage() {
return parse(formData, { schema });
},
shouldRevalidate: "onSubmit",
shouldValidate: "onSubmit",
});
const isLoading = navigation.state === "submitting" || navigation.state === "loading";
return (
<MainCenteredContainer className="max-w-[22rem]">
<FormTitle LeadingIcon="organization" title="Create an Organization" />
@@ -161,7 +165,12 @@ export default function NewOrganizationPage() {
<FormButtons
confirmButton={
<Button type="submit" variant={"primary/small"} TrailingIcon="arrow-right">
<Button
type="submit"
variant={"primary/small"}
TrailingIcon="arrow-right"
disabled={isLoading}
>
Create
</Button>
}
@@ -171,7 +171,7 @@ export default function Page() {
<Checkbox
id="marketingEmails"
{...conform.input(marketingEmails, { type: "checkbox" })}
label="Receive product updates"
label="Receive onboarding emails"
variant="simple/small"
defaultChecked={user.marketingEmails}
/>
@@ -1,6 +1,6 @@
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { z } from "zod";
import { DeleteEndpointIndexService } from "~/services/endpoints/deleteEndpointService";
import { DeleteEndpointService } from "~/services/endpoints/deleteEndpointService";
import { IndexEndpointService } from "~/services/endpoints/indexEndpoint.server";
import { requireUserId } from "~/services/session.server";
import { workerQueue } from "~/services/worker.server";
@@ -42,7 +42,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
return json({ success: true });
}
case "delete": {
const service = new DeleteEndpointIndexService();
const service = new DeleteEndpointService();
await service.call(endpointParam, userId);
return json({ success: true });
}
@@ -0,0 +1,97 @@
import { parse } from "@conform-to/zod";
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { PlainClient, uiComponent } from "@team-plain/typescript-sdk";
import { inspect } from "util";
import { z } from "zod";
import { env } from "~/env.server";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { requireUser } from "~/services/session.server";
import crypto from "node:crypto";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { prisma } from "~/db.server";
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
import { Header1 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { TextLink } from "~/components/primitives/TextLink";
import { LinkButton } from "~/components/primitives/Buttons";
import { rootPath } from "~/utils/pathBuilder";
import { FormTitle } from "~/components/primitives/FormTitle";
import { EnvelopeIcon } from "@heroicons/react/24/solid";
export const ParamsSchema = z.object({
userId: z.string(),
token: z.string(),
});
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { userId, token } = ParamsSchema.parse(params);
try {
const user = await prisma.user.findUnique({
where: { id: userId },
});
if (!user) {
return typedjson({
success: false as const,
message: "User not found",
});
}
//check that the token is valid for the userId
const hashedUserId = crypto
.createHash("sha256")
.update(`${userId}-${env.MAGIC_LINK_SECRET}`)
.digest("hex");
if (hashedUserId !== token) {
return typedjson({
success: false as const,
message: "This unsubscribe link was invalid so we can't unsubscribe you.",
});
}
await prisma.user.update({
where: { id: userId },
data: { marketingEmails: false },
});
return typedjson({ success: true as const, email: user.email });
} catch (e) {
const errorMessage = e instanceof Error ? e.message : JSON.stringify(e);
return typedjson({ success: false as const, message: errorMessage });
}
};
export default function Page() {
const result = useTypedLoaderData<typeof loader>();
return (
<AppContainer>
<MainCenteredContainer className="max-w-[22rem]">
{result.success ? (
<div>
<FormTitle LeadingIcon="envelope" title="Unsubscribed" />
<Paragraph spacing>
You have unsubscribed from onboarding emails, {result.email}.
</Paragraph>
<LinkButton variant="primary/medium" to={rootPath()}>
Dashboard
</LinkButton>
</div>
) : (
<div>
<FormTitle LeadingIcon="envelope" title="Unsubscribe failed" />
<Paragraph spacing>{result.message}</Paragraph>
<Paragraph spacing>
If you believe this is a bug, please{" "}
<TextLink href="https://trigger.dev/contact">contact support</TextLink>.
</Paragraph>
<LinkButton variant="primary/medium" to={rootPath()}>
Dashboard
</LinkButton>
</div>
)}
</MainCenteredContainer>
</AppContainer>
);
}
@@ -31,3 +31,9 @@ export async function setCurrentProjectId(id: string, request: Request) {
session.set("currentProjectId", id);
return session;
}
export async function clearCurrentProjectId(request: Request) {
const session = await getCurrentProjectSession(request);
session.unset("currentProjectId");
return session;
}
@@ -0,0 +1,97 @@
import { DateFormatter } from "@internationalized/date";
import { PrismaClient } from "@trigger.dev/database";
import { prisma } from "~/db.server";
import { featuresForRequest } from "~/features.server";
import { BillingService } from "./billing.server";
import { DeleteProjectService } from "./deleteProject.server";
export class DeleteOrganizationService {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call({
organizationSlug,
userId,
request,
}: {
organizationSlug: string;
userId: string;
request: Request;
}) {
const organization = await this.#prismaClient.organization.findFirst({
include: {
projects: true,
members: true,
},
where: {
slug: organizationSlug,
members: { some: { userId: userId } },
},
});
if (!organization) {
throw new Error("Organization not found");
}
if (organization.deletedAt) {
throw new Error("Organization already deleted");
}
//check if they have an active subscription
const { isManagedCloud } = featuresForRequest(request);
const billingPresenter = new BillingService(isManagedCloud);
const currentPlan = await billingPresenter.currentPlan(organization.id);
if (currentPlan && currentPlan.subscription && currentPlan.subscription.isPaying) {
//they've cancelled and that date hasn't passed yet
if (
currentPlan.subscription.canceledAt &&
new Date(currentPlan.subscription.canceledAt) > new Date()
) {
//a dateformatter that produces results like "Jan 1 2024"
const dateFormatter = new DateFormatter("en-us", {
year: "numeric",
month: "short",
day: "numeric",
});
throw new Error(
`This Organization has a canceled subscription. You can delete it when the cancelation date (${dateFormatter.format(
new Date(currentPlan.subscription.canceledAt)
)}) is in the past.`
);
}
throw new Error("You can't delete an Organization that has an active subscription");
}
// loop through the projects and delete them
const projectDeleteService = new DeleteProjectService();
for (const project of organization.projects) {
await projectDeleteService.call({ projectId: project.id, userId });
}
//set all the integrations to disabled
await this.#prismaClient.integrationConnection.updateMany({
where: {
organizationId: organization.id,
},
data: {
enabled: false,
},
});
//mark the organization as deleted
await this.#prismaClient.organization.update({
where: {
id: organization.id,
},
data: {
runsEnabled: false,
deletedAt: new Date(),
},
});
}
}
@@ -0,0 +1,141 @@
import { PrismaClient } from "@trigger.dev/database";
import { prisma } from "~/db.server";
import { DeleteEndpointService } from "./endpoints/deleteEndpointService";
import { logger } from "./logger.server";
import { DisableScheduleSourceService } from "./schedules/disableScheduleSource.server";
type Options = ({ projectId: string } | { projectSlug: string }) & {
userId: string;
};
export class DeleteProjectService {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call(options: Options) {
const projectId = await this.#getProjectId(options);
const project = await this.#prismaClient.project.findFirst({
include: {
environments: {
include: {
endpoints: true,
},
},
jobs: {
where: { deletedAt: null },
include: {
aliases: {
where: {
name: "latest",
},
include: {
version: true,
},
take: 1,
},
},
},
organization: true,
},
where: {
id: projectId,
organization: { members: { some: { userId: options.userId } } },
},
});
if (!project) {
throw new Error("Project not found");
}
if (project.deletedAt) {
return;
}
//disable and delete all jobs
const service = new DisableScheduleSourceService();
for (const environment of project.environments) {
//disable the event dispatchers
await this.#prismaClient.eventDispatcher.updateMany({
where: {
environmentId: environment.id,
},
data: {
enabled: false,
},
});
const eventDispatchers = await this.#prismaClient.eventDispatcher.findMany({
where: {
environmentId: environment.id,
},
});
logger.info("Deleting jobs", { jobs: project.jobs });
for (const job of project.jobs) {
//disable all the job versions
await this.#prismaClient.jobVersion.updateMany({
where: {
jobId: job.id,
},
data: {
status: "DISABLED",
},
});
await this.#prismaClient.job.update({
where: {
id: job.id,
},
data: {
deletedAt: new Date(),
},
});
//disable scheduled sources
for (const eventDispatcher of eventDispatchers) {
await service.call({
key: job.id,
dispatcher: eventDispatcher,
});
}
}
}
//delete all endpoints
const deleteEndpointService = new DeleteEndpointService();
for (const environment of project.environments) {
for (const endpoint of environment.endpoints) {
await deleteEndpointService.call(endpoint.id, options.userId);
}
}
//mark the project as deleted
await this.#prismaClient.project.update({
where: {
id: project.id,
},
data: {
deletedAt: new Date(),
},
});
}
async #getProjectId(options: Options) {
if ("projectId" in options) {
return options.projectId;
}
const { id } = await this.#prismaClient.project.findFirstOrThrow({
select: {
id: true,
},
where: {
slug: options.projectSlug,
},
});
return id;
}
}
@@ -1,7 +1,7 @@
import { PrismaClient } from "@trigger.dev/database";
import { prisma } from "~/db.server";
export class DeleteEndpointIndexService {
export class DeleteEndpointService {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
@@ -577,6 +577,13 @@ export class IntegrationAuthRepository {
throw new Error(`Connection ${connectionId} not found`);
}
if (!connection.enabled) {
logger.info("Connection is disabled", {
connection,
});
return;
}
let customOAuthClient: OAuthClient | undefined;
if (connection.integration.customClientReference) {
const secretStore = getSecretStore(env.SECRET_STORE);
@@ -687,9 +694,15 @@ export class IntegrationAuthRepository {
if (connection.expiresAt) {
const refreshBy = new Date(connection.expiresAt.getTime() - tokenRefreshThreshold * 1000);
if (refreshBy < new Date()) {
connection = await this.refreshConnection({
const refreshedConnection = await this.refreshConnection({
connectionId: connection.id,
});
if (!refreshedConnection) {
return;
}
connection = refreshedConnection;
}
}
@@ -2,6 +2,7 @@ import type { Job } from "@trigger.dev/database";
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import { telemetry } from "../telemetry.server";
import { logger } from "../logger.server";
export class DeleteJobService {
#prismaClient: PrismaClient;
@@ -25,6 +26,7 @@ export class DeleteJobService {
const allDisabled = latestVersions.every((alias) => alias.version.status === "DISABLED");
if (!allDisabled) {
logger.info("Not all latest versions are disabled, cannot delete job", { jobId: job.id });
throw new Error("All latest versions must be disabled before deleting a job");
}
@@ -207,6 +207,19 @@ export class PerformRunExecutionV3Service {
forceYieldCoordinator.deregisterRun(run.id);
//if the run has been canceled while it's being executed, we shouldn't do anything more
const updatedRun = await this.#prismaClient.jobRun.findUnique({
select: {
status: true,
},
where: {
id: run.id,
},
});
if (!updatedRun || updatedRun.status === "CANCELED") {
return;
}
if (!response) {
return await this.#failRunExecutionWithRetry(
run,
+8
View File
@@ -131,6 +131,10 @@ export function organizationBillingPath(organization: OrgForPath) {
return `${organizationPath(organization)}/billing`;
}
export function organizationSettingsPath(organization: OrgForPath) {
return `${organizationPath(organization)}/settings`;
}
export function usagePath(organization: OrgForPath) {
return `${organizationPath(organization)}/billing`;
}
@@ -216,6 +220,10 @@ export function projectEventsPath(organization: OrgForPath, project: ProjectForP
return `${projectPath(organization, project)}/events`;
}
export function projectSettingsPath(organization: OrgForPath, project: ProjectForPath) {
return `${projectPath(organization, project)}/settings`;
}
export function projectEventPath(
organization: OrgForPath,
project: ProjectForPath,
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/airtable
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/airtable",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev integration for airtable",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"airtable": "^0.12.1",
"zod": "3.22.3"
},
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/github
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "2.3.17",
"version": "2.3.18",
"description": "The official GitHub integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -30,8 +30,8 @@
"@octokit/request-error": "^5.0.1",
"@octokit/webhooks": "^12.0.10",
"octokit": "^3.1.2",
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"zod": "3.22.3"
},
"engines": {
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/linear
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/linear",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev integration for @linear/sdk",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@linear/sdk": "^8.0.0",
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"zod": "3.22.3"
},
"engines": {
+8
View File
@@ -1,5 +1,13 @@
# @trigger.dev/slack
## 2.3.18
### Patch Changes
- d69e4e71: Retry 400 status code, OpenAI returns this sometimes for no reason...
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/openai",
"version": "2.3.17",
"version": "2.3.18",
"description": "The official OpenAI integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -42,8 +42,8 @@
},
"dependencies": {
"openai": "^4.16.1",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.17"
"@trigger.dev/sdk": "workspace:^2.3.18",
"@trigger.dev/integration-kit": "workspace:^2.3.18"
},
"engines": {
"node": ">=18.0.0"
+29 -27
View File
@@ -60,11 +60,11 @@ function createTaskUsageProperties(
},
...("completion_tokens" in usage
? [
{
label: "Completion Usage",
text: String(usage.completion_tokens),
},
]
{
label: "Completion Usage",
text: String(usage.completion_tokens),
},
]
: []),
];
}
@@ -83,35 +83,35 @@ function createTaskRateLimitProperties(headers: Headers | undefined) {
return [
...(remainingRequests
? [
{
label: "Remaining Requests",
text: remainingRequests ?? "Unknown",
},
]
{
label: "Remaining Requests",
text: remainingRequests ?? "Unknown",
},
]
: []),
...(resetRequests
? [
{
label: "Reset Requests",
text: resetRequests ?? "Unknown",
},
]
{
label: "Reset Requests",
text: resetRequests ?? "Unknown",
},
]
: []),
...(remainingTokens
? [
{
label: "Remaining Tokens",
text: remainingTokens ?? "Unknown",
},
]
{
label: "Remaining Tokens",
text: remainingTokens ?? "Unknown",
},
]
: []),
...(resetTokens
? [
{
label: "Reset Tokens",
text: resetTokens ?? "Unknown",
},
]
{
label: "Reset Tokens",
text: resetTokens ?? "Unknown",
},
]
: []),
];
}
@@ -128,6 +128,8 @@ export function handleOpenAIError(error: unknown) {
}
return (
//sometimes OpenAI returns a 400 that when retried becomes a 200…
error.status === 400 ||
error.status === 429 ||
error.status === 408 ||
error.status === 409 ||
@@ -295,7 +297,7 @@ const requestOptionsKeys: KeysEnum<OpenAIRequestOptions> = {
export const isRequestOptions = (obj: unknown): obj is OpenAIRequestOptions => {
return (
typeof obj === 'object' &&
typeof obj === "object" &&
obj !== null &&
!isEmptyObj(obj) &&
Object.keys(obj).every((k) => hasOwn(requestOptionsKeys, k))
@@ -310,4 +312,4 @@ function isEmptyObj(obj: Object | null | undefined): boolean {
function hasOwn(obj: Object, key: string): boolean {
return Object.prototype.hasOwnProperty.call(obj, key);
}
}
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/plain
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/plain",
"version": "2.3.17",
"version": "2.3.18",
"description": "The official Plain.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"@team-plain/typescript-sdk": "^2.7.0"
},
"engines": {
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/replicate
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/replicate",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev integration for replicate",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"replicate": "^0.18.1",
"zod": "3.22.3"
},
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/resend
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "2.3.17",
"version": "2.3.18",
"description": "The official Resend.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"resend": "^2.1.0"
},
"engines": {
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/sendgrid
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sendgrid",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev integration for @sendgrid/mail",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@sendgrid/mail": "^7.7.0",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.17"
"@trigger.dev/sdk": "workspace:^2.3.18",
"@trigger.dev/integration-kit": "workspace:^2.3.18"
},
"engines": {
"node": ">=16.8.0"
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/shopify
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/shopify",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev integration for @shopify/shopify-api",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@shopify/shopify-api": "^8.0.2",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.18",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"zod": "3.22.3"
},
"engines": {
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/slack
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/slack",
"version": "2.3.17",
"version": "2.3.18",
"description": "The official Slack integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
},
"dependencies": {
"@slack/web-api": "^6.8.1",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.18",
"zod": "3.22.3"
},
"engines": {
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/stripe
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/stripe",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev integration for stripe",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"stripe": "^12.14.0",
"zod": "3.22.3"
},
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/supabase
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/supabase",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev integration for @supabase/supabase-js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@supabase/supabase-js": "^2.26.0",
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"supabase-management-js": "^1.0.0",
"zod": "3.22.3"
},
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/typeform
## 2.3.18
### Patch Changes
- @trigger.dev/integration-kit@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/typeform",
"version": "2.3.17",
"version": "2.3.18",
"description": "The official Typeform integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/integration-kit": "workspace:^2.3.18",
"@trigger.dev/sdk": "workspace:^2.3.18",
"@typeform/api-client": "^1.8.0",
"zod": "3.22.3"
},
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/astro
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/astro",
"description": "An Astro-native integration for Trigger.dev background jobs platform",
"version": "2.3.17",
"version": "2.3.18",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
@@ -20,7 +20,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^2.3.17"
"@trigger.dev/sdk": "workspace:^2.3.18"
},
"devDependencies": {
"astro": "^3.0.12",
+6
View File
@@ -1,5 +1,11 @@
# trigger.dev
## 1.0.7
### Patch Changes
- @trigger.dev/core@2.3.18
## 1.0.6
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "trigger.dev",
"version": "1.0.6",
"version": "1.0.7",
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+7
View File
@@ -1,5 +1,12 @@
# create-trigger
## 2.3.18
### Patch Changes
- @trigger.dev/core@2.3.18
- @trigger.dev/yalt@2.3.18
## 2.3.17
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/cli",
"version": "2.3.17",
"version": "2.3.18",
"description": "The Trigger.dev CLI",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+2
View File
@@ -1,5 +1,7 @@
# @trigger.dev/core-backend
## 2.3.18
## 2.3.17
## 2.3.16
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/core-backend",
"version": "2.3.17",
"version": "2.3.18",
"description": "Core code used across `@trigger.dev/sdk` and Trigger.dev server",
"license": "MIT",
"main": "./dist/index.js",
+2
View File
@@ -1,5 +1,7 @@
# internal-platform
## 2.3.18
## 2.3.17
## 2.3.16
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/core",
"version": "2.3.17",
"version": "2.3.18",
"description": "Core code used across the Trigger.dev SDK and platform",
"license": "MIT",
"main": "./dist/index.js",
@@ -0,0 +1,5 @@
-- DropIndex
DROP INDEX "idx_jobrun_jobid_createdat";
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "deletedAt" TIMESTAMP(3);
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "idx_jobrun_jobId_createdAt" ON "JobRun"("jobId", "createdAt" DESC);
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Organization" ADD COLUMN "deletedAt" TIMESTAMP(3);
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "IntegrationConnection" ADD COLUMN "enabled" BOOLEAN NOT NULL DEFAULT true;
+11 -4
View File
@@ -109,8 +109,9 @@ model Organization {
maximumExecutionTimePerRunInMs Int @default(900000) // 15 minutes
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
companySize String?
@@ -271,6 +272,9 @@ model IntegrationConnection {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
/// If enabled is false, OAuth refreshing will not be attempted
enabled Boolean @default(true)
runConnections RunConnection[]
}
@@ -398,8 +402,9 @@ model Project {
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
organizationId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
environments RuntimeEnvironment[]
endpoints Endpoint[]
@@ -848,6 +853,8 @@ model JobRun {
statuses JobRunStatusRecord[]
autoYieldExecution JobRunAutoYieldExecution[]
subscriptions JobRunSubscription[]
@@index([jobId, createdAt(sort: Desc)], map: "idx_jobrun_jobId_createdAt")
}
enum JobRunStatus {
+2
View File
@@ -1,5 +1,7 @@
# @trigger.dev/eslint-plugin
## 2.3.18
## 2.3.17
## 2.3.16
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/eslint-plugin",
"version": "2.3.17",
"version": "2.3.18",
"description": "ESLint plugin with trigger.dev best practices",
"keywords": [
"eslint",
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/express
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/express",
"version": "2.3.17",
"version": "2.3.18",
"description": "Official Express adapter for Trigger.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -23,7 +23,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.18",
"@trigger.dev/tsconfig": "workspace:*",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
@@ -39,7 +39,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^2.3.17"
"@trigger.dev/sdk": "workspace:^2.3.18"
},
"dependencies": {
"debug": "^4.3.4",
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/hono
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/hono",
"version": "2.3.17",
"version": "2.3.18",
"description": "A Trigger.dev adapter for Hono.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -32,7 +32,7 @@
},
"peerDependencies": {
"hono": "3.x",
"@trigger.dev/sdk": "workspace:^2.3.17"
"@trigger.dev/sdk": "workspace:^2.3.18"
},
"devDependencies": {
"@trigger.dev/tsconfig": "workspace:*",
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/integration-kit
## 2.3.18
### Patch Changes
- @trigger.dev/core@2.3.18
## 2.3.17
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/integration-kit",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev Integration Kit has helpers to make creating integrations easier",
"license": "MIT",
"main": "./dist/index.js",
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/nestjs
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/nestjs",
"version": "2.3.17",
"version": "2.3.18",
"description": "Official NestJS adapter for Trigger.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -23,7 +23,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.18",
"@trigger.dev/tsconfig": "workspace:*",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
@@ -41,7 +41,7 @@
},
"peerDependencies": {
"@nestjs/common": ">=10.0.0",
"@trigger.dev/sdk": "workspace:^2.3.17"
"@trigger.dev/sdk": "workspace:^2.3.18"
},
"dependencies": {
"@nestjs/common": "^10.2.4",
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/nextjs
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/nextjs",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev Next.js integration",
"license": "MIT",
"main": "./dist/index.js",
@@ -41,7 +41,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.18",
"next": ">=12.0.0"
},
"dependencies": {
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/react
## 2.3.18
### Patch Changes
- @trigger.dev/core@2.3.18
## 2.3.17
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/react",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev React SDK",
"license": "MIT",
"types": "dist/index.d.ts",
@@ -27,7 +27,7 @@
},
"dependencies": {
"@tanstack/react-query": "5.0.0-beta.2",
"@trigger.dev/core": "workspace:^2.3.17",
"@trigger.dev/core": "workspace:^2.3.18",
"debug": "^4.3.4",
"zod": "3.22.3"
},
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/remix
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/remix",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev Remix integration",
"license": "MIT",
"main": "./dist/index.js",
@@ -39,7 +39,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^2.3.17",
"@trigger.dev/sdk": "workspace:^2.3.18",
"@remix-run/server-runtime": ">1.19.0"
},
"dependencies": {
+6
View File
@@ -1,5 +1,11 @@
# @trigger.dev/sveltekit
## 2.3.18
### Patch Changes
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sveltekit",
"version": "2.3.17",
"version": "2.3.18",
"description": "Trigger.dev svelteKit integration",
"license": "MIT",
"main": "./dist/index.js",
@@ -39,7 +39,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^2.3.17"
"@trigger.dev/sdk": "workspace:^2.3.18"
},
"dependencies": {
"debug": "^4.3.4"
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/testing
## 2.3.18
### Patch Changes
- @trigger.dev/core@2.3.18
- @trigger.dev/sdk@2.3.18
## 2.3.17
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/testing",
"description": "A collection of useful tools to write tests for Trigger.dev.",
"version": "2.3.17",
"version": "2.3.18",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+7
View File
@@ -1,5 +1,12 @@
# @trigger.dev/sdk
## 2.3.18
### Patch Changes
- @trigger.dev/core@2.3.18
- @trigger.dev/core-backend@2.3.18
## 2.3.17
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sdk",
"version": "2.3.17",
"version": "2.3.18",
"description": "trigger.dev Node.JS SDK",
"license": "MIT",
"main": "./dist/index.js",
@@ -30,8 +30,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/core": "workspace:^2.3.17",
"@trigger.dev/core-backend": "workspace:^2.3.17",
"@trigger.dev/core": "workspace:^2.3.18",
"@trigger.dev/core-backend": "workspace:^2.3.18",
"chalk": "^5.2.0",
"cronstrue": "^2.21.0",
"debug": "^4.3.4",
+2
View File
@@ -1,5 +1,7 @@
# @trigger.dev/yalt
## 2.3.18
## 2.3.17
## 2.3.16
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/yalt",
"version": "2.3.17",
"version": "2.3.18",
"description": "yalt.dev client library",
"license": "MIT",
"main": "./dist/index.js",
+36 -36
View File
@@ -430,7 +430,7 @@ importers:
devDependencies:
eslint: 8.31.0
eslint-config-prettier: 8.6.0_eslint@8.31.0
eslint-config-turbo: 1.11.3_eslint@8.31.0
eslint-config-turbo: 1.12.3_eslint@8.31.0
eslint-plugin-react: 7.31.8_eslint@8.31.0
typescript: 4.9.4
@@ -458,8 +458,8 @@ importers:
integrations/airtable:
specifiers:
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': 16.x
@@ -488,8 +488,8 @@ importers:
'@octokit/types': ^12.4.0
'@octokit/webhooks': ^12.0.10
'@octokit/webhooks-types': ^7.3.1
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': '18'
@@ -519,8 +519,8 @@ importers:
integrations/linear:
specifiers:
'@linear/sdk': ^8.0.0
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': 16.x
@@ -543,8 +543,8 @@ importers:
integrations/openai:
specifiers:
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/jest': ^29.5.3
@@ -573,8 +573,8 @@ importers:
integrations/plain:
specifiers:
'@team-plain/typescript-sdk': ^2.7.0
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': '18'
@@ -595,8 +595,8 @@ importers:
integrations/replicate:
specifiers:
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': 16.x
@@ -620,8 +620,8 @@ importers:
integrations/resend:
specifiers:
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': '18'
@@ -644,8 +644,8 @@ importers:
integrations/sendgrid:
specifiers:
'@sendgrid/mail': ^7.7.0
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': 16.x
@@ -667,8 +667,8 @@ importers:
integrations/shopify:
specifiers:
'@shopify/shopify-api': ^8.0.2
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': 16.x
@@ -692,7 +692,7 @@ importers:
integrations/slack:
specifiers:
'@slack/web-api': ^6.8.1
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': '18'
@@ -714,8 +714,8 @@ importers:
integrations/stripe:
specifiers:
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsup': workspace:*
'@types/node': 16.x
rimraf: ^3.0.2
@@ -740,8 +740,8 @@ importers:
integrations/supabase:
specifiers:
'@supabase/supabase-js': ^2.26.0
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/node': 18.x
@@ -766,8 +766,8 @@ importers:
integrations/typeform:
specifiers:
'@trigger.dev/integration-kit': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/integration-kit': workspace:^2.3.18
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsup': workspace:*
'@typeform/api-client': ^1.8.0
'@types/node': 16.x
@@ -1112,7 +1112,7 @@ importers:
packages/express:
specifiers:
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/debug': ^4.1.7
@@ -1182,7 +1182,7 @@ importers:
specifiers:
'@nestjs/common': ^10.2.4
'@remix-run/web-fetch': ^4.3.5
'@trigger.dev/sdk': workspace:^2.3.17
'@trigger.dev/sdk': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/debug': ^4.1.7
@@ -1241,7 +1241,7 @@ importers:
packages/react:
specifiers:
'@tanstack/react-query': 5.0.0-beta.2
'@trigger.dev/core': workspace:^2.3.17
'@trigger.dev/core': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@types/debug': ^4.1.7
'@types/react': 18.2.17
@@ -1344,8 +1344,8 @@ importers:
packages/trigger-sdk:
specifiers:
'@trigger.dev/core': workspace:^2.3.17
'@trigger.dev/core-backend': workspace:^2.3.17
'@trigger.dev/core': workspace:^2.3.18
'@trigger.dev/core-backend': workspace:^2.3.18
'@trigger.dev/tsconfig': workspace:*
'@trigger.dev/tsup': workspace:*
'@types/debug': ^4.1.7
@@ -20591,13 +20591,13 @@ packages:
eslint: 8.45.0
dev: true
/eslint-config-turbo/1.11.3_eslint@8.31.0:
resolution: {integrity: sha512-v7CHpAHodBKlj+r+R3B2DJlZbCjpZLnK7gO/vCRk/Lc+tlD/f04wM6rmHlerevOlchtmwARilRLBnmzNLffTyQ==}
/eslint-config-turbo/1.12.3_eslint@8.31.0:
resolution: {integrity: sha512-Q46MEOiNJpJWC3Et5/YEuIYYhbOieS04yZwQOinO2hpZw3folEXV+hbwVo8M+ap/q8gtpjIWiRMZ1A4QxmhEqQ==}
peerDependencies:
eslint: '>6.6.0'
dependencies:
eslint: 8.31.0
eslint-plugin-turbo: 1.11.3_eslint@8.31.0
eslint-plugin-turbo: 1.12.3_eslint@8.31.0
dev: true
/eslint-doc-generator/1.4.3_eslint@8.45.0:
@@ -21300,8 +21300,8 @@ packages:
- typescript
dev: true
/eslint-plugin-turbo/1.11.3_eslint@8.31.0:
resolution: {integrity: sha512-R5ftTTWQzEYaKzF5g6m/MInCU8pIN+2TLL+S50AYBr1enwUovdZmnZ1HDwFMaxIjJ8x5ah+jvAzql5IJE9VWaA==}
/eslint-plugin-turbo/1.12.3_eslint@8.31.0:
resolution: {integrity: sha512-7hEyxa+oP898EFNoxVenHlH8jtBwV1hbbIkdQWgqDcB0EmVNGVEZkYRo5Hm6BuMAjR433B+NISBJdj0bQo4/Lg==}
peerDependencies:
eslint: '>6.6.0'
dependencies: