Merge branch 'dev'

This commit is contained in:
Eric Allam
2023-01-20 11:52:01 -08:00
93 changed files with 4245 additions and 764 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Added some logging messages (and disabled any messages by default)
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Fixed issue with workflow runs not completing when the run function returned undefined or null
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Added triggerTTL option that prevents old events from running a workflow
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/integrations": patch
"@trigger.dev/providers": patch
---
Added additional github webhook triggers, and improved the slack integration
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

@@ -19,7 +19,7 @@ export default function CreateNewWorkflow() {
<span>Documentation</span>
</PrimaryA>
<SecondaryA
href="https://docs.trigger.dev"
href="https://docs.trigger.dev/examples/github"
target="_blank"
rel="noreferrer"
>
@@ -51,7 +51,7 @@ export function CreateNewWorkflowNoWorkflows() {
<span>Documentation</span>
</PrimaryA>
<SecondaryA
href="https://docs.trigger.dev"
href="https://docs.trigger.dev/examples/github"
target="_blank"
rel="noreferrer"
>
+16 -7
View File
@@ -1,7 +1,9 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import Prism from "prismjs";
import "prismjs/components/prism-typescript";
import "prismjs/components/prism-json";
import "prismjs/plugins/line-numbers/prism-line-numbers";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
import { CopyTextButton } from "../CopyTextButton";
import classNames from "classnames";
@@ -13,6 +15,7 @@ type CodeBlockProps = {
showCopyButton?: boolean;
align?: "top" | "center";
maxHeight?: string;
showLineNumbers?: boolean;
className?: string;
};
@@ -22,16 +25,19 @@ export default function CodeBlock({
showCopyButton = true,
align = "center",
maxHeight,
showLineNumbers = true,
className,
}: CodeBlockProps) {
const [codeHtml, setCodeHtml] = useState(code);
const codeRef = useRef<HTMLPreElement>(null);
const [isCollapsed, setIsCollapsed] = useState(
maxHeight === undefined ? false : true
);
useEffect(() => {
const val = Prism.highlight(code, Prism.languages[language], language);
setCodeHtml(val);
}, [code, language]);
if (!codeRef.current) {
return;
}
Prism.highlightAllUnder(codeRef.current);
}, [code, language, codeRef]);
return (
<div
@@ -42,10 +48,13 @@ export default function CodeBlock({
)}
style={{ maxHeight: isCollapsed ? maxHeight : undefined }}
>
<pre className={`language-${language}`}>
<pre
className={classNames(showLineNumbers && `line-numbers`)}
ref={codeRef}
>
<code
className={`language-${language}`}
dangerouslySetInnerHTML={{ __html: codeHtml }}
dangerouslySetInnerHTML={{ __html: code }}
></code>
</pre>
{showCopyButton === true && (
@@ -117,6 +117,7 @@ export function AddApiKeyButton({
name="api_key"
placeholder="<api_key>"
spellCheck={false}
type="password"
/>
{errors && <FormError errors={errors} path={["api_key"]} />}
</InputGroup>
@@ -55,7 +55,7 @@ export function ConnectionSelector({
<Popover.Button
className={`
${open ? "" : ""}
inline-flex justify-between items-center rounded text-white bg-slate-700 shadow pl-3 pr-2 py-1.5 gap-2 text-sm hover:bg-slate-600/80 transition focus:outline-none`}
inline-flex justify-between items-center rounded text-slate-300 bg-slate-700 shadow pl-4 pr-3 py-2 gap-2 text-sm hover:bg-slate-600/80 transition focus:outline-none`}
>
<span className="transition text-sm text-slate-200">
{selectedConnection ? (
+2 -2
View File
@@ -26,7 +26,7 @@ export function Header({ children }: HeaderProps) {
<div className="flex flex-1 justify-center">{children}</div>
<div className="flex items-center gap-1">
<div className="flex items-center gap-4">
<TertiaryA href="https://docs.trigger.dev" target="_blank">
<DocumentTextIcon className="h-5 w-5" />
Docs
@@ -60,7 +60,7 @@ export function BreadcrumbDivider() {
y1="0.6286"
x2="0.6286"
y2="24.6762"
opacity={0.5}
opacity={0.3}
stroke="white"
strokeLinecap="round"
/>
+1 -1
View File
@@ -4,7 +4,7 @@ export type ListProps = {
export function List({ children }: { children: React.ReactNode }) {
return (
<div className="overflow-hidden bg-slate-800 shadow-md sm:rounded-md mb-10">
<div className="bg-slate-800 shadow-md sm:rounded-md mb-4">
<ul className="divide-y divide-slate-850">{children}</ul>
</div>
);
@@ -0,0 +1,23 @@
import { InformationCircleIcon } from "@heroicons/react/24/solid";
export type PanelInfoProps = {
children: React.ReactNode;
className?: string;
};
export function PanelInfo({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div
className={`flex gap-2.5 items-center bg-slate-400/10 border border-slate-600 w-full shadow-md rounded-md p-3 ${className}`}
>
<InformationCircleIcon className="h-6 w-6 min-w-[24px] text-blue-500" />
{children}
</div>
);
}
@@ -16,7 +16,7 @@ export function PanelWarning({
<div
className={`flex gap-2.5 items-center bg-amber-400/10 border border-amber-500 w-full shadow-md rounded-md p-3 ${className}`}
>
<ExclamationTriangleIcon className="h-6 w-6 text-amber-500" />
<ExclamationTriangleIcon className="h-6 w-6 min-w-[24px] text-amber-500" />
{children}
</div>
);
@@ -10,6 +10,7 @@ import {
BeakerIcon,
ClipboardDocumentCheckIcon,
Squares2X2Icon,
Cog6ToothIcon,
} from "@heroicons/react/24/outline";
import { Link, NavLink } from "@remix-run/react";
import {
@@ -94,6 +95,11 @@ export function WorkflowsSideMenu() {
icon: <Squares2X2Icon className={iconStyle} />,
to: `integrations`,
},
{
name: "Settings",
icon: <Cog6ToothIcon className={iconStyle} />,
to: `settings`,
},
];
return (
@@ -80,11 +80,12 @@ export function WorkflowMenu() {
className={classNames(
"flex items-center justify-between gap-1.5 mx-1 px-3 py-2 text-white rounded hover:bg-slate-800 transition",
workflow.slug === currentWorkflow?.slug &&
"!bg-slate-800"
"!bg-slate-800",
workflow.status === "DISABLED" && "opacity-50"
)}
>
<div className="relative flex items-center gap-2">
{workflow.status !== "READY" && (
{workflow.status === "CREATED" && (
<ExclamationTriangleIcon className="absolute -top-1.5 -left-2.5 h-3.5 w-3.5 text-amber-500" />
)}
<ArrowsRightLeftIcon
@@ -2,20 +2,23 @@ import { Link } from "@remix-run/react";
import classnames from "classnames";
const commonClasses =
"inline-flex items-center justify-center max-w-max rounded px-4 py-2 text-sm transition whitespace-nowrap";
"inline-flex items-center justify-center max-w-max rounded text-sm transition whitespace-nowrap";
const primaryClasses = classnames(
commonClasses,
"bg-indigo-700 text-white hover:bg-indigo-600 focus:ring-indigo-800 gap-2"
"px-4 py-2 bg-indigo-700 text-white hover:bg-indigo-600 focus:ring-indigo-800 gap-2"
);
const secondaryClasses = classnames(
commonClasses,
"bg-transparent border border-slate-700 text-white hover:bg-white/5 hover:border-slate-700 focus:ring-slate-300 gap-2"
"px-4 py-2 bg-transparent ring-1 ring-slate-700 ring-inset text-white hover:bg-white/5 hover:border-slate-700 focus:ring-slate-300 gap-2"
);
const tertiaryClasses = classnames(
commonClasses,
"text-white/60 hover:text-white gap-1"
);
const dangerClasses = classnames(
commonClasses,
"px-4 py-2 bg-rose-700 text-white hover:bg-rose-600 focus:ring-rose-800 gap-2"
);
type ButtonProps = React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
@@ -57,6 +60,14 @@ export function TertiaryButton({ children, className, ...props }: ButtonProps) {
);
}
export function DangerButton({ children, className, ...props }: ButtonProps) {
return (
<button className={classnames(dangerClasses, className)} {...props}>
{children}
</button>
);
}
export function PrimaryLink({ children, className, to, ...props }: LinkProps) {
return (
<Link to={to} className={classnames(primaryClasses, className)} {...props}>
@@ -6,10 +6,7 @@ const roundedStyles = {
roundedFull: "rounded",
};
type InputProps = React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
> & {
type InputProps = JSX.IntrinsicElements["input"] & {
roundedEdges?: "roundedLeft" | "roundedRight" | "roundedFull";
};
@@ -17,7 +17,7 @@ type ButtonProps = Parameters<typeof Listbox.Button>[0] & {
};
const buttonClassName =
"relative w-full rounded bg-slate-700 py-2 pl-3 pr-10 text-sm text-left shadow-md hover:cursor-pointer hover:bg-slate-600/80 transition";
"relative w-full rounded bg-slate-700 py-2 pl-4 pr-10 text-slate-300 text-sm text-left shadow-md hover:cursor-pointer hover:bg-slate-600/80 transition";
function Button({ children, ...props }: ButtonProps) {
return (
<Listbox.Button
@@ -1,10 +1,11 @@
import { BeakerIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
import { BeakerIcon } from "@heroicons/react/24/outline";
import { Link } from "@remix-run/react";
import classNames from "classnames";
import humanizeDuration from "humanize-duration";
import type { ReactNode } from "react";
import type { WorkflowRunListPresenter } from "~/models/workflowRunListPresenter.server";
import { dateDifference, formatDateTime } from "~/utils";
import { PanelWarning } from "../layout/PanelWarning";
import { Spinner } from "../primitives/Spinner";
import { runStatusIcon, runStatusLabel } from "./runStatus";
@@ -151,10 +152,7 @@ function BlankRow({ children }: { children: ReactNode }) {
export function NoRuns({ title }: { title: string }) {
return (
<div className="flex items-center justify-center">
<div className="flex items-center justify-center p-3 pr-4 gap-1 bg-yellow-200 border border-yellow-400 rounded-md text-yellow-700">
<InformationCircleIcon className="w-5 h-5" />
<span className="text-gray">{title}</span>
</div>
<PanelWarning className="max-w-max">{title}</PanelWarning>
</div>
);
}
@@ -21,6 +21,8 @@ export function runStatusTitle(status: WorkflowRunStatus): string {
return "Disconnected";
case "ERROR":
return "Error";
case "TIMED_OUT":
return "Timed out";
}
}
@@ -36,6 +38,8 @@ export function runStatusLabel(status: WorkflowRunStatus): ReactNode {
return <span className="text-amber-300">{runStatusTitle(status)}</span>;
case "ERROR":
return <span className="text-rose-500">{runStatusTitle(status)}</span>;
case "TIMED_OUT":
return <span className="text-amber-300">{runStatusTitle(status)}</span>;
}
}
@@ -91,5 +95,14 @@ export function runStatusIcon(
)}
/>
);
case "TIMED_OUT":
return (
<ExclamationTriangleIcon
className={classNames(
iconSize === "small" ? smallClasses : largeClasses,
"relative text-amber-300"
)}
/>
);
}
}
@@ -1,17 +1,21 @@
import type {
CustomEventTrigger,
ScheduledEventTrigger,
ScheduleSourceCron,
ScheduleSourceRate,
TriggerMetadata,
WebhookEventTrigger,
} from "@trigger.dev/common-schemas";
import { Body } from "../primitives/text/Body";
import { Header2 } from "../primitives/text/Headers";
import cronstrue from "cronstrue";
export function TriggerBody({ trigger }: { trigger: TriggerMetadata }) {
switch (trigger.type) {
case "WEBHOOK":
return <Webhook webhook={trigger} />;
case "SCHEDULE":
break;
return <Scheduled event={trigger} />;
case "CUSTOM_EVENT":
return <CustomEvent event={trigger} />;
case "HTTP_ENDPOINT":
@@ -57,3 +61,83 @@ function CustomEvent({ event }: { event: CustomEventTrigger }) {
</>
);
}
function Scheduled({ event }: { event: ScheduledEventTrigger }) {
return (
<>
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
{"rateOf" in event.source ? (
<RateOfScheduled source={event.source} />
) : (
<AtScheduled source={event.source} />
)}
</Body>
</>
);
}
function RateOfScheduled({ source }: { source: ScheduleSourceRate }) {
const unit =
"minutes" in source.rateOf
? source.rateOf.minutes > 1
? "minutes"
: "minute"
: "hours" in source.rateOf
? source.rateOf.hours > 1
? "hours"
: "hour"
: source.rateOf.days > 1
? "days"
: "day";
const value =
"minutes" in source.rateOf
? source.rateOf.minutes
: "hours" in source.rateOf
? source.rateOf.hours
: source.rateOf.days;
return (
<div className="flex gap-2 items-baseline">
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
Runs
</Body>
<Body size="small" className="text-slate-300 normal-case tracking-normal">
Every {value} {unit}
</Body>
</div>
);
}
function AtScheduled({ source }: { source: ScheduleSourceCron }) {
return (
<div className="flex flex-col gap-1.5">
<div className="flex items-baseline gap-2">
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
Runs
</Body>
<Body
size="small"
className="text-slate-300 normal-case tracking-normal"
>
{cronstrue.toString(source.cron, {
throwExceptionOnParseError: false,
verbose: false,
use24HourTimeFormat: true,
})}
</Body>
</div>
<div className="flex items-baseline gap-2">
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
Cron expression
</Body>
<Body
size="small"
className="text-slate-300 normal-case tracking-normal"
>
{source.cron}
</Body>
</div>
</div>
);
}
+2
View File
@@ -32,3 +32,5 @@ export function useCurrentWorkflow() {
return result;
}
export type CurrentWorkflow = ReturnType<typeof useCurrentWorkflow>;
+16
View File
@@ -61,3 +61,19 @@ export async function redirectWithErrorMessage(
},
});
}
export async function redirectBackWithErrorMessage(
request: Request,
message: string
) {
const url = new URL(request.url);
return redirectWithErrorMessage(url.pathname, request, message);
}
export async function redirectBackWithSuccessMessage(
request: Request,
message: string
) {
const url = new URL(request.url);
return redirectWithSuccessMessage(url.pathname, request, message);
}
@@ -28,6 +28,11 @@ export function getOrganizationFromSlug({
},
},
},
where: { isArchived: false },
orderBy: [
{ disabledAt: { sort: "asc", nulls: "first" } },
{ title: "asc" },
],
},
environments: {
select: {
@@ -1,5 +1,4 @@
import type { RuntimeEnvironment } from ".prisma/client";
import type { Session } from "@remix-run/node";
import { createCookieSessionStorage } from "@remix-run/node";
import { prisma } from "~/db.server";
import { env } from "~/env.server";
@@ -1,3 +1,6 @@
import type { SchedulerSource } from ".prisma/client";
import { ScheduleSourceSchema } from "@trigger.dev/common-schemas";
import cronstrue from "cronstrue";
import type { DisplayProperties } from "internal-integrations";
import { github } from "internal-integrations";
import invariant from "tiny-invariant";
@@ -6,6 +9,7 @@ import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import { getIntegration } from "~/utils/integrations";
import { getIntegrations } from "./integrations.server";
import { getRuntimeEnvironment } from "./runtimeEnvironment.server";
import type { ExternalSource, Workflow } from "./workflow.server";
export type WorkflowListItem = Awaited<
@@ -19,8 +23,24 @@ export class WorkflowListPresenter {
this.#prismaClient = prismaClient;
}
async data(organizationSlug: string) {
const workflows = await getWorkflows(this.#prismaClient, organizationSlug);
async data(organizationSlug: string, environmentSlug: string) {
const organization = await this.#prismaClient.organization.findUnique({
where: { slug: organizationSlug },
select: { id: true },
});
invariant(organization, "Organization not found");
const runtimeEnvironment = await getRuntimeEnvironment({
organizationId: organization.id,
slug: environmentSlug,
});
invariant(runtimeEnvironment, "Runtime environment not found");
const workflows = await getWorkflows(
this.#prismaClient,
organizationSlug,
runtimeEnvironment.id
);
const integrations = getIntegrations(true);
return workflows.map((workflow) => {
@@ -39,7 +59,8 @@ export class WorkflowListPresenter {
status: workflow.status,
trigger: triggerProperties(
workflow,
workflow.externalSource ?? undefined
workflow.externalSource ?? undefined,
workflow.schedulerSources[0] ?? undefined
),
integrations: {
source: workflow.service
@@ -55,9 +76,13 @@ export class WorkflowListPresenter {
}
}
function getWorkflows(prismaClient: PrismaClient, organizationSlug: string) {
function getWorkflows(
prismaClient: PrismaClient,
organizationSlug: string,
environmentId: string
) {
return prismaClient.workflow.findMany({
where: { organization: { slug: organizationSlug } },
where: { organization: { slug: organizationSlug }, isArchived: false },
include: {
externalServices: {
select: {
@@ -70,6 +95,16 @@ function getWorkflows(prismaClient: PrismaClient, organizationSlug: string) {
source: true,
},
},
schedulerSources: {
select: {
schedule: true,
},
where: {
environmentId,
},
orderBy: { createdAt: "desc" },
take: 1,
},
runs: {
select: {
finishedAt: true,
@@ -79,12 +114,17 @@ function getWorkflows(prismaClient: PrismaClient, organizationSlug: string) {
orderBy: { finishedAt: { sort: "desc", nulls: "last" } },
},
},
orderBy: [
{ disabledAt: { sort: "asc", nulls: "first" } },
{ title: "asc" },
],
});
}
function triggerProperties(
workflow: Pick<Workflow, "type" | "eventNames">,
externalSource?: Pick<ExternalSource, "service" | "source">
externalSource?: Pick<ExternalSource, "service" | "source">,
schedulerSource?: Pick<SchedulerSource, "schedule">
): {
type: Workflow["type"];
typeTitle: string;
@@ -113,6 +153,49 @@ function triggerProperties(
properties: displayProperties.properties,
};
}
case "SCHEDULE": {
invariant(schedulerSource, "Scheduler source is required for schedule");
const source = ScheduleSourceSchema.parse(schedulerSource.schedule);
if ("rateOf" in source) {
const unit =
"minutes" in source.rateOf
? source.rateOf.minutes > 1
? "minutes"
: "minute"
: "hours" in source.rateOf
? source.rateOf.hours > 1
? "hours"
: "hour"
: source.rateOf.days > 1
? "days"
: "day";
const value =
"minutes" in source.rateOf
? source.rateOf.minutes
: "hours" in source.rateOf
? source.rateOf.hours
: source.rateOf.days;
return {
type: workflow.type,
typeTitle: "Schedule",
title: `Every ${value} ${unit}`,
};
} else {
return {
type: workflow.type,
typeTitle: "Schedule",
title: cronstrue.toString(source.cron, {
throwExceptionOnParseError: false,
verbose: false,
use24HourTimeFormat: true,
}),
properties: [{ key: "Cron Expression", value: source.cron }],
};
}
}
case "CUSTOM_EVENT":
return {
type: workflow.type,
+14 -3
View File
@@ -19,6 +19,7 @@ export async function findWorklowRunById(id: string) {
include: {
event: true,
environment: true,
workflow: true,
},
});
}
@@ -80,10 +81,10 @@ export async function failWorkflowRun(
}
export async function completeWorkflowRun(
output: string,
runId: string,
apiKey: string,
timestamp: string
timestamp: string,
output?: string | null
) {
const workflowRun = await findWorkflowRunScopedToApiKey(runId, apiKey);
@@ -104,6 +105,9 @@ export async function completeWorkflowRun(
},
});
const parsedOutput =
typeof output === "string" ? JSON.parse(output) : undefined;
await tx.workflowRunStep.upsert({
where: {
runId_idempotencyKey: {
@@ -115,7 +119,7 @@ export async function completeWorkflowRun(
runId,
idempotencyKey: "output",
type: "OUTPUT",
output: JSON.parse(output),
output: parsedOutput === null ? undefined : parsedOutput,
context: {},
startedAt: new Date(),
finishedAt: new Date(),
@@ -170,6 +174,13 @@ export async function triggerEventInRun(
},
workflowRun.environment.organization
);
await prisma.workflowRunStep.update({
where: { id: step.step.id },
data: {
status: "SUCCESS",
},
});
}
export async function logMessageInRun(
@@ -62,6 +62,7 @@ export class WorkflowRunPresenter {
error: workflowRun.error
? await ErrorSchema.parseAsync(workflowRun.error)
: undefined,
timedOutReason: workflowRun.timedOutReason,
};
}
}
@@ -4,6 +4,7 @@ import {
} from "@heroicons/react/24/solid";
import { Link } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import classNames from "classnames";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { ApiLogoIcon } from "~/components/code/ApiLogoIcon";
@@ -19,6 +20,7 @@ import { Title } from "~/components/primitives/text/Title";
import { runStatusLabel } from "~/components/runs/runStatus";
import { TriggerTypeIcon } from "~/components/triggers/TriggerIcons";
import { useCurrentOrganization } from "~/hooks/useOrganizations";
import { getRuntimeEnvironmentFromRequest } from "~/models/runtimeEnvironment.server";
import type { WorkflowListItem } from "~/models/workflowListPresenter.server";
import { WorkflowListPresenter } from "~/models/workflowListPresenter.server";
import { requireUserId } from "~/services/session.server";
@@ -28,10 +30,12 @@ export const loader = async ({ request, params }: LoaderArgs) => {
await requireUserId(request);
invariant(params.organizationSlug, "Organization slug is required");
const currentEnv = await getRuntimeEnvironmentFromRequest(request);
const presenter = new WorkflowListPresenter();
try {
const workflows = await presenter.data(params.organizationSlug);
const workflows = await presenter.data(params.organizationSlug, currentEnv);
return typedjson({ workflows });
} catch (error: any) {
console.error(error);
@@ -84,13 +88,16 @@ function WorkflowList({
<li key={workflow.id}>
<Link
to={`/orgs/${currentOrganizationSlug}/workflows/${workflow.slug}`}
className="block hover:bg-slate-850/40 transition"
className={classNames(
"block hover:bg-slate-850/40 transition",
workflow.status === "DISABLED" ? workflowDisabled : ""
)}
>
<div className="flex justify-between lg:items-center flex-col lg:flex-row flex-wrap lg:flex-nowrap pl-4 pr-4 py-4">
<div className="flex items-center flex-1 justify-between">
<div className="relative flex items-center">
{workflow.status !== "READY" && (
<ExclamationTriangleIcon className="absolute -top-1.5 -left-1.5 h-6 w-6 text-amber-500" />
{workflow.status === "CREATED" && (
<ExclamationTriangleIcon className="absolute -top-1.5 -left-1.5 h-6 w-6 text-amber-400" />
)}
<div className="p-3 bg-slate-850 rounded-md flex-shrink-0 self-start h-20 w-20 mr-4">
<TriggerTypeIcon
@@ -114,7 +121,7 @@ function WorkflowList({
{workflow.trigger.title}
</Header3>
</div>
<div className="flex flex-wrap gap-x-2 items-baseline">
<div className="flex flex-wrap gap-x-3 items-baseline">
{workflow.trigger.properties &&
workflow.trigger.properties.map((property) => (
<WorkflowProperty
@@ -215,3 +222,5 @@ function WorkflowProperty({
</div>
);
}
const workflowDisabled = "opacity-30";
@@ -1,4 +1,4 @@
import { SquaresPlusIcon } from "@heroicons/react/24/outline";
import { EnvelopeIcon } from "@heroicons/react/24/outline";
import { PlusCircleIcon } from "@heroicons/react/24/solid";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { Provider } from "@trigger.dev/providers";
@@ -104,7 +104,7 @@ export default function Integrations() {
className="flex flex-col group max-w-[160px] rounded-md bg-slate-800 border border-slate-800 gap-4 text-sm text-slate-200 items-center overflow-hidden hover:bg-slate-800/30 transition shadow-md disabled:opacity-50"
>
<div className="relative flex items-center justify-center w-full py-6 bg-black/20 border-b border-slate-800">
<SquaresPlusIcon className="h-20 w-20 text-slate-400" />
<EnvelopeIcon className="h-20 w-20 text-slate-400" />
</div>
<div className="flex flex-col items-center justify-center text-center leading-relaxed text-slate-400">
<span className="px-2.5">Need an integration?</span>
@@ -129,7 +129,7 @@ function AddButtonContent({
return (
<>
<div className="relative flex items-center justify-center w-full py-6 bg-black/20 border-b border-slate-800">
<PlusCircleIcon className="absolute h-7 w-7 top-[6px] right-[6px] z-10 text-slate-500 shadow-md" />
<PlusCircleIcon className="absolute h-7 w-7 top-[6px] right-[6px] z-10 text-green-600 shadow-md" />
<img
src={integration.icon}
alt={integration.name}
@@ -4,8 +4,13 @@ import invariant from "tiny-invariant";
import { WorkflowConnections } from "~/components/integrations/WorkflowConnections";
import { Panel } from "~/components/layout/Panel";
import { PanelHeader } from "~/components/layout/PanelHeader";
import { PanelInfo } from "~/components/layout/PanelInfo";
import { PanelWarning } from "~/components/layout/PanelWarning";
import { PrimaryLink, SecondaryLink } from "~/components/primitives/Buttons";
import {
PrimaryLink,
SecondaryLink,
TertiaryLink,
} from "~/components/primitives/Buttons";
import { Body } from "~/components/primitives/text/Body";
import { SubTitle } from "~/components/primitives/text/SubTitle";
import { Title } from "~/components/primitives/text/Title";
@@ -79,14 +84,25 @@ export default function Page() {
{workflow.slug}
</Body>
</div>
{workflow.status !== "READY" && (
{workflow.status === "CREATED" && (
<>
<SubTitle>1 issue</SubTitle>
<PanelWarning className="mb-6">
This workflow requires its APIs to be connected before it can run.
</PanelWarning>
</>
)}
{workflow.status === "DISABLED" && (
<PanelInfo className="mb-6">
<Body className="flex grow items-center justify-between">
This workflow is disabled. Runs cannot be triggered or tested while
disabled. Runs in progress will continue until complete.
</Body>
<TertiaryLink to="settings" className="mr-1">
Settings
</TertiaryLink>
</PanelInfo>
)}
{apiConnectionCount > 0 && <WorkflowConnections />}
{eventRule && (
<>
@@ -123,7 +139,7 @@ export default function Page() {
View all
</SecondaryLink>
</div>
<Panel className="p-0 overflow-hidden overflow-x-auto">
<Panel className="p-0 overflow-hidden overflow-x-auto mb-6">
<RunsTable
runs={runs}
total={total}
@@ -1,6 +1,9 @@
import invariant from "tiny-invariant";
import { WorkflowConnections } from "~/components/integrations/WorkflowConnections";
import { PanelInfo } from "~/components/layout/PanelInfo";
import { PanelWarning } from "~/components/layout/PanelWarning";
import { TertiaryLink } from "~/components/primitives/Buttons";
import { Body } from "~/components/primitives/text/Body";
import { SubTitle } from "~/components/primitives/text/SubTitle";
import { Title } from "~/components/primitives/text/Title";
import { useConnectionSlots } from "~/hooks/useConnectionSlots";
@@ -16,14 +19,24 @@ export default function Page() {
return (
<>
<Title>Connected APIs</Title>
{workflow.status !== "READY" && (
{workflow.status === "CREATED" && (
<>
<SubTitle>1 issue</SubTitle>
<PanelWarning className="mb-6">
This workflow requires its APIs to be connected before it can run.
</PanelWarning>
</>
)}
{workflow.status === "DISABLED" && (
<PanelInfo className="mb-6">
<Body className="flex grow items-center justify-between">
This workflow is disabled. Runs cannot be triggered or tested while
disabled. Runs in progress will continue until complete.
</Body>
<TertiaryLink to="settings" className="mr-1">
Settings
</TertiaryLink>
</PanelInfo>
)}
{connectionSlots.source || connectionSlots.services.length > 0 ? (
<WorkflowConnections />
) : (
@@ -40,7 +40,7 @@ import { TriggerTypeIcon } from "~/components/triggers/TriggerIcons";
import { triggerLabel } from "~/components/triggers/triggerLabel";
import { useCurrentOrganization } from "~/hooks/useOrganizations";
import { useCurrentWorkflow } from "~/hooks/useWorkflows";
import type { WorkflowRunStatus } from "~/models/workflowRun.server";
import { WorkflowRunStatus } from "~/models/workflowRun.server";
import { WorkflowRunPresenter } from "~/models/workflowRunPresenter.server";
import { requireUserId } from "~/services/session.server";
import { dateDifference, formatDateTime } from "~/utils";
@@ -218,6 +218,29 @@ export default function Page() {
<div className="h-10 w-full ml-[10px] border-dashed border-l border-gradient border-slate-700"></div>
)}
{run.status === "TIMED_OUT" && (
<>
<div className="h-3 w-full ml-[10px] -mr-[10px] border-l border-slate-700"></div>
<Panel>
<PanelHeader
icon={
<ExclamationTriangleIcon className="h-6 w-6 text-amber-300" />
}
title="Run timed out"
runId={run.id}
/>
<div className="flex flex-col gap-1 text-slate-300">
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
Reason
</Body>
<Body className={workflowNodeDelayClasses} size="small">
{run.timedOutReason}
</Body>
</div>
</Panel>
</>
)}
{run.error && <Error error={run.error} />}
</>
);
@@ -7,23 +7,25 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { Panel } from "~/components/layout/Panel";
import { PanelInfo } from "~/components/layout/PanelInfo";
import { PanelWarning } from "~/components/layout/PanelWarning";
import { PaginationControls } from "~/components/Pagination";
import { TertiaryLink } from "~/components/primitives/Buttons";
import { StyledListBox } from "~/components/primitives/ListBox";
import { Body } from "~/components/primitives/text/Body";
import { Title } from "~/components/primitives/text/Title";
import { RunsTable } from "~/components/runs/RunsTable";
import {
runStatusIcon,
runStatusLabel,
runStatusTitle,
} from "~/components/runs/runStatus";
import { RunsTable } from "~/components/runs/RunsTable";
import { useCurrentWorkflow } from "~/hooks/useWorkflows";
import { getRuntimeEnvironmentFromRequest } from "~/models/runtimeEnvironment.server";
import type { WorkflowRunStatus } from "~/models/workflowRun.server";
import { WorkflowRunListPresenter } from "~/models/workflowRunListPresenter.server";
import { requireUserId } from "~/services/session.server";
import { getRuntimeEnvironmentFromRequest } from "~/models/runtimeEnvironment.server";
import { allStatuses } from "~/models/workflowRunStatus";
import { Title } from "~/components/primitives/text/Title";
import { useCurrentWorkflow } from "~/hooks/useWorkflows";
import { PanelWarning } from "~/components/layout/PanelWarning";
import { SubTitle } from "~/components/primitives/text/SubTitle";
import { requireUserId } from "~/services/session.server";
export const loader = async ({ request, params }: LoaderArgs) => {
const userId = await requireUserId(request);
@@ -77,14 +79,24 @@ export default function Page() {
return (
<>
<Title>Runs</Title>
{workflow.status !== "READY" && (
{workflow.status === "CREATED" && (
<>
<SubTitle>1 issue</SubTitle>
<PanelWarning className="mb-6">
This workflow requires its APIs to be connected before it can run.
</PanelWarning>
</>
)}
{workflow.status === "DISABLED" && (
<PanelInfo className="mb-6">
<Body className="flex grow items-center justify-between">
This workflow is disabled. Runs cannot be triggered or tested while
disabled. Runs in progress will continue until complete.
</Body>
<TertiaryLink to="settings" className="mr-1">
Settings
</TertiaryLink>
</PanelInfo>
)}
<fetcher.Form
method="get"
className="pb-4 flex gap-2"
@@ -0,0 +1,272 @@
import { ApiLogoIcon } from "~/components/code/ApiLogoIcon";
import { Panel } from "~/components/layout/Panel";
import {
DangerButton,
PrimaryButton,
SecondaryButton,
} from "~/components/primitives/Buttons";
import { Header3 } from "~/components/primitives/text/Headers";
import { SubTitle } from "~/components/primitives/text/SubTitle";
import { Title } from "~/components/primitives/text/Title";
import type { CurrentWorkflow } from "~/hooks/useWorkflows";
import { useCurrentWorkflow } from "~/hooks/useWorkflows";
import invariant from "tiny-invariant";
import { Form } from "@remix-run/react";
import type { ActionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { requireUserId } from "~/services/session.server";
import {
redirectBackWithErrorMessage,
redirectBackWithSuccessMessage,
redirectWithSuccessMessage,
} from "~/models/message.server";
import { DisableWorkflow } from "~/services/workflows/disableWorkflow.server";
import { EnableWorkflow } from "~/services/workflows/enableWorkflow.server";
import { ArchiveWorkflow } from "~/services/workflows/archiveWorkflow.server";
import { UnarchiveWorkflow } from "~/services/workflows/unarchiveWorkflow.server";
const ActionSchema = z.enum(["disable", "enable", "archive", "unarchive"]);
const FormSchema = z.object({
action: ActionSchema,
});
const ParamsSchema = z.object({
organizationSlug: z.string(),
workflowSlug: z.string(),
});
export async function action({ params, request }: ActionArgs) {
const userId = await requireUserId(request);
const formData = Object.fromEntries(await request.formData());
const { action } = FormSchema.parse(formData);
const { organizationSlug, workflowSlug } = ParamsSchema.parse(params);
switch (action) {
case "disable":
return disableAction(userId, organizationSlug, workflowSlug, request);
case "enable":
return enableAction(userId, organizationSlug, workflowSlug, request);
case "archive":
return archiveAction(userId, organizationSlug, workflowSlug, request);
case "unarchive":
return unarchiveAction(userId, organizationSlug, workflowSlug, request);
}
}
async function disableAction(
userId: string,
organizationSlug: string,
workflowSlug: string,
request: Request
) {
const service = new DisableWorkflow();
const result = await service.call(userId, organizationSlug, workflowSlug);
if (result.status === "error") {
return redirectBackWithErrorMessage(request, result.message);
}
return redirectBackWithSuccessMessage(
request,
"Workflow successfully disabled. We will stop triggering it for new events."
);
}
async function enableAction(
userId: string,
organizationSlug: string,
workflowSlug: string,
request: Request
) {
const service = new EnableWorkflow();
const result = await service.call(userId, organizationSlug, workflowSlug);
if (result.status === "error") {
return redirectBackWithErrorMessage(request, result.message);
}
return redirectBackWithSuccessMessage(
request,
"Workflow successfully enabled. Triggering of new events will resume."
);
}
async function archiveAction(
userId: string,
organizationSlug: string,
workflowSlug: string,
request: Request
) {
const service = new ArchiveWorkflow();
const result = await service.call(userId, organizationSlug, workflowSlug);
if (result.status === "error") {
return redirectBackWithErrorMessage(request, result.message);
}
return redirectWithSuccessMessage(
`/orgs/${organizationSlug}`,
request,
"Workflow successfully archived."
);
}
async function unarchiveAction(
userId: string,
organizationSlug: string,
workflowSlug: string,
request: Request
) {
const service = new UnarchiveWorkflow();
const result = await service.call(userId, organizationSlug, workflowSlug);
if (result.status === "error") {
return redirectBackWithErrorMessage(request, result.message);
}
return redirectBackWithSuccessMessage(
request,
"Workflow successfully unarchived. Enable it to resume triggering of new events."
);
}
export default function Page() {
const workflow = useCurrentWorkflow();
invariant(workflow, "Workflow not found");
const panel =
workflow.status === "READY" ? (
<WorkflowReadyPanel workflow={workflow} />
) : workflow.isArchived ? (
<WorkflowArchivedPanel workflow={workflow} />
) : workflow.status === "DISABLED" ? (
<WorkflowDisabledPanel workflow={workflow} />
) : null;
return (
<>
<Title>Settings</Title>
<SubTitle>Workflow status</SubTitle>
{panel}
</>
);
}
function WorkflowReadyPanel({
workflow,
}: {
workflow: NonNullable<CurrentWorkflow>;
}) {
return (
<Panel className="flex items-center justify-between !p-4">
<div className="flex gap-4 items-center">
<ApiLogoIcon size="regular" />
<Header3 size="small" className="text-slate-300">
{workflow.title} <span className="text-green-500">is active.</span>
</Header3>
</div>
<div className="flex gap-3">
<Form
method="post"
onSubmit={(e) =>
!confirm(
"Disabling this workflow will prevent any new triggers from running, but will not stop in-progress runs from completing. Are you sure you want to disable this workflow?"
) && e.preventDefault()
}
>
<SecondaryButton name="action" value="disable" type="submit">
Disable
</SecondaryButton>
</Form>
<Form
method="post"
onSubmit={(e) =>
!confirm(
"Archiving this workflow disables it and removes it from your list of workflows. Are you sure you want to archive this workflow?"
) && e.preventDefault()
}
>
<DangerButton name="action" value="archive" type="submit">
Archive
</DangerButton>
</Form>
</div>
</Panel>
);
}
function WorkflowDisabledPanel({
workflow,
}: {
workflow: NonNullable<CurrentWorkflow>;
}) {
return (
<Panel className="flex items-center justify-between !p-4">
<div className="flex gap-4 items-center">
<ApiLogoIcon size="regular" />
<Header3 size="small" className="text-slate-300">
{workflow.title} <span className="text-amber-300">is disabled.</span>
</Header3>
</div>
<div className="flex gap-3">
<Form
method="post"
onSubmit={(e) =>
!confirm(
"Enabling this workflow will resume triggering workflows from new events. Are you sure you want to enable this workflow?"
) && e.preventDefault()
}
>
<PrimaryButton name="action" value="enable" type="submit">
Enable
</PrimaryButton>
</Form>
<Form
method="post"
onSubmit={(e) =>
!confirm(
"Archiving this workflow will remove it from your list of workflows. Are you sure you want to archive this workflow?"
) && e.preventDefault()
}
>
<DangerButton name="action" value="archive" type="submit">
Archive
</DangerButton>
</Form>
</div>
</Panel>
);
}
function WorkflowArchivedPanel({
workflow,
}: {
workflow: NonNullable<CurrentWorkflow>;
}) {
return (
<Panel className="flex items-center justify-between !p-4">
<div className="flex gap-4 items-center">
<ApiLogoIcon size="regular" />
<Header3 size="small" className="text-slate-300">
{workflow.title} <span className="text-rose-500">is archived.</span>
</Header3>
</div>
<div className="flex gap-3">
<Form
method="post"
onSubmit={(e) =>
!confirm(
"Unarchiving this workflow will add it back to your list of workflows, but not enable it. Are you sure you want to unarchive this workflow?"
) && e.preventDefault()
}
>
<PrimaryButton name="action" value="unarchive" type="submit">
Unarchive
</PrimaryButton>
</Form>
</div>
</Panel>
);
}
@@ -5,10 +5,11 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { JSONEditor } from "~/components/code/JSONEditor";
import { Panel } from "~/components/layout/Panel";
import { PanelInfo } from "~/components/layout/PanelInfo";
import { PanelWarning } from "~/components/layout/PanelWarning";
import { PrimaryButton } from "~/components/primitives/Buttons";
import { PrimaryButton, TertiaryLink } from "~/components/primitives/Buttons";
import { Select } from "~/components/primitives/Select";
import { SubTitle } from "~/components/primitives/text/SubTitle";
import { Body } from "~/components/primitives/text/Body";
import { Title } from "~/components/primitives/text/Title";
import { useCurrentOrganization } from "~/hooks/useOrganizations";
import { useCurrentWorkflow } from "~/hooks/useWorkflows";
@@ -40,14 +41,25 @@ export default function Page() {
return (
<>
<Title>Test</Title>
{workflow.status !== "READY" && (
{workflow.status === "CREATED" && (
<>
<SubTitle>1 issue</SubTitle>
<PanelWarning className="mb-6">
This workflow requires its APIs to be connected before it can run.
</PanelWarning>
</>
)}
{workflow.status === "DISABLED" && (
<PanelInfo className="mb-6">
<Body className="flex grow items-center justify-between">
This workflow is disabled. Runs cannot be triggered or tested while
disabled. Runs in progress will continue until complete.
</Body>
<TertiaryLink to="settings" className="mr-1">
Settings
</TertiaryLink>
</PanelInfo>
)}
{workflow.status === "READY" && (
<Panel className="mt-4">
<Tester
@@ -33,6 +33,7 @@ export class DispatchEvent {
organizationId: event.organizationId ?? undefined,
environmentId: event.environmentId ?? undefined,
type: event.type,
enabled: true,
},
include: {
workflow: true,
@@ -135,6 +136,10 @@ export class DispatchWorkflowRun {
event: TriggerEvent,
environment: RuntimeEnvironment
) {
if (workflow.status === "DISABLED") {
return true;
}
const workflowRun = await this.#prismaClient.workflowRun.create({
data: {
workflow: {
@@ -43,6 +43,7 @@ import { WaitForConnection } from "./requests/waitForConnection.server";
import { WorkflowRunDisconnected } from "./runs/runDisconnected.server";
import { DeliverScheduledEvent } from "./scheduler/deliverScheduledEvent.server";
import { RegisterSchedulerSource } from "./scheduler/registerSchedulerSource.server";
import { WorkflowRunTriggerTimeout } from "./runs/runTriggerTimeout.server";
let pulsarClient: PulsarClient;
let triggerPublisher: ZodPublisher<TriggerCatalog>;
@@ -130,6 +131,9 @@ export async function init() {
function createClient() {
const client = createPulsarClient({
serviceUrl: env.PULSAR_SERVICE_URL,
ioThreads: 5,
messageListenerThreads: 5,
operationTimeoutSeconds: 30,
});
console.log(`📡 Connected to pulsar at ${env.PULSAR_SERVICE_URL}`);
@@ -142,6 +146,7 @@ function createTriggerPublisher() {
client: pulsarClient,
config: {
topic: Topics.triggers,
batchingEnabled: false,
},
schema: triggerCatalog,
});
@@ -187,10 +192,10 @@ function createCommandSubscriber() {
},
WORKFLOW_RUN_COMPLETE: async (id, data, properties) => {
await completeWorkflowRun(
data.output,
properties["x-workflow-run-id"],
properties["x-api-key"],
properties["x-timestamp"]
properties["x-timestamp"],
data.output
);
return true;
@@ -202,6 +207,13 @@ function createCommandSubscriber() {
return !!success;
},
WORKFLOW_RUN_TRIGGER_TIMEOUT: async (id, data, properties) => {
const service = new WorkflowRunTriggerTimeout();
await service.call(data);
return true;
},
SEND_INTEGRATION_REQUEST: async (id, data, properties) => {
const service = new CreateIntegrationRequest();
@@ -251,6 +263,7 @@ function createCommandResponsePublisher() {
client: pulsarClient,
config: {
topic: Topics.runCommandResponses,
batchingEnabled: false,
},
schema: commandResponseCatalog,
});
@@ -521,6 +534,10 @@ function createTaskQueue() {
"x-workflow-id": run.workflowId,
"x-env": run.environment.slug,
"x-workflow-run-id": run.id,
"x-ttl": run.workflow.triggerTtlInSeconds,
},
{
eventTimestamp: run.event.timestamp.getTime(),
}
);
@@ -0,0 +1,37 @@
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
export class WorkflowRunTriggerTimeout {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
async call(data: { id: string; ttl: number; elapsedSeconds: number }) {
const workflowRun = await this.#prismaClient.workflowRun.findUnique({
where: { id: data.id },
include: {
event: true,
environment: true,
},
});
if (!workflowRun) {
throw new Error("Workflow run not found");
}
if (workflowRun.status !== "PENDING") {
return;
}
await this.#prismaClient.workflowRun.update({
where: { id: data.id },
data: {
status: "TIMED_OUT",
timedOutAt: new Date(),
timedOutReason: `Trigger timed out after ${data.elapsedSeconds}s because it exceeded the TTL of ${data.ttl}s`,
},
});
}
}
@@ -52,6 +52,10 @@ export class DeliverScheduledEvent {
return true;
}
if (!eventRule.enabled) {
return true;
}
// 1. Create a TriggerEvent
const triggerEvent = await this.#prismaClient.triggerEvent.create({
data: {
@@ -63,6 +67,7 @@ export class DeliverScheduledEvent {
context: {},
organizationId: schedulerSource.organizationId,
environmentId: schedulerSource.environmentId,
timestamp: payload.scheduledTime,
},
});
@@ -0,0 +1,71 @@
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import { DisableWorkflow } from "./disableWorkflow.server";
export class ArchiveWorkflow {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call(userId: string, organizationSlug: string, slug: string) {
const organization = await this.#prismaClient.organization.findFirst({
where: {
slug: organizationSlug,
users: {
some: {
id: userId,
},
},
},
});
if (!organization) {
return {
status: "error" as const,
message:
"There was an issue archiving this workflow. Please contact help@trigger.dev for assistance.",
};
}
const workflow = await this.#prismaClient.workflow.findFirst({
where: {
slug,
organizationId: organization.id,
},
});
if (!workflow) {
return {
status: "error" as const,
message:
"There was an issue archiving this workflow. Please contact help@trigger.dev for assistance.",
};
}
if (workflow.isArchived) {
return {
status: "error" as const,
message: "This workflow is already archived",
};
}
const disableService = new DisableWorkflow();
await disableService.call(userId, organizationSlug, slug);
await this.#prismaClient.workflow.update({
where: {
id: workflow.id,
},
data: {
isArchived: true,
archivedAt: new Date(),
},
});
return {
status: "success" as const,
};
}
}
@@ -0,0 +1,84 @@
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
export class DisableWorkflow {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call(userId: string, organizationSlug: string, slug: string) {
const organization = await this.#prismaClient.organization.findFirst({
where: {
slug: organizationSlug,
users: {
some: {
id: userId,
},
},
},
});
if (!organization) {
return {
status: "error" as const,
message:
"There was an issue disabling this workflow. Please contact help@trigger.dev for assistance.",
};
}
const workflow = await this.#prismaClient.workflow.findFirst({
where: {
slug,
organizationId: organization.id,
},
});
if (!workflow) {
return {
status: "error" as const,
message:
"There was an issue disabling this workflow. Please contact help@trigger.dev for assistance.",
};
}
if (workflow.status === "DISABLED") {
return {
status: "error" as const,
message: "This workflow is already disabled",
};
}
if (workflow.isArchived) {
return {
status: "error" as const,
message: "This workflow is already archived, and cannot be disabled",
};
}
const updatedWorkflow = await this.#prismaClient.workflow.update({
where: {
id: workflow.id,
},
data: {
status: "DISABLED",
disabledAt: new Date(),
},
});
await this.#prismaClient.eventRule.updateMany({
where: {
workflowId: workflow.id,
},
data: {
enabled: false,
},
});
return {
status: "success" as const,
workflow: updatedWorkflow,
};
}
}
@@ -0,0 +1,97 @@
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import { ScheduleNextEvent } from "../scheduler/scheduleNextEvent.server";
export class EnableWorkflow {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call(userId: string, organizationSlug: string, slug: string) {
const organization = await this.#prismaClient.organization.findFirst({
where: {
slug: organizationSlug,
users: {
some: {
id: userId,
},
},
},
});
if (!organization) {
return {
status: "error" as const,
message:
"There was an issue enabling this workflow. Please contact help@trigger.dev for assistance.",
};
}
const workflow = await this.#prismaClient.workflow.findFirst({
where: {
slug,
organizationId: organization.id,
},
});
if (!workflow) {
return {
status: "error" as const,
message:
"There was an issue enabling this workflow. Please contact help@trigger.dev for assistance.",
};
}
if (workflow.status === "READY" || workflow.status === "CREATED") {
return {
status: "error" as const,
message: "This workflow is already enabled",
};
}
if (workflow.isArchived) {
return {
status: "error" as const,
message: "This workflow is already archived, and cannot be enabled",
};
}
await this.#prismaClient.workflow.update({
where: {
id: workflow.id,
},
data: {
status: "READY",
disabledAt: null,
},
});
await this.#prismaClient.eventRule.updateMany({
where: {
workflowId: workflow.id,
},
data: {
enabled: true,
},
});
if (workflow.type === "SCHEDULE") {
const schedulers = await this.#prismaClient.schedulerSource.findMany({
where: {
workflowId: workflow.id,
},
});
for (const scheduler of schedulers) {
const scheduleNextEvent = new ScheduleNextEvent();
await scheduleNextEvent.call(scheduler);
}
}
return {
status: "success" as const,
};
}
}
@@ -36,6 +36,13 @@ export class RegisterWorkflow {
organization
);
if (workflow.isArchived) {
return {
status: "success" as const,
data: { id: workflow.id },
};
}
if (validation.data.trigger.service !== "trigger") {
await this.#upsertExternalSource(
validation.data,
@@ -73,6 +80,7 @@ export class RegisterWorkflow {
},
update: {
filter: "filter" in payload.trigger ? payload.trigger.filter : {},
trigger: payload.trigger,
},
create: {
workflowId: workflow.id,
@@ -103,6 +111,7 @@ export class RegisterWorkflow {
type: payload.trigger.type,
service: payload.trigger.service,
eventNames: payload.trigger.name,
triggerTtlInSeconds: payload.triggerTTL,
},
create: {
organizationId: organization.id,
@@ -113,6 +122,7 @@ export class RegisterWorkflow {
status: payload.trigger.service === "trigger" ? "READY" : "CREATED",
service: payload.trigger.service,
eventNames: payload.trigger.name,
triggerTtlInSeconds: payload.triggerTTL,
},
include: {
externalSource: true,
@@ -0,0 +1,68 @@
import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import { DisableWorkflow } from "./disableWorkflow.server";
export class UnarchiveWorkflow {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call(userId: string, organizationSlug: string, slug: string) {
const organization = await this.#prismaClient.organization.findFirst({
where: {
slug: organizationSlug,
users: {
some: {
id: userId,
},
},
},
});
if (!organization) {
return {
status: "error" as const,
message:
"There was an issue unarchiving this workflow. Please contact help@trigger.dev for assistance.",
};
}
const workflow = await this.#prismaClient.workflow.findFirst({
where: {
slug,
organizationId: organization.id,
},
});
if (!workflow) {
return {
status: "error" as const,
message:
"There was an issue unarchiving this workflow. Please contact help@trigger.dev for assistance.",
};
}
if (!workflow.isArchived) {
return {
status: "error" as const,
message: "This workflow is not currently archived",
};
}
await this.#prismaClient.workflow.update({
where: {
id: workflow.id,
},
data: {
isArchived: false,
archivedAt: null,
},
});
return {
status: "success" as const,
};
}
}
+430 -60
View File
@@ -1,17 +1,208 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-tomorrow */
/* PrismJS 1.14.0
http://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+actionscript+ada+apacheconf+apl+applescript+c+arff+asciidoc+asm6502+aspnet+autohotkey+autoit+bash+basic+batch+bison+brainfuck+bro+cpp+csharp+arduino+coffeescript+clojure+ruby+csp+css-extras+d+dart+diff+django+docker+eiffel+elixir+elm+markup-templating+erlang+fsharp+flow+fortran+gedcom+gherkin+git+glsl+go+graphql+groovy+haml+handlebars+haskell+haxe+http+hpkp+hsts+ichigojam+icon+inform7+ini+io+j+java+jolie+json+julia+keyman+kotlin+latex+less+liquid+lisp+livescript+lolcode+lua+makefile+markdown+erb+matlab+mel+mizar+monkey+n4js+nasm+nginx+nim+nix+nsis+objectivec+ocaml+opencl+oz+parigp+parser+pascal+perl+php+php-extras+sql+powershell+processing+prolog+properties+protobuf+pug+puppet+pure+python+q+qore+r+jsx+typescript+renpy+reason+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+plsql+soy+stylus+swift+tcl+textile+twig+tsx+vbnet+velocity+verilog+vhdl+vim+visual-basic+wasm+wiki+xeora+xojo+yaml&plugins=line-numbers+toolbar+show-language */
/**
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/chriskempson/tomorrow-theme
* @author Rose Pritchard
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
/*
* Dracula Theme for Prism.JS
*
* @author Gustavo Costa
* e-mail: gusbemacbe@gmail.com
*
* @contributor Jon Leopard
* e-mail: jonlprd@gmail.com
*
* @license MIT 2016-2020
*/
/* Scrollbars */
:root {
--background: #0f172a;
--comment: #6272a4;
--foreground: #f8f8f2;
--selection: #44475a;
--cyan: #8be9fd;
--green: #50fa7b;
--orange: #ffb86c;
--pink: #ff79c6;
--purple: #bd93f9;
--red: #ff5555;
--yellow: #f1fa8c;
/* Transparency */
/** 30% of transparency **/
--background-30: #282a3633;
--comment-30: #6272a433;
--foreground-30: #f8f8f233;
--selection-30: #44475a33;
--cyan-30: #8be9fd33;
--green-30: #50fa7b33;
--orange-30: #ffb86c33;
--pink-30: #ff79c633;
--purple-30: #bd93f933;
--red-30: #ff555533;
--yellow-30: #f1fa8c33;
/** 40% of transparency **/
--background-40: #282a3666;
--comment-40: #6272a466;
--foreground-40: #f8f8f266;
--selection-40: #44475a66;
--cyan-40: #8be9fd66;
--green-40: #50fa7b66;
--orange-40: #ffb86c66;
--pink-40: #ff79c666;
--purple-40: #bd93f966;
--red-40: #ff555566;
--yellow-40: #f1fa8c66;
}
/* Selection */
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background-color: var(--selection);
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background-color: var(--selection);
}
/* Line numbers */
pre.line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre.line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid rgb(153, 153, 153);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
/* Toolbar for copying */
div.code-toolbar {
position: relative;
}
div.code-toolbar > .toolbar {
position: absolute;
top: 0.3em;
right: 0.2em;
transition: opacity 0.3s ease-in-out;
opacity: 0;
}
div.code-toolbar:hover > .toolbar {
opacity: 1;
}
div.code-toolbar > .toolbar .toolbar-item {
display: inline-block;
padding-right: 20px;
}
div.code-toolbar > .toolbar a {
cursor: pointer;
}
div.code-toolbar > .toolbar button {
background: none;
border: 0;
color: inherit;
font: inherit;
line-height: normal;
overflow: visible;
padding: 0;
-webkit-user-select: none; /* for button */
-moz-user-select: none;
-ms-user-select: none;
}
div.code-toolbar > .toolbar a,
div.code-toolbar > .toolbar button,
div.code-toolbar > .toolbar span {
color: var(--foreground);
font-size: 0.8em;
padding: 0.5em;
background: var(--comment);
border-radius: 0.5em;
}
div.code-toolbar > .toolbar a:hover,
div.code-toolbar > .toolbar a:focus,
div.code-toolbar > .toolbar button:hover,
div.code-toolbar > .toolbar button:focus,
div.code-toolbar > .toolbar span:hover,
div.code-toolbar > .toolbar span:focus {
color: inherit;
text-decoration: none;
background-color: var(--green);
}
/* Remove text shadow for printing */
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
code[class*="language-"],
pre[class*="language-"] {
color: #ccc;
background: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
font-size: 0.95em;
color: var(--foreground);
background: var(--background);
text-shadow: none;
font-family: PT Mono, Consolas, Monaco, "Andale Mono", "Ubuntu Mono",
monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
@@ -19,9 +210,9 @@ pre[class*="language-"] {
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
@@ -30,94 +221,273 @@ pre[class*="language-"] {
}
/* Code blocks */
pre[class*="language-"] {
padding: 0.5em;
background: var(--background);
border-radius: 0.5em;
padding: 1em;
margin: 0.5em 0;
overflow: auto;
height: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #0f172a;
background: var(--background);
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
padding: 4px 7px;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #999;
/* Code box limit */
.limit-300 {
height: 300px !important;
}
.token.punctuation {
color: #ccc;
.limit-300 {
height: 400px !important;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
color: #e2777a;
.limit-500 {
height: 500px !important;
}
.token.function-name {
color: #6196cc;
.limit-600 {
height: 600px !important;
}
.token.boolean,
.token.number,
.token.function {
color: #f08d49;
.limit-700 {
height: 700px !important;
}
.token.property,
.token.class-name,
.token.constant,
.token.symbol {
color: #f8c555;
.limit-800 {
height: 800px !important;
}
.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
color: #cc99cd;
.language-css {
color: var(--purple);
}
.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
color: #7ec699;
.token {
color: var(--pink);
}
.token.operator,
.token.entity,
.token.url {
color: #67cdcc;
.language-css .token {
color: var(--pink);
}
.token.script {
color: var(--foreground);
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
.token.atrule,
.token.attr-name,
.token.attr-value {
color: var(--green);
}
.token.inserted {
color: green;
.language-css .token.atrule {
color: var(--purple);
}
.language-html .token.attr-value,
.language-markup .token.attr-value {
color: var(--yellow);
}
.token.boolean {
color: var(--purple);
}
.token.builtin,
.token.class-name {
color: var(--cyan);
}
.token.comment {
color: var(--comment);
}
.token.constant {
color: var(--purple);
}
.language-javascript .token.constant {
color: var(--orange);
font-style: italic;
}
.token.entity {
color: var(--pink);
}
.language-css .token.entity {
color: var(--green);
}
.language-html .token.entity.named-entity {
color: var(--purple);
}
.language-html .token.entity:not(.named-entity) {
color: var(--pink);
}
.language-markup .token.entity.named-entity {
color: var(--purple);
}
.language-markup .token.entity:not(.named-entity) {
color: var(--pink);
}
.token.function {
color: var(--green);
}
.language-css .token.function {
color: var(--cyan);
}
.token.important,
.token.keyword {
color: var(--pink);
}
.token.prolog {
color: var(--foreground);
}
.token.property {
color: var(--orange);
}
.language-css .token.property {
color: var(--cyan);
}
.token.punctuation {
color: var(--pink);
}
.language-css .token.punctuation {
color: var(--orange);
}
.language-html .token.punctuation,
.language-markup .token.punctuation {
color: var(--foreground);
}
.token.selector {
color: var(--pink);
}
.language-css .token.selector {
color: var(--green);
}
.token.regex {
color: var(--red);
}
.language-css .token.rule:not(.atrule) {
color: var(--foreground);
}
.token.string {
color: var(--yellow);
}
.token.tag {
color: var(--pink);
}
.token.url {
color: var(--cyan);
}
.language-css .token.url {
color: var(--orange);
}
.token.variable {
color: var(--comment);
}
.token.number {
color: rgba(189, 147, 249, 1);
}
.token.operator {
color: rgba(139, 233, 253, 1);
}
.token.char {
color: rgba(255, 135, 157, 1);
}
.token.symbol {
color: rgba(255, 184, 108, 1);
}
.token.deleted {
color: #e2777a;
}
.token.namespace {
color: #e2777a;
}
/* Line Highlighter */
.highlight-line {
color: inherit;
display: inline-block;
text-decoration: none;
border-radius: 4px;
padding: 2px 10px;
}
.highlight-line:empty:before {
content: " ";
}
.highlight-line:not(:last-child) {
min-width: 100%;
}
.highlight-line .highlight-line:not(:last-child) {
min-width: 0;
}
.highlight-line-isdir {
color: var(--foreground);
background-color: var(--selection-30);
}
.highlight-line-active {
background-color: var(--comment-30);
}
.highlight-line-add {
background-color: var(--green-30);
}
.highlight-line-remove {
background-color: var(--red-30);
}
+2 -1
View File
@@ -63,12 +63,14 @@
"@tailwindcss/forms": "^0.5.2",
"@tanstack/react-table": "^8.0.0-alpha.87",
"@trigger.dev/common-schemas": "workspace:*",
"@trigger.dev/providers": "workspace:*",
"@uiw/react-codemirror": "^4.13.2",
"bcryptjs": "^2.4.3",
"classnames": "^2.3.1",
"clsx": "^1.2.1",
"compression": "^1.7.4",
"cron-parser": "^4.7.1",
"cronstrue": "^2.21.0",
"cross-env": "^7.0.3",
"csstype": "^3.0.10",
"cuid": "^2.1.8",
@@ -78,7 +80,6 @@
"humanize-duration": "^3.27.3",
"internal-integrations": "workspace:*",
"internal-platform": "workspace:*",
"@trigger.dev/providers": "workspace:*",
"internal-pulsar": "workspace:*",
"ioredis": "^5.2.4",
"javascript-time-ago": "^2.5.7",
@@ -0,0 +1,14 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "WorkflowStatus" ADD VALUE 'DISABLED';
ALTER TYPE "WorkflowStatus" ADD VALUE 'ARCHIVED';
-- AlterTable
ALTER TABLE "EventRule" ADD COLUMN "archivedAt" TIMESTAMP(3),
ADD COLUMN "disabledAt" TIMESTAMP(3);
@@ -0,0 +1,14 @@
/*
Warnings:
- You are about to drop the column `archivedAt` on the `EventRule` table. All the data in the column will be lost.
- You are about to drop the column `disabledAt` on the `EventRule` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "EventRule" DROP COLUMN "archivedAt",
DROP COLUMN "disabledAt";
-- AlterTable
ALTER TABLE "Workflow" ADD COLUMN "archivedAt" TIMESTAMP(3),
ADD COLUMN "disabledAt" TIMESTAMP(3);
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EventRule" ADD COLUMN "enabled" BOOLEAN NOT NULL DEFAULT true;
@@ -0,0 +1,19 @@
/*
Warnings:
- The values [ARCHIVED] on the enum `WorkflowStatus` will be removed. If these variants are still used in the database, this will fail.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "WorkflowStatus_new" AS ENUM ('CREATED', 'READY', 'DISABLED');
ALTER TABLE "Workflow" ALTER COLUMN "status" DROP DEFAULT;
ALTER TABLE "Workflow" ALTER COLUMN "status" TYPE "WorkflowStatus_new" USING ("status"::text::"WorkflowStatus_new");
ALTER TYPE "WorkflowStatus" RENAME TO "WorkflowStatus_old";
ALTER TYPE "WorkflowStatus_new" RENAME TO "WorkflowStatus";
DROP TYPE "WorkflowStatus_old";
ALTER TABLE "Workflow" ALTER COLUMN "status" SET DEFAULT 'CREATED';
COMMIT;
-- AlterTable
ALTER TABLE "Workflow" ADD COLUMN "isArchived" BOOLEAN NOT NULL DEFAULT false;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Workflow" ADD COLUMN "triggerTtlInSeconds" INTEGER NOT NULL DEFAULT 3600;
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "WorkflowRunStatus" ADD VALUE 'TIMED_OUT';
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "WorkflowRun" ADD COLUMN "timedOutAt" TIMESTAMP(3),
ADD COLUMN "timedOutReason" TEXT;
+13
View File
@@ -138,6 +138,12 @@ model Workflow {
service String @default("trigger")
eventNames String[]
disabledAt DateTime?
archivedAt DateTime?
isArchived Boolean @default(false)
triggerTtlInSeconds Int @default(3600)
@@unique([organizationId, slug])
}
@@ -161,6 +167,8 @@ model EventRule {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
enabled Boolean @default(true)
runs WorkflowRun[]
@@unique([workflowId, environmentId])
@@ -178,6 +186,7 @@ enum TriggerType {
enum WorkflowStatus {
CREATED
READY
DISABLED
}
model ExternalSource {
@@ -399,6 +408,9 @@ model WorkflowRun {
startedAt DateTime?
finishedAt DateTime?
timedOutAt DateTime?
timedOutReason String?
isTest Boolean @default(false)
requests IntegrationRequest[]
delays DurableDelay[]
@@ -410,6 +422,7 @@ enum WorkflowRunStatus {
DISCONNECTED
SUCCESS
ERROR
TIMED_OUT
}
model WorkflowRunStep {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -58,6 +58,7 @@ async function main() {
client: pulsarClient,
config: {
topic: Topics.runCommands,
batchingEnabled: false,
},
});
+5 -1
View File
@@ -72,4 +72,8 @@ function oauth2AuthenticationFromEnv(): AuthenticationOauth2 | undefined {
});
}
export const pulsarClient = createPulsarClient();
export const pulsarClient = createPulsarClient({
ioThreads: 4,
messageListenerThreads: 4,
operationTimeoutSeconds: 30,
});
+31 -2
View File
@@ -296,7 +296,7 @@ export class TriggerServer {
}
async #initializeWorkflow(
data: z.infer<typeof ServerRPCSchema["INITIALIZE_HOST"]["request"]>
data: z.infer<(typeof ServerRPCSchema)["INITIALIZE_HOST"]["request"]>
) {
if (this.#isInitialized) {
throw new Error(
@@ -324,6 +324,7 @@ export class TriggerServer {
name: data.packageName,
version: data.packageVersion,
},
triggerTTL: data.triggerTTL,
});
this.#workflowId = response.id;
@@ -340,7 +341,7 @@ export class TriggerServer {
subscriptionInitialPosition: "Earliest",
},
handlers: {
TRIGGER_WORKFLOW: async (id, data, properties) => {
TRIGGER_WORKFLOW: async (id, data, properties, messageAttributes) => {
this.#logger.debug("Received trigger", id, data, properties);
// If the API keys don't match, then we should ignore it
// This ensures the workflow is triggered for the correct environment
@@ -366,6 +367,33 @@ export class TriggerServer {
);
}
if (properties["x-ttl"] && messageAttributes.eventTimestamp) {
const ttl = properties["x-ttl"];
const eventTimestamp = messageAttributes.eventTimestamp;
const now = Date.now();
const elapsedMilliseconds = now - eventTimestamp.getTime();
const elapsedSeconds = elapsedMilliseconds / 1000;
if (elapsedSeconds > ttl) {
this.#logger.debug("Message is expired, ignoring", {
messageAttributes,
properties,
});
await this.#commandPublisher.publish(
"WORKFLOW_RUN_TRIGGER_TIMEOUT",
{
id: data.id,
ttl,
elapsedSeconds,
},
{ ...properties, "x-timestamp": String(Date.now()) }
);
return true;
}
}
this.#logger.debug("Triggering workflow", data, properties);
const runController = new WorkflowRunController({
@@ -502,6 +530,7 @@ function createForwardHandler<
client: pulsarClient,
config: {
topic: topicName,
batchingEnabled: false,
},
});
+87 -20
View File
@@ -1,31 +1,98 @@
import { Trigger } from "@trigger.dev/sdk";
import { github } from "@trigger.dev/integrations";
const trigger = new Trigger({
new Trigger({
id: "github-webhook-9",
name: "GitHub Issue changes for trigger.dev",
name: "GitHub issue: triggerdotdev/trigger.dev-examples",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: github.events.repoIssueEvent({ repo: "triggerdotdev/trigger.dev" }),
on: github.events.issueEvent({
repo: "triggerdotdev/trigger.dev-examples",
}),
run: async (event, ctx) => {
await ctx.logger.info(
"Inside the github-webhook workflow, received event",
{
event,
}
);
await ctx.logger.info(`Action was ${event.action}`);
if (event.action === "assigned") {
await ctx.logger.info(`New assignee: ${event.assignee.login}`);
// await slack.sendMessage({
// channel: "C01BQKZJZ7M",
// text: `New issue: ${event.issue.title}`,
// });
}
return event;
return {};
},
});
}).listen();
trigger.listen();
new Trigger({
id: "github-webhook-pull_request",
name: "GitHub PR: triggerdotdev/trigger.dev-examples",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: github.events.pullRequestEvent({
repo: "triggerdotdev/trigger.dev-examples",
}),
run: async (event, ctx) => {
await ctx.logger.info(`Action was ${event.action}`);
return {};
},
}).listen();
new Trigger({
id: "github-webhook-pull_request_comment",
name: "GitHub PR review comment: triggerdotdev/trigger.dev-examples",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: github.events.pullRequestCommentEvent({
repo: "triggerdotdev/trigger.dev-examples",
}),
run: async (event, ctx) => {
await ctx.logger.info(`Action was ${event.action}`);
return {};
},
}).listen();
new Trigger({
id: "github-webhook-pull_request_review",
name: "GitHub PR review: triggerdotdev/trigger.dev-examples",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: github.events.pullRequestReviewEvent({
repo: "triggerdotdev/trigger.dev-examples",
}),
run: async (event, ctx) => {
await ctx.logger.info(`Action was ${event.action}`);
return {};
},
}).listen();
new Trigger({
id: "github-webhook-push",
name: "GitHub push: triggerdotdev/trigger.dev-examples",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: github.events.pushEvent({
repo: "triggerdotdev/trigger.dev-examples",
}),
run: async (event, ctx) => {
await ctx.logger.info(`Push with commits`, event.commits);
return {};
},
}).listen();
new Trigger({
id: "github-webhook-commit_comment",
name: "GitHub commit comment: triggerdotdev/trigger.dev-examples",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: github.events.commitCommentEvent({
repo: "triggerdotdev/trigger.dev-examples",
}),
run: async (event, ctx) => {
await ctx.logger.info(`Push with action ${event.action}`);
return {};
},
}).listen();
+269 -268
View File
@@ -2,8 +2,23 @@ import { Trigger, customEvent, scheduleEvent } from "@trigger.dev/sdk";
import { github, slack } from "@trigger.dev/integrations";
import { z } from "zod";
// Workflow that sends a message to Slack
// // Workflow that sends a message to Slack
// // new Trigger({
// // id: "playground-1",
// // name: "Post to Slack immediately",
// // apiKey: "trigger_dev_zC25mKNn6c0q",
// // endpoint: "ws://localhost:8889/ws",
// // on: customEvent({
// // name: "playground",
// // schema: z.object({
// // id: z.string(),
// // }),
// // }),
// // run: async (event, ctx) => {
// // await ctx.logger.info(
// // "This workflow will post a message to Slack immediately and display an Error, Info, Debug, and Warning message."
// // );
// new Trigger({
// id: "playground-1",
// name: "Post to Slack immediately",
@@ -19,113 +34,6 @@ import { z } from "zod";
// await ctx.logger.info(
// "This workflow will post a message to Slack immediately and display an Error, Info, Debug, and Warning message."
// );
new Trigger({
id: "playground-1",
name: "Post to Slack immediately",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
on: customEvent({
name: "playground",
schema: z.object({
id: z.string(),
}),
}),
run: async (event, ctx) => {
await ctx.logger.info(
"This workflow will post a message to Slack immediately and display an Error, Info, Debug, and Warning message."
);
await ctx.logger.error("Error message!", { event });
await ctx.logger.info("Info message", { event });
await ctx.logger.debug("Debug message");
await ctx.logger.warn("Warning message!");
const response = await slack.postMessage("send-to-slack", {
channel: "test-integrations",
text: `This is a message from the "Posts to Slack" workflow ${event.id}`,
});
return response.message;
},
}).listen();
// Webhook workflow that sends a message to Slack when a Github issue is created after 2 delays
new Trigger({
id: "playground-2",
name: "Posts to Slack after a GitHub Issue created",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
on: github.events.repoIssueEvent({ repo: "triggerdotdev/trigger.dev" }),
run: async (event, ctx) => {
await ctx.logger.info(
"This workflow will post to Slack when a GitHub Issue created or modified after 2 delays."
);
await ctx.waitFor("initial-wait", { seconds: 30 });
await ctx.waitUntil("wait-until", new Date(Date.now() + 1000 * 30));
await ctx.logger.info("Both types of delay happened");
const response = await slack.postMessage("send-to-slack", {
channel: "test-integrations",
text: `This is a message posts after an Issue was created on GitHub ${event.action}`,
});
await ctx.logger.debug("Debug message");
await ctx.logger.warn("Warning message!");
return response.message;
},
}).listen();
// Workflow that shows all the log types with a 5 second delay between each
new Trigger({
id: "playground-3",
name: "All log types with a 5 second delay between each",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
on: customEvent({
name: "playground",
schema: z.object({
id: z.string(),
}),
}),
run: async (event, ctx) => {
await ctx.logger.info(
"This workflow prints all the log types with a 5 second delay between each."
);
await ctx.logger.info(
"Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it. Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it. Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it. Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it."
);
await ctx.waitFor("first-wait", { seconds: 5 });
await ctx.logger.error(
"This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message!",
{ event }
);
await ctx.waitFor("second-wait", { seconds: 5 });
await ctx.logger.info(
"This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message."
);
await ctx.waitFor("third-wait", { seconds: 5 });
await ctx.logger.debug(
"This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. "
);
// await ctx.logger.error("Error message!", { event });
@@ -219,129 +127,276 @@ new Trigger({
// "This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. "
// );
// await ctx.waitFor("fourth-wait", { seconds: 5 });
// // await ctx.logger.error("Error message!", { event });
// await ctx.logger.warn(
// "This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! "
// );
// // await ctx.logger.info("Info message", { event });
// await ctx.waitFor("fifth-wait", { seconds: 5 });
// // await ctx.logger.debug("Debug message");
// const response = await slack.postMessage("send-to-slack", {
// channel: "test-integrations",
// text: `This test displays all the log types in the webapp ${event.id}`,
// });
// // await ctx.logger.warn("Warning message!");
// return response.message;
// },
// }).listen();
// // const response = await slack.postMessage("send-to-slack", {
// // channel: "test-integrations",
// // text: `This is a message from the "Posts to Slack" workflow ${event.id}`,
// // });
// // Workflow that sends 2 messages to Slack with a 10 second delay between both
// // return response.message;
// // },
// // }).listen();
// new Trigger({
// id: "playground-4",
// name: "Post to Slack twice after 10 second delays",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// // // Webhook workflow that sends a message to Slack when a Github issue is created after 2 delays
// // new Trigger({
// // id: "playground-2",
// // name: "Posts to Slack after a GitHub Issue created",
// // apiKey: "trigger_dev_zC25mKNn6c0q",
// // endpoint: "ws://localhost:8889/ws",
// // on: github.events.repoIssueEvent({ repo: "triggerdotdev/trigger.dev" }),
// // run: async (event, ctx) => {
// // await ctx.logger.info(
// // "This workflow will post to Slack when a GitHub Issue created or modified after 2 delays."
// // );
// // await ctx.waitFor("initial-wait", { seconds: 30 });
// // await ctx.waitUntil("wait-until", new Date(Date.now() + 1000 * 30));
// // await ctx.logger.info("Both types of delay happened");
// // const response = await slack.postMessage("send-to-slack", {
// // channel: "test-integrations",
// // text: `This is a message posts after an Issue was created on GitHub ${event.action}`,
// // });
// // await ctx.logger.debug("Debug message");
// // await ctx.logger.warn("Warning message!");
// // return response.message;
// // },
// // }).listen();
// // // Workflow that shows all the log types with a 5 second delay between each
// // new Trigger({
// // id: "playground-3",
// // name: "All log types with a 5 second delay between each",
// // apiKey: "trigger_dev_zC25mKNn6c0q",
// // endpoint: "ws://localhost:8889/ws",
// // on: customEvent({
// // name: "playground",
// // schema: z.object({
// // id: z.string(),
// // }),
// // }),
// // run: async (event, ctx) => {
// // await ctx.logger.info(
// // "This workflow prints all the log types with a 5 second delay between each."
// // );
// // await ctx.logger.info(
// // "Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it. Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it. Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it. Hey there! This is a really long message to see how the layout handles it. If this breaks the layout, I will fix it."
// // );
// // await ctx.waitFor("first-wait", { seconds: 5 });
// // await ctx.logger.error(
// // "This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message! This is a really long Error message!",
// // { event }
// // );
// // await ctx.waitFor("second-wait", { seconds: 5 });
// // await ctx.logger.info(
// // "This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message. This is a really long Info message."
// // );
// // await ctx.waitFor("third-wait", { seconds: 5 });
// // await ctx.logger.debug(
// // "This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. This is a really long Debug message. "
// // );
// // await ctx.waitFor("fourth-wait", { seconds: 5 });
// // await ctx.logger.warn(
// // "This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! This is a really long Warning message! "
// // );
// // await ctx.waitFor("fifth-wait", { seconds: 5 });
// // const response = await slack.postMessage("send-to-slack", {
// // channel: "test-integrations",
// // text: `This test displays all the log types in the webapp ${event.id}`,
// // });
// // return response.message;
// // },
// // }).listen();
// // // Workflow that sends 2 messages to Slack with a 10 second delay between both
// // new Trigger({
// // id: "playground-4",
// // name: "Post to Slack twice after 10 second delays",
// // apiKey: "trigger_dev_zC25mKNn6c0q",
// // endpoint: "ws://localhost:8889/ws",
// // on: customEvent({
// // name: "playground",
// // schema: z.object({
// // id: z.string(),
// // }),
// // }),
// // run: async (event, ctx) => {
// // await ctx.logger.info(
// // "This workflow sends 2 messages to Slack with a 10 second delay between both."
// // );
// // await ctx.waitFor("initial-wait", { seconds: 10 });
// // await slack.postMessage("send-to-slack-1", {
// // channel: "test-integrations",
// // text: `This is test message 1/2 from "Post to Slack twice after 10 second delays" workflow ${event.id}`,
// // });
// // await ctx.waitFor("second-wait", { seconds: 10 });
// // const response = await slack.postMessage("send-to-slack-2", {
// // channel: "test-integrations",
// // text: `This is test message 2/2 from "Post to Slack twice after 10 second delays" workflow ${event.id}`,
// // });
// // return response.message;
// // },
// // }).listen();
// // // Workflow that send a message to Slack after 1 hour
// // new Trigger({
// // id: "playground-5",
// // name: "Post to Slack after 1 hour",
// // apiKey: "trigger_dev_zC25mKNn6c0q",
// // endpoint: "ws://localhost:8889/ws",
// // on: customEvent({
// // name: "playground",
// // schema: z.object({
// // id: z.string(),
// // }),
// // }),
// // run: async (event, ctx) => {
// // await ctx.logger.info(
// // "This workflow posts a message to Slack after a 1 hour delay"
// // );
// // await ctx.waitUntil("wait-until", new Date(Date.now() + 1000 * 60 * 60));
// // await ctx.logger.info("The workflow resumed after the 1 hour delay", {
// // event,
// // });
// // const response = await slack.postMessage("send-to-slack", {
// // channel: "test-integrations",
// // text: `This message was sent after a 1 hour delay ${event.id}`,
// // });
// // return response.message;
// // },
// // }).listen();
// // // Workflow that send a message to Slack after 24 hours
// // new Trigger({
// // id: "playground-6",
// // name: "Post to Slack after a 24 hour delay",
// // apiKey: "trigger_dev_zC25mKNn6c0q",
// // endpoint: "ws://localhost:8889/ws",
// // on: customEvent({
// // name: "playground",
// // schema: z.object({
// // id: z.string(),
// // }),
// // }),
// // run: async (event, ctx) => {
// // await ctx.logger.info(
// // "This workflow posts a message to Slack after a 24 hour delay"
// // );
// // await ctx.waitUntil(
// // "wait-until",
// // new Date(Date.now() + 1000 * 60 * 60 * 24)
// // );
// // await ctx.logger.info("The workflow resumed after the 24 hour delay", {
// // event,
// // });
// // const response = await slack.postMessage("send-to-slack", {
// // channel: "test-integrations",
// // text: `This message was sent after a 24 hour delay ${event.id}`,
// // });
// // return response.message;
// // },
// // }).listen();
// // // Workflow that send a message to Slack after 24 hours
// // new Trigger({
// // id: "playground-7",
// // name: "Post to Slack many times in a loop",
// // apiKey: "trigger_dev_zC25mKNn6c0q",
// // endpoint: "ws://localhost:8889/ws",
// // on: customEvent({
// // name: "playground",
// // schema: z.object({
// // id: z.string(),
// // }),
// // }),
// // run: async (event, ctx) => {
// // for (let index = 0; index < 4; index++) {
// // const response = await slack.postMessage(`send-to-slack-${index}`, {
// // channel: "test-integrations",
// // text: `This is a post to Slack many times in a loop ${index} ${event.id}`,
// // });
// // }
// // return {};
// // },
// // }).listen();
// const postMessage = new Trigger({
// id: "new-user",
// name: "New user slack message",
// apiKey: "trigger_development_lwlXEjyhSNF4",
// endpoint: "ws://localhost:8889/ws",
// logLevel: "info",
// on: customEvent({
// name: "playground",
// name: "user.created",
// schema: z.object({
// id: z.string(),
// name: z.string(),
// email: z.string(),
// paidPlan: z.boolean(),
// }),
// }),
// run: async (event, ctx) => {
// await ctx.logger.info(
// "This workflow sends 2 messages to Slack with a 10 second delay between both."
// );
// await ctx.waitFor("initial-wait", { seconds: 10 });
// await slack.postMessage("send-to-slack-1", {
// channel: "test-integrations",
// text: `This is test message 1/2 from "Post to Slack twice after 10 second delays" workflow ${event.id}`,
// });
// await ctx.waitFor("second-wait", { seconds: 10 });
// const response = await slack.postMessage("send-to-slack-2", {
// channel: "test-integrations",
// text: `This is test message 2/2 from "Post to Slack twice after 10 second delays" workflow ${event.id}`,
// });
// return response.message;
// },
// }).listen();
// // Workflow that send a message to Slack after 1 hour
// new Trigger({
// id: "playground-5",
// name: "Post to Slack after 1 hour",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// endpoint: "ws://localhost:8889/ws",
// on: customEvent({
// name: "playground",
// schema: z.object({
// id: z.string(),
// }),
// }),
// run: async (event, ctx) => {
// await ctx.logger.info(
// "This workflow posts a message to Slack after a 1 hour delay"
// );
// await ctx.waitUntil("wait-until", new Date(Date.now() + 1000 * 60 * 60));
// await ctx.logger.info("The workflow resumed after the 1 hour delay", {
// event,
// });
// await ctx.logger.info("This log will appear on the Trigger.dev run page");
// //send a message to the #new-users Slack channel with user details
// const response = await slack.postMessage("send-to-slack", {
// channel: "test-integrations",
// text: `This message was sent after a 1 hour delay ${event.id}`,
// channel: "new-users",
// text: `New user: ${event.name} (${event.email}) signed up. ${
// event.paidPlan ? "They are paying" : "They are on the free plan"
// }.`,
// });
// return response.message;
// },
// }).listen();
// });
// // Workflow that send a message to Slack after 24 hours
// new Trigger({
// id: "playground-6",
// name: "Post to Slack after a 24 hour delay",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// endpoint: "ws://localhost:8889/ws",
// on: customEvent({
// name: "playground",
// schema: z.object({
// id: z.string(),
// }),
// }),
// run: async (event, ctx) => {
// await ctx.logger.info(
// "This workflow posts a message to Slack after a 24 hour delay"
// );
// await ctx.waitUntil(
// "wait-until",
// new Date(Date.now() + 1000 * 60 * 60 * 24)
// );
// await ctx.logger.info("The workflow resumed after the 24 hour delay", {
// event,
// });
// const response = await slack.postMessage("send-to-slack", {
// channel: "test-integrations",
// text: `This message was sent after a 24 hour delay ${event.id}`,
// });
// return response.message;
// },
// }).listen();
// // Workflow that send a message to Slack after 24 hours
// //this workflow will now connect and start listening for events
// postMessage.listen();
// new Trigger({
// id: "playground-7",
@@ -366,68 +421,14 @@ new Trigger({
// },
// }).listen();
const postMessage = new Trigger({
id: "new-user",
name: "New user slack message",
apiKey: "trigger_development_lwlXEjyhSNF4",
endpoint: "ws://localhost:8889/ws",
logLevel: "info",
on: customEvent({
name: "user.created",
schema: z.object({
name: z.string(),
email: z.string(),
paidPlan: z.boolean(),
}),
}),
run: async (event, ctx) => {
await ctx.logger.info("This log will appear on the Trigger.dev run page");
//send a message to the #new-users Slack channel with user details
const response = await slack.postMessage("send-to-slack", {
channel: "new-users",
text: `New user: ${event.name} (${event.email}) signed up. ${
event.paidPlan ? "They are paying" : "They are on the free plan"
}.`,
});
return response.message;
},
});
//this workflow will now connect and start listening for events
postMessage.listen();
new Trigger({
id: "playground-7",
name: "Post to Slack many times in a loop",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
on: customEvent({
name: "playground",
schema: z.object({
id: z.string(),
}),
}),
run: async (event, ctx) => {
for (let index = 0; index < 4; index++) {
const response = await slack.postMessage(`send-to-slack-${index}`, {
channel: "test-integrations",
text: `This is a post to Slack many times in a loop ${index} ${event.id}`,
});
}
return {};
},
}).listen();
new Trigger({
id: "scheduled-workflow",
name: "Scheduled Workflow",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: scheduleEvent({ rateOf: { minutes: 5 } }),
triggerTTL: 5,
on: scheduleEvent({ rateOf: { minutes: 4 } }),
run: async (event, ctx) => {
await ctx.logger.info("Received the scheduled event", {
event,
+2 -2
View File
@@ -21,11 +21,11 @@ const trigger = new Trigger({
"Received domain.created event, waiting for 1 minutes..."
);
await ctx.waitFor("initial-wait", { minutes: 1 });
await ctx.waitFor("initial-wait", { seconds: 5 });
const response = await slack.postMessage("send-to-slack", {
channel: "test-integrations",
text: `New domain created: ${event.domain} by customer ${event.customerId}`,
text: `New domain created: ${event.domain} by customer ${event.customerId} cc @Eric #general`,
});
await ctx.logger.debug("Debug message");
+2 -10
View File
@@ -11,7 +11,8 @@ const trigger = new Trigger({
name: "My workflow",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
logLevel: "log",
triggerTTL: 60 * 60 * 24,
on: customEvent({ name: "user.created", schema: userCreatedEvent }),
run: async (event, ctx) => {
await ctx.logger.info("Inside the smoke test workflow, received event", {
@@ -34,12 +35,3 @@ const trigger = new Trigger({
});
trigger.listen();
(async () => {
process.env.TRIGGER_API_KEY = "trigger_dev_zC25mKNn6c0q";
process.env.TRIGGER_API_URL = "http://localhost:3000";
await sendEvent(ulid(), {
name: "user.created",
payload: { id: "123" },
});
})();
+176 -2
View File
@@ -62,7 +62,9 @@
"packages/internal-pulsar/src/**",
"./pnpm-lock.yaml"
],
"dependsOn": ["p-db"],
"dependsOn": [
"p-db"
],
"envVariables": {
"FROM_EMAIL": "hello@email.trigger.dev",
"REPLY_TO_EMAIL": "hello@trigger.dev",
@@ -170,6 +172,178 @@
"connectionStringEnvVarName": "REDIS_URL"
}
]
},
{
"id": "staging",
"name": "Staging",
"region": "us-east-1",
"source": {
"branch": "dev",
"trigger": "manual"
},
"envVariables": {
"PULSAR_ISSUER_URL": "https://auth.streamnative.cloud/",
"PULSAR_AUDIENCE": {
"fromParameterStore": "/Staging/Pulsar/Audience"
},
"PULSAR_TENANT": "triggerdotdev",
"PULSAR_WORKFLOWS_NAMESPACE": "workflows",
"PULSAR_QUEUES_NAMESPACE": "queues",
"PULSAR_SERVICE_URL": {
"fromParameterStore": "/Staging/Pulsar/ServiceURL"
},
"DOCKER_USERNAME": "maverickdotdev",
"DOCKER_PASSWORD": {
"fromParameterStore": "/Docker/Password"
}
},
"services": [
{
"id": "s-webapp",
"name": "Staging Webapp",
"type": "fargate",
"buildType": "docker",
"dockerfilePath": "./apps/webapp/Dockerfile",
"dockerContext": ".",
"cpu": 0.5,
"memory": 2,
"minInstances": 1,
"maxInstances": 2,
"domain": "app-staging.trigger.dev",
"port": 3000,
"healthCheckPath": "/healthcheck",
"watchPaths": [
"apps/webapp/app/**",
"apps/webapp/public/**",
"apps/webapp/Dockerfile",
"apps/webapp/package.json",
"apps/webapp/server.ts",
"apps/webapp/remix.config.js",
"apps/webapp/tailwind.config.js",
"apps/webapp/tsconfig.json",
"apps/webapp/postcss.config.js",
"apps/webapp/styles/**",
"apps/webapp/prisma/**",
"apps/webapp/types/**",
"packages/internal-platform/src/**",
"packages/common-schemas/src/**",
"packages/internal-integrations/src/**",
"packages/trigger-providers/src/**",
"packages/emails/src/**",
"packages/emails/emails/**",
"packages/internal-pulsar/src/**",
"./pnpm-lock.yaml"
],
"dependsOn": [
"s-db"
],
"envVariables": {
"FROM_EMAIL": "hello@email.trigger.dev",
"REPLY_TO_EMAIL": "hello@trigger.dev",
"RESEND_API_KEY": {
"fromParameterStore": "/Staging/webapp/RESEND_API_KEY"
},
"SENTRY_DSN": {
"fromParameterStore": "SENTRY_DSN"
},
"SESSION_SECRET": {
"fromParameterStore": "/Staging/webapp/SESSION_SECRET"
},
"MAGIC_LINK_SECRET": {
"fromParameterStore": "/Staging/webapp/MAGIC_LINK_SECRET"
},
"GITHUB_CLIENT_ID": "724e56fcebe940eb5b22",
"GITHUB_SECRET": {
"fromParameterStore": "/Staging/webapp/GITHUB_SECRET"
},
"DATABASE_URL": {
"fromService": {
"id": "s-db",
"value": "dbConnectionString"
}
},
"PULSAR_CLIENT_ID": {
"fromParameterStore": "/Staging/Pulsar/webapp/ClientId"
},
"PULSAR_CLIENT_SECRET": {
"fromParameterStore": "/Staging/Pulsar/webapp/ClientSecret"
},
"PULSAR_ROLE": {
"fromParameterStore": "/Staging/Pulsar/webapp/Role"
},
"PIZZLY_HOST": "https://staging-auth.trigger.dev",
"PIZZLY_SECRET_KEY": {
"fromParameterStore": "/Staging/webapp/PIZZLY_SECRET_KEY"
},
"TRIGGER_LOG_LEVEL": "debug",
"PULSAR_ENABLED": "1",
"PULSAR_DEBUG": true
}
},
{
"id": "s-wss",
"name": "Staging WSS",
"type": "fargate",
"buildType": "docker",
"dockerfilePath": "./apps/wss/Dockerfile",
"dockerContext": ".",
"cpu": 1,
"memory": 2,
"minInstances": 1,
"maxInstances": 2,
"domain": "wss-staging.trigger.dev",
"port": 8889,
"healthCheckPath": "/healthcheck",
"watchPaths": [
"apps/wss/src/**",
"apps/wss/tsconfig.json",
"apps/wss/tsup.config.ts",
"apps/wss/package.json",
"packages/internal-platform/src/**",
"packages/internal-bridge/src/**",
"packages/common-schemas/src/**",
"packages/internal-pulsar/src/**",
"./pnpm-lock.yaml"
],
"envVariables": {
"PULSAR_CLIENT_ID": {
"fromParameterStore": "/Staging/Pulsar/wss/ClientId"
},
"PULSAR_CLIENT_SECRET": {
"fromParameterStore": "/Staging/Pulsar/wss/ClientSecret"
},
"PULSAR_ROLE": {
"fromParameterStore": "/Staging/Pulsar/wss/Role"
},
"PLATFORM_API_URL": "https://app.trigger.dev",
"TRIGGER_LOG_LEVEL": "debug"
}
},
{
"id": "s-db",
"name": "Staging Database",
"type": "rds",
"engine": "postgres",
"applyChangesImmediately": true,
"engineVersion": "14",
"autoUpgradeMinorVersions": true,
"instanceSize": "db.t3.large",
"storage": 100,
"maxStorage": 400,
"private": false,
"deletionProtection": true,
"port": 5432
},
{
"id": "s-redis",
"name": "Staging Redis",
"type": "elasticache",
"engine": "redis",
"engineVersion": "7.0",
"instanceSize": "cache.t3.small",
"connectionStringEnvVarName": "REDIS_URL"
}
]
}
]
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import { EventFilterSchema } from "./events";
import { EventFilterSchema, ScheduleSourceSchema } from "./events";
import { JsonSchema } from "./json";
export const CustomEventTriggerSchema = z.object({
@@ -31,7 +31,7 @@ export const ScheduledEventTriggerSchema = z.object({
type: z.literal("SCHEDULE"),
service: z.literal("scheduler"),
name: z.string(),
source: JsonSchema,
source: ScheduleSourceSchema,
});
export type ScheduledEventTrigger = z.infer<typeof ScheduledEventTriggerSchema>;
+16 -9
View File
@@ -1,14 +1,21 @@
// Create a logger class that uses the debug package internally
export type LogLevel = "log" | "error" | "warn" | "info" | "debug";
const logLevels = [
"disabled",
"log",
"error",
"warn",
"info",
"debug",
] as const;
const logLevels: Array<LogLevel> = ["log", "error", "warn", "info", "debug"];
export type LogLevel = (typeof logLevels)[number];
export class Logger {
#name: string;
readonly #level: number;
constructor(name: string, level: LogLevel = "info") {
constructor(name: string, level: LogLevel = "disabled") {
this.#name = name;
this.#level = logLevels.indexOf(
(process.env.TRIGGER_LOG_LEVEL ?? level) as LogLevel
@@ -16,31 +23,31 @@ export class Logger {
}
log(...args: any[]) {
if (this.#level < 0) return;
if (this.#level < 1) return;
console.log(`[${formattedDateTime()}] [${this.#name}] `, ...args);
console.log(`[${this.#name}] `, ...args);
}
error(...args: any[]) {
if (this.#level < 1) return;
if (this.#level < 2) return;
console.error(`[${formattedDateTime()}] [${this.#name}] `, ...args);
}
warn(...args: any[]) {
if (this.#level < 2) return;
if (this.#level < 3) return;
console.warn(`[${formattedDateTime()}] [${this.#name}] `, ...args);
}
info(...args: any[]) {
if (this.#level < 3) return;
if (this.#level < 4) return;
console.info(`[${formattedDateTime()}] [${this.#name}] `, ...args);
}
debug(...args: any[]) {
if (this.#level < 4) return;
if (this.#level < 5) return;
console.debug(`[${formattedDateTime()}] [${this.#name}] `, ...args);
}
@@ -58,6 +58,7 @@ export const ServerRPCSchema = {
trigger: TriggerMetadataSchema,
packageVersion: z.string(),
packageName: z.string(),
triggerTTL: z.number().optional(),
}),
response: z
.discriminatedUnion("type", [
@@ -81,7 +82,7 @@ export const ServerRPCSchema = {
COMPLETE_WORKFLOW_RUN: {
request: z.object({
runId: z.string(),
output: z.string(),
output: z.string().optional(),
timestamp: z.string(),
}),
response: z.boolean(),
+11 -9
View File
@@ -1,6 +1,7 @@
import { z, ZodError } from "zod";
import { createHash } from "node:crypto";
import { IConnection } from "./types";
import { Logger } from "./logger";
export const RPCMessageSchema = z.object({
id: z.string(),
@@ -45,6 +46,7 @@ export class ZodRPC<
#receiver: ReceiverSchema;
#handlers: ZodRPCHandlers<ReceiverSchema>;
#pendingCalls = new Map<string, onResponseCallback>();
#logger: Logger = new Logger("ZodRPC");
constructor(options: ZodRPCOptions<SenderSchema, ReceiverSchema>) {
this.#connection = options.connection;
@@ -73,7 +75,7 @@ export class ZodRPC<
await this.#onResponse(data);
}
} catch (err) {
console.error(err);
this.#logger.error(err);
}
}
@@ -82,14 +84,12 @@ export class ZodRPC<
await this.#handleCall(message);
} catch (callError) {
if (callError instanceof ZodError) {
console.error(
`[ZodRPC][foobar] Received invalid call:\n${JSON.stringify(
message
)}: `,
this.#logger.error(
`[ZodRPC] Received invalid call:\n${JSON.stringify(message)}: `,
callError.errors
);
} else {
console.error(
this.#logger.error(
`[ZodRPC] Error handling call:\n${JSON.stringify(message)}: `,
callError
);
@@ -102,12 +102,12 @@ export class ZodRPC<
await this.#handleResponse(message);
} catch (callError) {
if (callError instanceof ZodError) {
console.error(
this.#logger.error(
`[ZodRPC] Received invalid response\n\n${JSON.stringify(message)}: `,
callError.flatten()
);
} else {
console.error(
this.#logger.error(
`[ZodRPC] Error handling response\n\n${JSON.stringify(message)}: `,
callError
);
@@ -153,6 +153,8 @@ export class ZodRPC<
throw new Error(`There is no method for ${message.methodName}`);
}
this.#logger.debug("Received call", { message });
// struggling to get real inference here
const inputs = method.request.parse(message.data);
@@ -169,7 +171,7 @@ export class ZodRPC<
try {
await this.#connection.send(preparedResponseText);
} catch (err) {
console.error("Failed sending response", preparedResponseText, err);
this.#logger.error("Failed sending response", preparedResponseText, err);
}
return;
@@ -8,14 +8,15 @@ import { Webhooks } from "@octokit/webhooks";
import { z } from "zod";
import { github } from "@trigger.dev/providers";
import { getAccessToken } from "../accessInfo";
import fetch from "node-fetch";
export class GitHubWebhookIntegration implements WebhookIntegration {
keyForSource(source: unknown): string {
const githubSource = parseWebhookSource(source);
if (githubSource.subresource === "repository") {
return `repository.${githubSource.repo}`;
return `repository.${githubSource.repo}.${githubSource.events.join(
"."
)}}`;
} else if (githubSource.subresource === "organization") {
return `organization.${githubSource.org}`;
} else {
@@ -11,9 +11,15 @@ import { slack } from "@trigger.dev/providers";
import debug from "debug";
import { getAccessToken } from "../accessInfo";
import { z } from "zod";
const log = debug("trigger:integrations:slack");
const SendSlackMessageRequestBodySchema =
slack.schemas.PostMessageBodySchema.extend({
link_names: z.literal(1),
});
class SlackRequestIntegration implements RequestIntegration {
#joinChannelEndpoint = new HttpEndpoint<
typeof slack.schemas.JoinConversationResponseSchema,
@@ -32,7 +38,7 @@ class SlackRequestIntegration implements RequestIntegration {
#postMessageEndpoint = new HttpEndpoint<
typeof slack.schemas.PostMessageResponseSchema,
typeof slack.schemas.PostMessageBodySchema
typeof SendSlackMessageRequestBodySchema
>({
response: slack.schemas.PostMessageResponseSchema,
method: "POST",
@@ -101,6 +107,7 @@ class SlackRequestIntegration implements RequestIntegration {
const response = await service.performRequest(this.#postMessageEndpoint, {
...parsedParams,
link_names: 1,
channel,
});
@@ -1,10 +1,13 @@
import { z } from "zod";
import { TriggerWorkflowMessageSchema } from "../schemas/workflows";
import { WorkflowRunEventPropertiesSchema } from "../sharedSchemas";
const Catalog = {
TRIGGER_WORKFLOW: {
data: TriggerWorkflowMessageSchema,
properties: WorkflowRunEventPropertiesSchema,
properties: WorkflowRunEventPropertiesSchema.extend({
"x-ttl": z.coerce.number().optional(),
}),
},
};
@@ -5,7 +5,7 @@ import { WorkflowSendRunEventPropertiesSchema } from "../sharedSchemas";
export const commands = {
WORKFLOW_RUN_COMPLETE: {
data: z.object({
output: z.string(),
output: z.string().optional(),
}),
properties: WorkflowSendRunEventPropertiesSchema,
},
@@ -27,4 +27,12 @@ export const commands = {
}),
properties: WorkflowSendRunEventPropertiesSchema,
},
WORKFLOW_RUN_TRIGGER_TIMEOUT: {
data: z.object({
id: z.string(),
ttl: z.number(),
elapsedSeconds: z.number(),
}),
properties: WorkflowSendRunEventPropertiesSchema,
},
};
@@ -27,7 +27,11 @@ export class ZodPubSub<TPubSubSchema extends MessageCatalogSchema> {
constructor(options: ZodPubSubOptions<TPubSubSchema>) {
this.#publisher = new ZodPublisher({
client: options.client,
config: { ...options.publisherConfig, topic: options.topic },
config: {
...options.publisherConfig,
topic: options.topic,
batchingEnabled: false,
},
schema: options.schema,
});
@@ -16,6 +16,7 @@ export type PublishOptions = {
partitionKey?: string;
orderingKey?: string;
id?: string;
eventTimestamp?: number;
};
type PendingMessages<PublisherSchema extends MessageCatalogSchema> = Array<{
@@ -216,6 +217,7 @@ export class ZodPublisher<PublisherSchema extends MessageCatalogSchema> {
deliverAt: options?.deliverAt,
partitionKey: options?.partitionKey,
orderingKey: options?.orderingKey,
eventTimestamp: options?.eventTimestamp,
});
return response.toString();
@@ -14,13 +14,21 @@ import {
import { z, ZodError } from "zod";
import { ZodPubSubStatus } from "./types";
export type SubscriberMessageAttributes = {
messageId: string;
eventTimestamp?: Date;
publishedTimestamp: Date;
redeliveryCount: number;
};
export type ZodSubscriberHandlers<
TConsumerSchema extends MessageCatalogSchema
> = {
[K in keyof TConsumerSchema]: (
id: string,
data: z.infer<TConsumerSchema[K]["data"]>,
properties: z.infer<TConsumerSchema[K]["properties"]>
properties: z.infer<TConsumerSchema[K]["properties"]>,
attributes: SubscriberMessageAttributes
) => Promise<boolean>;
};
@@ -109,8 +117,32 @@ export class ZodSubscriber<SubscriberSchema extends MessageCatalogSchema> {
const properties = this.#getRawProperties(msg);
const messageId = msg.getMessageId();
const publishedTimestamp = msg.getPublishTimestamp();
const eventTimestamp = msg.getEventTimestamp();
const redeliveryCount = msg.getRedeliveryCount();
this.#logger.debug("#onMessage", {
messageId,
publishedTimestamp,
eventTimestamp,
redeliveryCount,
});
const messageAttributes = {
eventTimestamp:
eventTimestamp === 0 ? undefined : new Date(eventTimestamp),
messageId: messageId.toString(),
publishedTimestamp: new Date(publishedTimestamp),
redeliveryCount,
};
try {
const wasHandled = await this.#handleMessage(messageData, properties);
const wasHandled = await this.#handleMessage(
messageData,
properties,
messageAttributes
);
if (wasHandled) {
await consumer.acknowledge(msg);
@@ -134,7 +166,8 @@ export class ZodSubscriber<SubscriberSchema extends MessageCatalogSchema> {
async #handleMessage<K extends keyof SubscriberSchema>(
rawMessage: MessageData,
rawProperties: Record<string, string> = {}
rawProperties: Record<string, string> = {},
messageAttributes: SubscriberMessageAttributes
): Promise<boolean> {
const subscriberSchema = this.#schema;
type TypeKeys = keyof typeof subscriberSchema;
@@ -160,7 +193,12 @@ export class ZodSubscriber<SubscriberSchema extends MessageCatalogSchema> {
const handler = this.#handlers[typeName];
const returnValue = await handler(rawMessage.id, message, properties);
const returnValue = await handler(
rawMessage.id,
message,
properties,
messageAttributes
);
return returnValue;
}
@@ -11,6 +11,7 @@ export const WorkflowMetadataSchema = z.object({
name: z.string(),
trigger: TriggerMetadataSchema,
package: PackageMetadataSchema,
triggerTTL: z.number().optional(),
});
export type WorkflowMetadata = z.infer<typeof WorkflowMetadataSchema>;
@@ -1,9 +1,39 @@
import { TriggerEvent } from "@trigger.dev/sdk";
import { github } from "@trigger.dev/providers";
export function repoIssueEvent(params: {
export function commitCommentEvent(params: {
repo: string;
}): TriggerEvent<typeof github.schemas.IssueEventSchema> {
}): TriggerEvent<
typeof github.schemas.commitComments.commitCommentEventSchema
> {
return {
metadata: {
type: "WEBHOOK",
service: "github",
name: "commit_comment",
filter: {
service: ["github"],
payload: {
repository: {
full_name: [params.repo],
},
},
event: ["commit_comment"],
},
source: github.schemas.WebhookSourceSchema.parse({
subresource: "repository",
scopes: ["repo"],
repo: params.repo,
events: ["commit_comment"],
}),
},
schema: github.schemas.commitComments.commitCommentEventSchema,
};
}
export function issueEvent(params: {
repo: string;
}): TriggerEvent<typeof github.schemas.issues.issuesEventSchema> {
return {
metadata: {
type: "WEBHOOK",
@@ -25,34 +55,151 @@ export function repoIssueEvent(params: {
events: ["issues"],
}),
},
schema: github.schemas.IssueEventSchema,
schema: github.schemas.issues.issuesEventSchema,
};
}
export function orgIssueEvent(params: {
org: string;
}): TriggerEvent<typeof github.schemas.IssueEventSchema> {
export function issueCommentEvent(params: {
repo: string;
}): TriggerEvent<typeof github.schemas.issuesComments.issueCommentEventSchema> {
return {
metadata: {
type: "WEBHOOK",
service: "github",
name: "issues",
name: "issue_comment",
filter: {
service: ["github"],
payload: {
organizaton: {
name: [params.org],
repository: {
full_name: [params.repo],
},
},
event: ["issues"],
event: ["issue_comment"],
},
source: github.schemas.WebhookSourceSchema.parse({
subresource: "organization",
subresource: "repository",
scopes: ["repo"],
org: params.org,
events: ["issues"],
repo: params.repo,
events: ["issue_comment"],
}),
},
schema: github.schemas.IssueEventSchema,
schema: github.schemas.issuesComments.issueCommentEventSchema,
};
}
export function pullRequestEvent(params: {
repo: string;
}): TriggerEvent<typeof github.schemas.pullRequest.pullRequestEventSchema> {
return {
metadata: {
type: "WEBHOOK",
service: "github",
name: "pull_request",
filter: {
service: ["github"],
payload: {
repository: {
full_name: [params.repo],
},
},
event: ["pull_request"],
},
source: github.schemas.WebhookSourceSchema.parse({
subresource: "repository",
scopes: ["repo"],
repo: params.repo,
events: ["pull_request"],
}),
},
schema: github.schemas.pullRequest.pullRequestEventSchema,
};
}
export function pullRequestCommentEvent(params: {
repo: string;
}): TriggerEvent<
typeof github.schemas.pullRequestComments.pullRequestReviewCommentEventSchema
> {
return {
metadata: {
type: "WEBHOOK",
service: "github",
name: "pull_request_review_comment",
filter: {
service: ["github"],
payload: {
repository: {
full_name: [params.repo],
},
},
event: ["pull_request_review_comment"],
},
source: github.schemas.WebhookSourceSchema.parse({
subresource: "repository",
scopes: ["repo"],
repo: params.repo,
events: ["pull_request_review_comment"],
}),
},
schema:
github.schemas.pullRequestComments.pullRequestReviewCommentEventSchema,
};
}
export function pullRequestReviewEvent(params: {
repo: string;
}): TriggerEvent<
typeof github.schemas.pullRequestReviews.pullRequestReviewEventSchema
> {
return {
metadata: {
type: "WEBHOOK",
service: "github",
name: "pull_request_review",
filter: {
service: ["github"],
payload: {
repository: {
full_name: [params.repo],
},
},
event: ["pull_request_review"],
},
source: github.schemas.WebhookSourceSchema.parse({
subresource: "repository",
scopes: ["repo"],
repo: params.repo,
events: ["pull_request_review"],
}),
},
schema: github.schemas.pullRequestReviews.pullRequestReviewEventSchema,
};
}
export function pushEvent(params: {
repo: string;
}): TriggerEvent<typeof github.schemas.push.pushEventSchema> {
return {
metadata: {
type: "WEBHOOK",
service: "github",
name: "push",
filter: {
service: ["github"],
payload: {
repository: {
full_name: [params.repo],
},
},
event: ["push"],
},
source: github.schemas.WebhookSourceSchema.parse({
subresource: "repository",
scopes: ["repo"],
repo: params.repo,
events: ["push"],
}),
},
schema: github.schemas.push.pushEventSchema,
};
}
@@ -1,240 +0,0 @@
import { z } from "zod";
export const WebhookRepoSourceSchema = z.object({
subresource: z.literal("repository"),
scopes: z.array(z.string()),
repo: z.string(),
events: z.array(z.string()),
});
export const WebhookOrganizationSourceSchema = z.object({
subresource: z.literal("organization"),
scopes: z.array(z.string()),
org: z.string(),
events: z.array(z.string()),
});
export const WebhookSourceSchema = z.union([
WebhookRepoSourceSchema,
WebhookOrganizationSourceSchema,
]);
const ReactionsSchema = z.object({
"+1": z.number().int(),
"-1": z.number().int(),
url: z.string().url(),
eyes: z.number().int(),
heart: z.number().int(),
laugh: z.number().int(),
hooray: z.number().int(),
rocket: z.number().int(),
confused: z.number().int(),
total_count: z.number().int(),
});
const UserSchema = z.object({
id: z.number().int(),
url: z.string().url(),
type: z.string(),
login: z.string(),
node_id: z.string(),
html_url: z.string().url(),
gists_url: z.string().url(),
repos_url: z.string().url(),
avatar_url: z.string().url(),
events_url: z.string().url(),
site_admin: z.boolean(),
gravatar_id: z.string(),
starred_url: z.string().url(),
followers_url: z.string().url(),
following_url: z.string().url(),
organizations_url: z.string().url(),
subscriptions_url: z.string().url(),
received_events_url: z.string().url(),
});
const RepositorySchema = z.object({
id: z.number().int(),
url: z.string().url(),
fork: z.boolean(),
name: z.string(),
size: z.number().int(),
forks: z.number().int(),
owner: UserSchema,
topics: z.array(z.string()),
git_url: z.string().url(),
license: z.object({
key: z.string(),
url: z.string().url(),
name: z.string(),
node_id: z.string(),
spdx_id: z.string(),
}),
node_id: z.string(),
private: z.boolean(),
ssh_url: z.string(),
svn_url: z.string().url(),
archived: z.boolean(),
disabled: z.boolean(),
has_wiki: z.boolean(),
homepage: z.string().url(),
html_url: z.string().url(),
keys_url: z.string().url(),
language: z.string(),
tags_url: z.string().url(),
watchers: z.number().int(),
blobs_url: z.string().url(),
clone_url: z.string().url(),
forks_url: z.string().url(),
full_name: z.string(),
has_pages: z.boolean(),
hooks_url: z.string().url(),
pulls_url: z.string().url(),
pushed_at: z.string(),
teams_url: z.string().url(),
trees_url: z.string().url(),
created_at: z.string(),
events_url: z.string().url(),
has_issues: z.boolean(),
issues_url: z.string().url(),
labels_url: z.string().url(),
merges_url: z.string().url(),
mirror_url: z.null(),
updated_at: z.string(),
visibility: z.string(),
archive_url: z.string().url(),
commits_url: z.string().url(),
compare_url: z.string().url(),
description: z.string(),
forks_count: z.number().int(),
is_template: z.boolean(),
open_issues: z.number().int(),
branches_url: z.string().url(),
comments_url: z.string().url(),
contents_url: z.string().url(),
git_refs_url: z.string().url(),
git_tags_url: z.string().url(),
has_projects: z.boolean(),
releases_url: z.string().url(),
statuses_url: z.string().url(),
allow_forking: z.boolean(),
assignees_url: z.string().url(),
downloads_url: z.string().url(),
has_downloads: z.boolean(),
languages_url: z.string().url(),
default_branch: z.string(),
milestones_url: z.string().url(),
stargazers_url: z.string().url(),
watchers_count: z.number().int(),
deployments_url: z.string().url(),
git_commits_url: z.string().url(),
has_discussions: z.boolean(),
subscribers_url: z.string().url(),
contributors_url: z.string().url(),
issue_events_url: z.string().url(),
stargazers_count: z.number().int(),
subscription_url: z.string().url(),
collaborators_url: z.string().url(),
issue_comment_url: z.string().url(),
notifications_url: z.string().url(),
open_issues_count: z.number().int(),
web_commit_signoff_required: z.boolean(),
});
const IssueSchema = z.object({
id: z.number().int(),
url: z.string().url(),
body: z.string(),
user: UserSchema,
state: z.string(),
title: z.string(),
labels: z.array(z.any()),
locked: z.boolean(),
number: z.number().int(),
node_id: z.string(),
assignee: UserSchema.optional(),
comments: z.number().int(),
html_url: z.string().url(),
assignees: z.array(UserSchema),
closed_at: z.null(),
milestone: z.null(),
reactions: ReactionsSchema,
created_at: z.string(),
events_url: z.string().url(),
labels_url: z.string().url(),
updated_at: z.string(),
comments_url: z.string().url(),
state_reason: z.null(),
timeline_url: z.string().url(),
repository_url: z.string().url(),
active_lock_reason: z.null(),
author_association: z.string(),
performed_via_github_app: z.null(),
});
const LabelSchema = z.object({
id: z.number().int(),
url: z.string().url(),
name: z.string(),
color: z.string(),
default: z.boolean(),
node_id: z.string(),
description: z.string(),
});
const OrganizationSchema = z.object({
id: z.number().int(),
url: z.string().url(),
login: z.string(),
node_id: z.string(),
hooks_url: z.string().url(),
repos_url: z.string().url(),
avatar_url: z.string().url(),
events_url: z.string().url(),
issues_url: z.string().url(),
description: z.string(),
members_url: z.string().url(),
public_members_url: z.string().url(),
});
const SharedIssueEventSchema = z.object({
issue: IssueSchema,
sender: UserSchema,
repository: RepositorySchema,
organization: OrganizationSchema,
});
const LabeledIssueEventSchema = z
.object({
action: z.literal("labeled"),
label: LabelSchema,
})
.merge(SharedIssueEventSchema);
const UnlabeledIssueEventSchema = z
.object({
action: z.literal("unlabeled"),
label: LabelSchema,
})
.merge(SharedIssueEventSchema);
const AssignedIssueEventSchema = z
.object({
action: z.literal("assigned"),
assignee: UserSchema,
})
.merge(SharedIssueEventSchema);
const UnassignedIssueEventSchema = z
.object({
action: z.literal("unassigned"),
assignee: UserSchema,
})
.merge(SharedIssueEventSchema);
export const IssueEventSchema = z.discriminatedUnion("action", [
LabeledIssueEventSchema,
UnlabeledIssueEventSchema,
AssignedIssueEventSchema,
UnassignedIssueEventSchema,
]);
@@ -0,0 +1,31 @@
import { z } from "zod";
import {
userSchema,
authorAssociationSchema,
repositorySchema,
installationLiteSchema,
organizationSchema,
} from "./shared";
export const commitCommentEventSchema = z.object({
action: z.literal("created"),
comment: z.object({
url: z.string(),
html_url: z.string(),
id: z.number(),
node_id: z.string(),
user: userSchema,
position: z.number().nullable(),
line: z.number().nullable(),
path: z.string().nullable(),
commit_id: z.string(),
created_at: z.string(),
updated_at: z.string(),
author_association: authorAssociationSchema,
body: z.string(),
}),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
@@ -0,0 +1,37 @@
import { z } from "zod";
import * as commitComments from "./commitComments";
import * as issuesComments from "./issueComments";
import * as issues from "./issues";
import * as pullRequestComments from "./pullRequestComments";
import * as pullRequestReviews from "./pullRequestReviews";
import * as pullRequest from "./pullRequests";
import * as push from "./pushes";
export const WebhookRepoSourceSchema = z.object({
subresource: z.literal("repository"),
scopes: z.array(z.string()),
repo: z.string(),
events: z.array(z.string()),
});
export const WebhookOrganizationSourceSchema = z.object({
subresource: z.literal("organization"),
scopes: z.array(z.string()),
org: z.string(),
events: z.array(z.string()),
});
export const WebhookSourceSchema = z.union([
WebhookRepoSourceSchema,
WebhookOrganizationSourceSchema,
]);
export {
commitComments,
issues,
issuesComments,
pullRequest,
pullRequestComments,
pullRequestReviews,
push,
};
@@ -0,0 +1,91 @@
import { z } from "zod";
import { issueSchema } from "./issues";
import {
appSchema,
authorAssociationSchema,
installationLiteSchema,
labelSchema,
organizationSchema,
reactionsSchema,
repositorySchema,
userSchema,
} from "./shared";
export const issueCommentSchema = z.object({
url: z.string(),
html_url: z.string(),
issue_url: z.string(),
id: z.number(),
node_id: z.string(),
user: userSchema,
created_at: z.string(),
updated_at: z.string(),
author_association: authorAssociationSchema,
body: z.string(),
reactions: reactionsSchema,
performed_via_github_app: appSchema.nullable(),
});
export const issueCommentCreatedEventSchema = z.object({
action: z.literal("created"),
issue: issueSchema.and(
z.object({
assignee: userSchema.nullable(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
labels: z.array(labelSchema),
})
),
comment: issueCommentSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issueCommentDeletedEventSchema = z.object({
action: z.literal("deleted"),
issue: issueSchema.and(
z.object({
assignee: userSchema.nullable(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
labels: z.array(labelSchema),
})
),
comment: issueCommentSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issueCommentEditedEventSchema = z.object({
action: z.literal("edited"),
changes: z.object({
body: z
.object({
from: z.string(),
})
.optional(),
}),
issue: issueSchema.and(
z.object({
assignee: userSchema.nullable(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
labels: z.array(labelSchema),
})
),
comment: issueCommentSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issueCommentEventSchema = z.discriminatedUnion("action", [
issueCommentCreatedEventSchema,
issueCommentDeletedEventSchema,
issueCommentEditedEventSchema,
]);
@@ -0,0 +1,311 @@
import { z } from "zod";
import {
appSchema,
authorAssociationSchema,
installationLiteSchema,
labelSchema,
milestoneSchema,
organizationSchema,
reactionsSchema,
repositorySchema,
userSchema,
} from "./shared";
export const issueSchema = z.object({
url: z.string(),
repository_url: z.string(),
labels_url: z.string(),
comments_url: z.string(),
events_url: z.string(),
html_url: z.string(),
id: z.number(),
node_id: z.string(),
number: z.number(),
title: z.string(),
user: userSchema,
labels: z.array(labelSchema).optional(),
state: z.union([z.literal("open"), z.literal("closed")]).optional(),
locked: z.boolean().optional(),
assignee: userSchema.optional().nullable(),
assignees: z.array(userSchema),
milestone: milestoneSchema.nullable(),
comments: z.number(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
author_association: authorAssociationSchema,
active_lock_reason: z
.union([
z.literal("resolved"),
z.literal("off-topic"),
z.literal("too heated"),
z.literal("spam"),
])
.nullable(),
draft: z.boolean().optional(),
performed_via_github_app: appSchema.optional().nullable(),
pull_request: z
.object({
url: z.string().optional(),
html_url: z.string().optional(),
diff_url: z.string().optional(),
patch_url: z.string().optional(),
merged_at: z.string().optional().nullable(),
})
.optional(),
body: z.string().nullable(),
reactions: reactionsSchema,
timeline_url: z.string().optional(),
state_reason: z.string().optional().nullable(),
});
export const issuesAssignedEventSchema = z.object({
action: z.literal("assigned"),
issue: issueSchema,
assignee: userSchema.optional().nullable(),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesClosedEventSchema = z.object({
action: z.literal("closed"),
issue: issueSchema.and(
z.object({
state: z.literal("closed"),
closed_at: z.string(),
})
),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesDeletedEventSchema = z.object({
action: z.literal("deleted"),
issue: issueSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesDemilestonedEventSchema = z.object({
action: z.literal("demilestoned"),
issue: issueSchema.and(
z.object({
milestone: z.null(),
})
),
milestone: milestoneSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesEditedEventSchema = z.object({
action: z.literal("edited"),
issue: issueSchema,
label: labelSchema.optional(),
changes: z.object({
body: z
.object({
from: z.string(),
})
.optional(),
title: z
.object({
from: z.string(),
})
.optional(),
}),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesLabeledEventSchema = z.object({
action: z.literal("labeled"),
issue: issueSchema,
label: labelSchema.optional(),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesLockedEventSchema = z.object({
action: z.literal("locked"),
issue: issueSchema.and(
z.object({
locked: z.literal(true),
active_lock_reason: z
.union([
z.literal("resolved"),
z.literal("off-topic"),
z.literal("too heated"),
z.literal("spam"),
])
.nullable(),
})
),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesMilestonedEventSchema = z.object({
action: z.literal("milestoned"),
issue: issueSchema.and(
z.object({
milestone: milestoneSchema,
})
),
milestone: milestoneSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesOpenedEventSchema = z.object({
action: z.literal("opened"),
changes: z
.object({
old_issue: issueSchema,
old_repository: repositorySchema,
})
.optional(),
issue: issueSchema.and(
z.object({
state: z.literal("open"),
closed_at: z.null(),
})
),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesPinnedEventSchema = z.object({
action: z.literal("pinned"),
issue: issueSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesReopenedEventSchema = z.object({
action: z.literal("reopened"),
issue: issueSchema.and(
z.object({
state: z.literal("open"),
})
),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesTransferredEventSchema = z.object({
action: z.literal("transferred"),
changes: z.object({
new_issue: issueSchema,
new_repository: repositorySchema,
}),
issue: issueSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesUnassignedEventSchema = z.object({
action: z.literal("unassigned"),
issue: issueSchema,
assignee: userSchema.optional().nullable(),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesUnlabeledEventSchema = z.object({
action: z.literal("unlabeled"),
issue: issueSchema,
label: labelSchema.optional(),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesUnlockedEventSchema = z.object({
action: z.literal("unlocked"),
issue: issueSchema.and(
z.object({
locked: z.literal(false),
active_lock_reason: z.null(),
})
),
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesUnpinnedEventSchema = z.object({
action: z.literal("unpinned"),
issue: issueSchema,
repository: repositorySchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
export const issuesEventSchema: z.ZodDiscriminatedUnion<
"action",
[
typeof issuesAssignedEventSchema,
typeof issuesClosedEventSchema,
typeof issuesDeletedEventSchema,
typeof issuesDemilestonedEventSchema,
typeof issuesEditedEventSchema,
typeof issuesLabeledEventSchema,
typeof issuesLockedEventSchema,
typeof issuesMilestonedEventSchema,
typeof issuesOpenedEventSchema,
typeof issuesPinnedEventSchema,
typeof issuesReopenedEventSchema,
typeof issuesTransferredEventSchema,
typeof issuesUnassignedEventSchema,
typeof issuesUnlabeledEventSchema,
typeof issuesUnlockedEventSchema,
typeof issuesUnpinnedEventSchema
]
> = z.discriminatedUnion("action", [
issuesAssignedEventSchema,
issuesClosedEventSchema,
issuesDeletedEventSchema,
issuesDemilestonedEventSchema,
issuesEditedEventSchema,
issuesLabeledEventSchema,
issuesLockedEventSchema,
issuesMilestonedEventSchema,
issuesOpenedEventSchema,
issuesPinnedEventSchema,
issuesReopenedEventSchema,
issuesTransferredEventSchema,
issuesUnassignedEventSchema,
issuesUnlabeledEventSchema,
issuesUnlockedEventSchema,
issuesUnpinnedEventSchema,
]);
@@ -0,0 +1,285 @@
import { z } from "zod";
import {
userSchema,
teamSchema,
labelSchema,
milestoneSchema,
repositorySchema,
linkSchema,
authorAssociationSchema,
installationLiteSchema,
organizationSchema,
reactionsSchema,
pullRequestAutoMergeSchema,
} from "./shared";
export const pullRequestReviewCommentSchema = z.object({
url: z.string(),
pull_request_review_id: z.number(),
id: z.number(),
node_id: z.string(),
diff_hunk: z.string(),
path: z.string(),
position: z.number().nullable(),
original_position: z.number(),
commit_id: z.string(),
original_commit_id: z.string(),
user: userSchema,
body: z.string(),
created_at: z.string(),
updated_at: z.string(),
html_url: z.string(),
pull_request_url: z.string(),
author_association: authorAssociationSchema,
_links: z.object({
self: linkSchema,
html: linkSchema,
pull_request: linkSchema,
}),
reactions: reactionsSchema,
start_line: z.number().nullable(),
original_start_line: z.number().nullable(),
start_side: z.union([z.literal("LEFT"), z.literal("RIGHT")]).nullable(),
line: z.number().nullable(),
original_line: z.number(),
side: z.union([z.literal("LEFT"), z.literal("RIGHT")]),
in_reply_to_id: z.number().optional(),
});
export const pullRequestReviewCommentCreatedEventSchema = z.object({
action: z.literal("created"),
comment: pullRequestReviewCommentSchema,
pull_request: z.object({
url: z.string(),
id: z.number(),
node_id: z.string(),
html_url: z.string(),
diff_url: z.string(),
patch_url: z.string(),
issue_url: z.string(),
number: z.number(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
title: z.string(),
user: userSchema,
body: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
merged_at: z.string().nullable(),
merge_commit_sha: z.string().nullable(),
assignee: userSchema.nullable(),
assignees: z.array(userSchema),
requested_reviewers: z.array(z.union([userSchema, teamSchema])),
requested_teams: z.array(teamSchema),
labels: z.array(labelSchema),
milestone: milestoneSchema.nullable(),
draft: z.boolean().optional(),
commits_url: z.string(),
review_comments_url: z.string(),
review_comment_url: z.string(),
comments_url: z.string(),
statuses_url: z.string(),
head: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
base: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
_links: z.object({
self: linkSchema,
html: linkSchema,
issue: linkSchema,
comments: linkSchema,
review_comments: linkSchema,
review_comment: linkSchema,
commits: linkSchema,
statuses: linkSchema,
}),
auto_merge: pullRequestAutoMergeSchema.optional().nullable(),
author_association: authorAssociationSchema,
active_lock_reason: z
.union([
z.literal("resolved"),
z.literal("off-topic"),
z.literal("too heated"),
z.literal("spam"),
])
.nullable(),
}),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewCommentDeletedEventSchema = z.object({
action: z.literal("deleted"),
comment: pullRequestReviewCommentSchema,
pull_request: z.object({
url: z.string(),
id: z.number(),
node_id: z.string(),
html_url: z.string(),
diff_url: z.string(),
patch_url: z.string(),
issue_url: z.string(),
number: z.number(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
title: z.string(),
user: userSchema,
body: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
merged_at: z.string().nullable(),
merge_commit_sha: z.string().nullable(),
assignee: userSchema.nullable(),
assignees: z.array(userSchema),
requested_reviewers: z.array(z.union([userSchema, teamSchema])),
requested_teams: z.array(teamSchema),
labels: z.array(labelSchema),
milestone: milestoneSchema.nullable(),
draft: z.boolean().optional(),
commits_url: z.string(),
review_comments_url: z.string(),
review_comment_url: z.string(),
comments_url: z.string(),
statuses_url: z.string(),
head: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
base: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
_links: z.object({
self: linkSchema,
html: linkSchema,
issue: linkSchema,
comments: linkSchema,
review_comments: linkSchema,
review_comment: linkSchema,
commits: linkSchema,
statuses: linkSchema,
}),
auto_merge: pullRequestAutoMergeSchema.optional().nullable(),
author_association: authorAssociationSchema,
active_lock_reason: z
.union([
z.literal("resolved"),
z.literal("off-topic"),
z.literal("too heated"),
z.literal("spam"),
])
.nullable(),
}),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewCommentEditedEventSchema = z.object({
action: z.literal("edited"),
changes: z.object({
body: z
.object({
from: z.string(),
})
.optional(),
}),
comment: pullRequestReviewCommentSchema,
pull_request: z.object({
url: z.string(),
id: z.number(),
node_id: z.string(),
html_url: z.string(),
diff_url: z.string(),
patch_url: z.string(),
issue_url: z.string(),
number: z.number(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
title: z.string(),
user: userSchema,
body: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
merged_at: z.string().nullable(),
merge_commit_sha: z.string().nullable(),
assignee: userSchema.nullable(),
assignees: z.array(userSchema),
requested_reviewers: z.array(z.union([userSchema, teamSchema])),
requested_teams: z.array(teamSchema),
labels: z.array(labelSchema),
milestone: milestoneSchema.nullable(),
draft: z.boolean().optional(),
commits_url: z.string(),
review_comments_url: z.string(),
review_comment_url: z.string(),
comments_url: z.string(),
statuses_url: z.string(),
head: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
base: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
_links: z.object({
self: linkSchema,
html: linkSchema,
issue: linkSchema,
comments: linkSchema,
review_comments: linkSchema,
review_comment: linkSchema,
commits: linkSchema,
statuses: linkSchema,
}),
auto_merge: pullRequestAutoMergeSchema.optional().nullable(),
author_association: authorAssociationSchema,
active_lock_reason: z
.union([
z.literal("resolved"),
z.literal("off-topic"),
z.literal("too heated"),
z.literal("spam"),
])
.nullable(),
}),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewCommentEventSchema = z.union([
pullRequestReviewCommentCreatedEventSchema,
pullRequestReviewCommentDeletedEventSchema,
pullRequestReviewCommentEditedEventSchema,
]);
@@ -0,0 +1,79 @@
import { z } from "zod";
import {
repositorySchema,
installationLiteSchema,
organizationSchema,
userSchema,
authorAssociationSchema,
linkSchema,
simplePullRequestSchema,
} from "./shared";
export const pullRequestReviewSchema = z.object({
id: z.number(),
node_id: z.string(),
user: userSchema,
body: z.string().nullable(),
commit_id: z.string(),
submitted_at: z.string().nullable(),
state: z.union([
z.literal("commented"),
z.literal("changes_requested"),
z.literal("approved"),
z.literal("dismissed"),
]),
html_url: z.string(),
pull_request_url: z.string(),
author_association: authorAssociationSchema,
_links: z.object({
html: linkSchema,
pull_request: linkSchema,
}),
});
export const pullRequestReviewDismissedEventSchema = z.object({
action: z.literal("dismissed"),
review: pullRequestReviewSchema.and(
z.object({
state: z.literal("dismissed"),
})
),
pull_request: simplePullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewEditedEventSchema = z.object({
action: z.literal("edited"),
changes: z.object({
body: z
.object({
from: z.string(),
})
.optional(),
}),
review: pullRequestReviewSchema,
pull_request: simplePullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewSubmittedEventSchema = z.object({
action: z.literal("submitted"),
review: pullRequestReviewSchema,
pull_request: simplePullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewEventSchema = z.union([
pullRequestReviewDismissedEventSchema,
pullRequestReviewEditedEventSchema,
pullRequestReviewSubmittedEventSchema,
]);
@@ -0,0 +1,401 @@
import { z } from "zod";
import {
authorAssociationSchema,
installationLiteSchema,
labelSchema,
linkSchema,
milestoneSchema,
organizationSchema,
pullRequestAutoMergeSchema,
repositorySchema,
teamSchema,
userSchema,
} from "./shared";
export const pullRequestSchema = z.object({
url: z.string(),
id: z.number(),
node_id: z.string(),
html_url: z.string(),
diff_url: z.string(),
patch_url: z.string(),
issue_url: z.string(),
number: z.number(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
title: z.string(),
user: userSchema,
body: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
merged_at: z.string().nullable(),
merge_commit_sha: z.string().nullable(),
assignee: userSchema.nullable(),
assignees: z.array(userSchema),
requested_reviewers: z.array(z.union([userSchema, teamSchema])),
requested_teams: z.array(teamSchema),
labels: z.array(labelSchema),
milestone: milestoneSchema.nullable(),
commits_url: z.string(),
review_comments_url: z.string(),
review_comment_url: z.string(),
comments_url: z.string(),
statuses_url: z.string(),
head: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
base: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
_links: z.object({
self: linkSchema,
html: linkSchema,
issue: linkSchema,
comments: linkSchema,
review_comments: linkSchema,
review_comment: linkSchema,
commits: linkSchema,
statuses: linkSchema,
}),
author_association: authorAssociationSchema,
auto_merge: pullRequestAutoMergeSchema.nullable(),
active_lock_reason: z
.union([
z.literal("resolved"),
z.literal("off-topic"),
z.literal("too heated"),
z.literal("spam"),
])
.nullable(),
draft: z.boolean(),
merged: z.boolean().nullable(),
mergeable: z.boolean().nullable(),
rebaseable: z.boolean().nullable(),
mergeable_state: z.string(),
merged_by: userSchema.nullable(),
comments: z.number(),
review_comments: z.number(),
maintainer_can_modify: z.boolean(),
commits: z.number(),
additions: z.number(),
deletions: z.number(),
changed_files: z.number(),
});
export const pullRequestAssignedEventSchema = z.object({
action: z.literal("assigned"),
number: z.number(),
pull_request: pullRequestSchema,
assignee: userSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestAutoMergeDisabledEventSchema = z.object({
action: z.literal("auto_merge_disabled"),
number: z.number(),
pull_request: pullRequestSchema,
reason: z.string(),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestAutoMergeEnabledEventSchema = z.object({
action: z.literal("auto_merge_enabled"),
number: z.number(),
pull_request: pullRequestSchema,
reason: z.string(),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestClosedEventSchema = z.object({
action: z.literal("closed"),
number: z.number(),
pull_request: pullRequestSchema.and(
z.object({
state: z.literal("closed"),
closed_at: z.string(),
merged: z.boolean(),
})
),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestConvertedToDraftEventSchema = z.object({
action: z.literal("converted_to_draft"),
number: z.number(),
pull_request: pullRequestSchema.and(
z.object({
closed_at: z.null(),
merged_at: z.null(),
draft: z.literal(true),
merged: z.literal(false),
merged_by: z.null(),
})
),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestDequeuedEventSchema = z.object({
action: z.literal("dequeued"),
number: z.number(),
reason: z.string(),
pull_request: pullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestEditedEventSchema = z.object({
action: z.literal("edited"),
number: z.number(),
changes: z.object({
body: z
.object({
from: z.string(),
})
.optional(),
title: z
.object({
from: z.string(),
})
.optional(),
base: z
.object({
ref: z.object({
from: z.string(),
}),
sha: z.object({
from: z.string(),
}),
})
.optional(),
}),
pull_request: pullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestLabeledEventSchema = z.object({
action: z.literal("labeled"),
number: z.number(),
pull_request: pullRequestSchema,
label: labelSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestLockedEventSchema = z.object({
action: z.literal("locked"),
number: z.number(),
pull_request: pullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestOpenedEventSchema = z.object({
action: z.literal("opened"),
number: z.number(),
pull_request: pullRequestSchema.and(
z.object({
state: z.literal("open"),
closed_at: z.null(),
merged_at: z.null(),
active_lock_reason: z.null(),
merged_by: z.null(),
})
),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestQueuedEventSchema = z.object({
action: z.literal("queued"),
number: z.number(),
pull_request: pullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReadyForReviewEventSchema = z.object({
action: z.literal("ready_for_review"),
number: z.number(),
pull_request: pullRequestSchema.and(
z.object({
state: z.literal("open"),
closed_at: z.null(),
merged_at: z.null(),
draft: z.literal(false),
merged: z.boolean(),
merged_by: z.null(),
})
),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReopenedEventSchema = z.object({
action: z.literal("reopened"),
number: z.number(),
pull_request: pullRequestSchema.and(
z.object({
state: z.literal("open"),
closed_at: z.null(),
merged_at: z.null(),
merged: z.boolean(),
merged_by: z.null(),
})
),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestSynchronizeEventSchema = z.object({
action: z.literal("synchronize"),
number: z.number(),
before: z.string(),
after: z.string(),
pull_request: pullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestUnassignedEventSchema = z.object({
action: z.literal("unassigned"),
number: z.number(),
pull_request: pullRequestSchema,
assignee: userSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestUnlabeledEventSchema = z.object({
action: z.literal("unlabeled"),
number: z.number(),
pull_request: pullRequestSchema,
label: labelSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestUnlockedEventSchema = z.object({
action: z.literal("unlocked"),
number: z.number(),
pull_request: pullRequestSchema,
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewRequestRemovedEventSchema = z.object({
action: z.literal("review_request_removed"),
number: z.number(),
pull_request: pullRequestSchema,
requested_reviewer: userSchema.optional(),
requested_team: teamSchema.optional(),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestReviewRequestedEventSchema = z.object({
action: z.literal("review_requested"),
number: z.number(),
pull_request: pullRequestSchema,
requested_reviewer: userSchema.optional(),
requested_team: teamSchema.optional(),
repository: repositorySchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
sender: userSchema,
});
export const pullRequestEventSchema: z.ZodDiscriminatedUnion<
"action",
[
typeof pullRequestAssignedEventSchema,
typeof pullRequestAutoMergeDisabledEventSchema,
typeof pullRequestAutoMergeEnabledEventSchema,
typeof pullRequestClosedEventSchema,
typeof pullRequestConvertedToDraftEventSchema,
typeof pullRequestDequeuedEventSchema,
typeof pullRequestEditedEventSchema,
typeof pullRequestLabeledEventSchema,
typeof pullRequestLockedEventSchema,
typeof pullRequestOpenedEventSchema,
typeof pullRequestQueuedEventSchema,
typeof pullRequestReadyForReviewEventSchema,
typeof pullRequestReopenedEventSchema,
typeof pullRequestReviewRequestRemovedEventSchema,
typeof pullRequestReviewRequestedEventSchema,
typeof pullRequestSynchronizeEventSchema,
typeof pullRequestUnassignedEventSchema,
typeof pullRequestUnlabeledEventSchema,
typeof pullRequestUnlockedEventSchema
]
> = z.discriminatedUnion("action", [
pullRequestAssignedEventSchema,
pullRequestAutoMergeDisabledEventSchema,
pullRequestAutoMergeEnabledEventSchema,
pullRequestClosedEventSchema,
pullRequestConvertedToDraftEventSchema,
pullRequestDequeuedEventSchema,
pullRequestEditedEventSchema,
pullRequestLabeledEventSchema,
pullRequestLockedEventSchema,
pullRequestOpenedEventSchema,
pullRequestQueuedEventSchema,
pullRequestReadyForReviewEventSchema,
pullRequestReopenedEventSchema,
pullRequestReviewRequestRemovedEventSchema,
pullRequestReviewRequestedEventSchema,
pullRequestSynchronizeEventSchema,
pullRequestUnassignedEventSchema,
pullRequestUnlabeledEventSchema,
pullRequestUnlockedEventSchema,
]);
@@ -0,0 +1,27 @@
import { z } from "zod";
import {
repositorySchema,
userSchema,
installationLiteSchema,
organizationSchema,
commitSchema,
committerSchema,
} from "./shared";
export const pushEventSchema = z.object({
ref: z.string(),
before: z.string(),
after: z.string(),
created: z.boolean(),
deleted: z.boolean(),
forced: z.boolean(),
base_ref: z.string().nullable(),
compare: z.string(),
commits: z.array(commitSchema),
head_commit: commitSchema.nullable(),
repository: repositorySchema,
pusher: committerSchema,
sender: userSchema,
installation: installationLiteSchema.optional(),
organization: organizationSchema.optional(),
});
@@ -0,0 +1,507 @@
import { z } from "zod";
export const userSchema = z.object({
login: z.string(),
id: z.number(),
node_id: z.string(),
name: z.string().optional(),
email: z.string().optional().nullable(),
avatar_url: z.string(),
gravatar_id: z.string(),
url: z.string(),
html_url: z.string(),
followers_url: z.string(),
following_url: z.string(),
gists_url: z.string(),
starred_url: z.string(),
subscriptions_url: z.string(),
organizations_url: z.string(),
repos_url: z.string(),
events_url: z.string(),
received_events_url: z.string(),
type: z.union([
z.literal("Bot"),
z.literal("User"),
z.literal("Organization"),
]),
site_admin: z.boolean(),
});
export const licenseSchema = z.object({
key: z.string(),
name: z.string(),
spdx_id: z.string(),
url: z.string().nullable(),
node_id: z.string(),
});
export const installationLiteSchema = z.object({
id: z.number(),
node_id: z.string(),
});
export const organizationSchema = z.object({
login: z.string(),
id: z.number(),
node_id: z.string(),
url: z.string(),
html_url: z.string().optional(),
repos_url: z.string(),
events_url: z.string(),
hooks_url: z.string(),
issues_url: z.string(),
members_url: z.string(),
public_members_url: z.string(),
avatar_url: z.string(),
description: z.string().nullable(),
});
export const repositorySchema = z.object({
id: z.number(),
node_id: z.string(),
name: z.string(),
full_name: z.string(),
private: z.boolean(),
owner: userSchema,
html_url: z.string(),
description: z.string().nullable(),
fork: z.boolean(),
url: z.string(),
forks_url: z.string(),
keys_url: z.string(),
collaborators_url: z.string(),
teams_url: z.string(),
hooks_url: z.string(),
issue_events_url: z.string(),
events_url: z.string(),
assignees_url: z.string(),
branches_url: z.string(),
tags_url: z.string(),
blobs_url: z.string(),
git_tags_url: z.string(),
git_refs_url: z.string(),
trees_url: z.string(),
statuses_url: z.string(),
languages_url: z.string(),
stargazers_url: z.string(),
contributors_url: z.string(),
subscribers_url: z.string(),
subscription_url: z.string(),
commits_url: z.string(),
git_commits_url: z.string(),
comments_url: z.string(),
issue_comment_url: z.string(),
contents_url: z.string(),
compare_url: z.string(),
merges_url: z.string(),
archive_url: z.string(),
downloads_url: z.string(),
issues_url: z.string(),
pulls_url: z.string(),
milestones_url: z.string(),
notifications_url: z.string(),
labels_url: z.string(),
releases_url: z.string(),
deployments_url: z.string(),
created_at: z.union([z.number(), z.string()]),
updated_at: z.string(),
pushed_at: z.union([z.number(), z.string()]).nullable(),
git_url: z.string(),
ssh_url: z.string(),
clone_url: z.string(),
svn_url: z.string(),
homepage: z.string().nullable(),
size: z.number(),
stargazers_count: z.number(),
watchers_count: z.number(),
language: z.string().nullable(),
has_issues: z.boolean(),
has_projects: z.boolean(),
has_downloads: z.boolean(),
has_wiki: z.boolean(),
has_pages: z.boolean(),
forks_count: z.number(),
mirror_url: z.string().nullable(),
archived: z.boolean(),
disabled: z.boolean().optional(),
open_issues_count: z.number(),
license: licenseSchema.nullable(),
forks: z.number(),
open_issues: z.number(),
watchers: z.number(),
stargazers: z.number().optional(),
default_branch: z.string(),
allow_squash_merge: z.boolean().optional(),
allow_merge_commit: z.boolean().optional(),
allow_rebase_merge: z.boolean().optional(),
allow_auto_merge: z.boolean().optional(),
allow_forking: z.boolean().optional(),
allow_update_branch: z.boolean().optional(),
use_squash_pr_title_as_default: z.boolean().optional(),
squash_merge_commit_message: z.string().optional(),
squash_merge_commit_title: z.string().optional(),
merge_commit_message: z.string().optional(),
merge_commit_title: z.string().optional(),
is_template: z.boolean(),
web_commit_signoff_required: z.boolean(),
topics: z.array(z.string()),
visibility: z.union([
z.literal("public"),
z.literal("private"),
z.literal("internal"),
]),
delete_branch_on_merge: z.boolean().optional(),
master_branch: z.string().optional(),
permissions: z
.object({
pull: z.boolean(),
push: z.boolean(),
admin: z.boolean(),
maintain: z.boolean().optional(),
triage: z.boolean().optional(),
})
.optional(),
public: z.boolean().optional(),
organization: z.string().optional(),
});
export const teamSchema = z.object({
name: z.string(),
id: z.number(),
node_id: z.string(),
slug: z.string(),
description: z.string().nullable(),
privacy: z.union([
z.literal("open"),
z.literal("closed"),
z.literal("secret"),
]),
url: z.string(),
html_url: z.string(),
members_url: z.string(),
repositories_url: z.string(),
permission: z.string(),
parent: z
.object({
name: z.string(),
id: z.number(),
node_id: z.string(),
slug: z.string(),
description: z.string().nullable(),
privacy: z.union([
z.literal("open"),
z.literal("closed"),
z.literal("secret"),
]),
url: z.string(),
html_url: z.string(),
members_url: z.string(),
repositories_url: z.string(),
permission: z.string(),
})
.optional()
.nullable(),
});
export const linkSchema = z.object({
href: z.string(),
});
export const appSchema = z.object({
id: z.number(),
slug: z.string().optional(),
node_id: z.string(),
owner: userSchema,
name: z.string(),
description: z.string().nullable(),
external_url: z.string(),
html_url: z.string(),
created_at: z.string(),
updated_at: z.string(),
permissions: z
.object({
actions: z.union([z.literal("read"), z.literal("write")]).optional(),
administration: z
.union([z.literal("read"), z.literal("write")])
.optional(),
blocking: z.union([z.literal("read"), z.literal("write")]).optional(),
checks: z.union([z.literal("read"), z.literal("write")]).optional(),
content_references: z
.union([z.literal("read"), z.literal("write")])
.optional(),
contents: z.union([z.literal("read"), z.literal("write")]).optional(),
deployments: z.union([z.literal("read"), z.literal("write")]).optional(),
discussions: z.union([z.literal("read"), z.literal("write")]).optional(),
emails: z.union([z.literal("read"), z.literal("write")]).optional(),
environments: z.union([z.literal("read"), z.literal("write")]).optional(),
issues: z.union([z.literal("read"), z.literal("write")]).optional(),
keys: z.union([z.literal("read"), z.literal("write")]).optional(),
members: z.union([z.literal("read"), z.literal("write")]).optional(),
merge_queues: z.union([z.literal("read"), z.literal("write")]).optional(),
metadata: z.union([z.literal("read"), z.literal("write")]).optional(),
organization_administration: z
.union([z.literal("read"), z.literal("write")])
.optional(),
organization_hooks: z
.union([z.literal("read"), z.literal("write")])
.optional(),
organization_packages: z
.union([z.literal("read"), z.literal("write")])
.optional(),
organization_plan: z
.union([z.literal("read"), z.literal("write")])
.optional(),
organization_projects: z
.union([z.literal("read"), z.literal("write")])
.optional(),
organization_secrets: z
.union([z.literal("read"), z.literal("write")])
.optional(),
organization_self_hosted_runners: z
.union([z.literal("read"), z.literal("write")])
.optional(),
organization_user_blocking: z
.union([z.literal("read"), z.literal("write")])
.optional(),
packages: z.union([z.literal("read"), z.literal("write")]).optional(),
pages: z.union([z.literal("read"), z.literal("write")]).optional(),
pull_requests: z
.union([z.literal("read"), z.literal("write")])
.optional(),
repository_hooks: z
.union([z.literal("read"), z.literal("write")])
.optional(),
repository_projects: z
.union([z.literal("read"), z.literal("write")])
.optional(),
secret_scanning_alerts: z
.union([z.literal("read"), z.literal("write")])
.optional(),
secrets: z.union([z.literal("read"), z.literal("write")]).optional(),
security_events: z
.union([z.literal("read"), z.literal("write")])
.optional(),
security_scanning_alert: z
.union([z.literal("read"), z.literal("write")])
.optional(),
single_file: z.union([z.literal("read"), z.literal("write")]).optional(),
statuses: z.union([z.literal("read"), z.literal("write")]).optional(),
team_discussions: z
.union([z.literal("read"), z.literal("write")])
.optional(),
vulnerability_alerts: z
.union([z.literal("read"), z.literal("write")])
.optional(),
workflows: z.union([z.literal("read"), z.literal("write")]).optional(),
})
.optional(),
events: z
.array(
z.union([
z.literal("branch_protection_rule"),
z.literal("check_run"),
z.literal("check_suite"),
z.literal("code_scanning_alert"),
z.literal("commit_comment"),
z.literal("content_reference"),
z.literal("create"),
z.literal("delete"),
z.literal("deployment"),
z.literal("deployment_review"),
z.literal("deployment_status"),
z.literal("deploy_key"),
z.literal("discussion"),
z.literal("discussion_comment"),
z.literal("fork"),
z.literal("gollum"),
z.literal("issues"),
z.literal("issue_comment"),
z.literal("label"),
z.literal("member"),
z.literal("membership"),
z.literal("merge_group"),
z.literal("merge_queue_entry"),
z.literal("milestone"),
z.literal("organization"),
z.literal("org_block"),
z.literal("page_build"),
z.literal("project"),
z.literal("projects_v2_item"),
z.literal("project_card"),
z.literal("project_column"),
z.literal("public"),
z.literal("pull_request"),
z.literal("pull_request_review"),
z.literal("pull_request_review_comment"),
z.literal("push"),
z.literal("registry_package"),
z.literal("release"),
z.literal("repository"),
z.literal("repository_dispatch"),
z.literal("secret_scanning_alert"),
z.literal("secret_scanning_alert_location"),
z.literal("security_and_analysis"),
z.literal("star"),
z.literal("status"),
z.literal("team"),
z.literal("team_add"),
z.literal("watch"),
z.literal("workflow_dispatch"),
z.literal("workflow_run"),
z.literal("workflow_job"),
])
)
.optional(),
});
export const reactionsSchema = z.object({
url: z.string(),
total_count: z.number(),
"+1": z.number(),
"-1": z.number(),
laugh: z.number(),
hooray: z.number(),
confused: z.number(),
heart: z.number(),
rocket: z.number(),
eyes: z.number(),
});
export const authorAssociationSchema = z.union([
z.literal("COLLABORATOR"),
z.literal("CONTRIBUTOR"),
z.literal("FIRST_TIMER"),
z.literal("FIRST_TIME_CONTRIBUTOR"),
z.literal("MANNEQUIN"),
z.literal("MEMBER"),
z.literal("NONE"),
z.literal("OWNER"),
]);
export const milestoneSchema = z.object({
url: z.string(),
html_url: z.string(),
labels_url: z.string(),
id: z.number(),
node_id: z.string(),
number: z.number(),
title: z.string(),
description: z.string().nullable(),
creator: userSchema,
open_issues: z.number(),
closed_issues: z.number(),
state: z.union([z.literal("open"), z.literal("closed")]),
created_at: z.string(),
updated_at: z.string(),
due_on: z.string().nullable(),
closed_at: z.string().nullable(),
});
export const labelSchema = z.object({
id: z.number(),
node_id: z.string(),
url: z.string(),
name: z.string(),
description: z.string().nullable(),
color: z.string(),
default: z.boolean(),
});
export const pullRequestAutoMergeSchema = z.object({
enabled_by: userSchema,
merge_method: z.union([
z.literal("merge"),
z.literal("squash"),
z.literal("rebase"),
]),
commit_title: z.string(),
commit_message: z.string(),
});
export const simplePullRequestSchema = z.object({
url: z.string(),
id: z.number(),
node_id: z.string(),
html_url: z.string(),
diff_url: z.string(),
patch_url: z.string(),
issue_url: z.string(),
number: z.number(),
state: z.union([z.literal("open"), z.literal("closed")]),
locked: z.boolean(),
title: z.string(),
user: userSchema,
body: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
merged_at: z.string().nullable(),
merge_commit_sha: z.string().nullable(),
assignee: userSchema.nullable(),
assignees: z.array(userSchema),
requested_reviewers: z.array(z.union([userSchema, teamSchema])),
requested_teams: z.array(teamSchema),
labels: z.array(labelSchema),
milestone: milestoneSchema.nullable(),
draft: z.boolean(),
commits_url: z.string(),
review_comments_url: z.string(),
review_comment_url: z.string(),
comments_url: z.string(),
statuses_url: z.string(),
head: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
base: z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: userSchema,
repo: repositorySchema,
}),
_links: z.object({
self: linkSchema,
html: linkSchema,
issue: linkSchema,
comments: linkSchema,
review_comments: linkSchema,
review_comment: linkSchema,
commits: linkSchema,
statuses: linkSchema,
}),
author_association: authorAssociationSchema,
auto_merge: pullRequestAutoMergeSchema.nullable(),
active_lock_reason: z
.union([
z.literal("resolved"),
z.literal("off-topic"),
z.literal("too heated"),
z.literal("spam"),
])
.nullable(),
});
export const committerSchema = z.object({
name: z.string(),
email: z.string().nullable(),
date: z.string().optional(),
username: z.string().optional(),
});
export const commitSchema = z.object({
id: z.string(),
tree_id: z.string(),
distinct: z.boolean(),
message: z.string(),
timestamp: z.string(),
url: z.string(),
author: committerSchema,
committer: committerSchema,
added: z.array(z.string()),
modified: z.array(z.string()),
removed: z.array(z.string()),
});
+7
View File
@@ -362,6 +362,12 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
return this.#trigger.options
.run(eventData, ctx)
.then((output) => {
this.#logger.log(
`Completed workflow '${this.#options.name}', run ${
data.id
} 🏃`
);
return serverRPC.send("COMPLETE_WORKFLOW_RUN", {
runId: data.id,
output: JSON.stringify(output),
@@ -437,6 +443,7 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
trigger: this.#trigger.on.metadata,
packageVersion: pkg.version,
packageName: pkg.name,
triggerTTL: this.#options.triggerTTL,
});
if (response?.type === "error") {
@@ -12,6 +12,13 @@ export type TriggerOptions<TSchema extends z.ZodTypeAny> = {
apiKey?: string;
endpoint?: string;
logLevel?: LogLevel;
/**
* The TTL for the trigger in seconds. If the trigger is not run within this time, it will be aborted. Defaults to 3600 seconds (1 hour).
* @type {number}
*/
triggerTTL?: number;
run: (event: z.infer<TSchema>, ctx: TriggerContext) => Promise<any>;
};
+19 -42
View File
@@ -106,6 +106,7 @@ importers:
clsx: ^1.2.1
compression: ^1.7.4
cron-parser: ^4.7.1
cronstrue: ^2.21.0
cross-env: ^7.0.3
csstype: ^3.0.10
cuid: ^2.1.8
@@ -210,6 +211,7 @@ importers:
clsx: 1.2.1
compression: 1.7.4
cron-parser: 4.7.1
cronstrue: 2.21.0
cross-env: 7.0.3
csstype: 3.1.1
cuid: 2.1.8
@@ -647,7 +649,7 @@ importers:
'@urql/core': 3.1.1_graphql@16.6.0
debug: 4.3.4
graphql: 16.6.0
urql: 3.0.3_graphql@16.6.0
urql: 3.0.3_onqnqwb3ubg5opvemcqf7c2qhy
zod: 3.20.2
devDependencies:
'@trigger.dev/providers': link:../trigger-providers
@@ -3506,7 +3508,6 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@cush/relative/1.0.0:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
@@ -4116,7 +4117,6 @@ packages:
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/set-array/1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
@@ -4125,7 +4125,6 @@ packages:
/@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true
/@jridgewell/trace-mapping/0.3.17:
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
@@ -4139,7 +4138,6 @@ packages:
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@jsdevtools/ono/7.1.3:
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
@@ -5028,7 +5026,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-darwin-x64/1.3.26:
@@ -5037,7 +5034,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm-gnueabihf/1.3.26:
@@ -5046,7 +5042,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-gnu/1.3.26:
@@ -5055,7 +5050,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-musl/1.3.26:
@@ -5064,7 +5058,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-gnu/1.3.26:
@@ -5073,7 +5066,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-musl/1.3.26:
@@ -5082,7 +5074,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-arm64-msvc/1.3.26:
@@ -5091,7 +5082,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-ia32-msvc/1.3.26:
@@ -5100,7 +5090,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-x64-msvc/1.3.26:
@@ -5109,7 +5098,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core/1.3.26:
@@ -5127,7 +5115,6 @@ packages:
'@swc/core-win32-arm64-msvc': 1.3.26
'@swc/core-win32-ia32-msvc': 1.3.26
'@swc/core-win32-x64-msvc': 1.3.26
dev: true
/@swc/helpers/0.4.14:
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
@@ -5155,7 +5142,7 @@ packages:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
mini-svg-data-uri: 1.4.4
tailwindcss: 3.1.8_postcss@8.4.21
tailwindcss: 3.1.8_aesdjsunmf4wiehhujt67my7tu
/@tailwindcss/typography/0.5.9_tailwindcss@3.1.8:
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
@@ -5166,7 +5153,7 @@ packages:
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
tailwindcss: 3.1.8_postcss@8.4.21
tailwindcss: 3.1.8_aesdjsunmf4wiehhujt67my7tu
dev: true
/@tanstack/react-table/8.7.6_biqbaboplfbrettd7655fr4n2y:
@@ -5256,19 +5243,15 @@ packages:
/@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12/1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14/1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16/1.0.3:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/acorn/4.0.6:
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
@@ -5932,7 +5915,6 @@ packages:
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn/7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
@@ -5943,7 +5925,6 @@ packages:
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/agent-base/6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
@@ -6109,7 +6090,6 @@ packages:
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/arg/5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@@ -7402,7 +7382,6 @@ packages:
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/crelt/1.0.5:
resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==}
@@ -7415,6 +7394,11 @@ packages:
luxon: 3.2.1
dev: false
/cronstrue/2.21.0:
resolution: {integrity: sha512-YxabE1ZSHA1zJZMPCTSEbc0u4cRRenjqqTgCwJT7OvkspPSvfYFITuPFtsT+VkBuavJtFv2kJXT+mKSnlUJxfg==}
hasBin: true
dev: false
/cross-env/7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
@@ -7896,7 +7880,6 @@ packages:
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff/5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
@@ -8479,7 +8462,7 @@ packages:
eslint: 8.31.0
eslint-import-resolver-node: 0.3.7
eslint-import-resolver-typescript: 2.7.1_hnftvkj7qg3s6bbigj4pr6djxy
eslint-plugin-import: 2.27.4_2es4x7ly2gmvaoztzzbbx3tgsy
eslint-plugin-import: 2.27.4_eslint@8.31.0
eslint-plugin-jsx-a11y: 6.7.1_eslint@8.31.0
eslint-plugin-react: 7.31.8_eslint@8.31.0
eslint-plugin-react-hooks: 4.6.0_eslint@8.31.0
@@ -8535,7 +8518,7 @@ packages:
dependencies:
debug: 4.3.4
eslint: 8.31.0
eslint-plugin-import: 2.27.4_2es4x7ly2gmvaoztzzbbx3tgsy
eslint-plugin-import: 2.27.4_eslint@8.31.0
glob: 7.2.3
is-glob: 4.0.3
resolve: 1.22.1
@@ -8564,7 +8547,7 @@ packages:
- supports-color
dev: true
/eslint-module-utils/2.7.4_co4ldsxivxjwelu6vt6qmibemq:
/eslint-module-utils/2.7.4_py7h5widiydke2tta7oabbx7ru:
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
engines: {node: '>=4'}
peerDependencies:
@@ -8585,11 +8568,9 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.48.1_iukboom6ndih5an6iafl45j2fe
debug: 3.2.7
eslint: 8.31.0
eslint-import-resolver-node: 0.3.7
eslint-import-resolver-typescript: 2.7.1_hnftvkj7qg3s6bbigj4pr6djxy
transitivePeerDependencies:
- supports-color
dev: true
@@ -8677,7 +8658,7 @@ packages:
- supports-color
dev: true
/eslint-plugin-import/2.27.4_2es4x7ly2gmvaoztzzbbx3tgsy:
/eslint-plugin-import/2.27.4_eslint@8.31.0:
resolution: {integrity: sha512-Z1jVt1EGKia1X9CnBCkpAOhWy8FgQ7OmJ/IblEkT82yrFU/xJaxwujaTzLWqigewwynRQ9mmHfX9MtAfhxm0sA==}
engines: {node: '>=4'}
peerDependencies:
@@ -8687,7 +8668,6 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.48.1_iukboom6ndih5an6iafl45j2fe
array-includes: 3.1.6
array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1
@@ -8695,7 +8675,7 @@ packages:
doctrine: 2.1.0
eslint: 8.31.0
eslint-import-resolver-node: 0.3.7
eslint-module-utils: 2.7.4_co4ldsxivxjwelu6vt6qmibemq
eslint-module-utils: 2.7.4_py7h5widiydke2tta7oabbx7ru
has: 1.0.3
is-core-module: 2.11.0
is-glob: 4.0.3
@@ -11577,7 +11557,6 @@ packages:
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/make-iterator/0.1.1:
resolution: {integrity: sha512-s8tbTxYqrfcXYHAPxUecPxgBnWod7yFShdSOWiV17WRM87bBH2mzr24A4tpUDv9SqebaV6JsPApwKXnisMmMBA==}
@@ -13224,7 +13203,6 @@ packages:
postcss: 8.4.21
ts-node: 10.9.1_fodzh64fuekdilycyvke2qmf2e
yaml: 1.10.2
dev: true
/postcss-load-config/3.1.4_postcss@8.4.21:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@@ -13241,6 +13219,7 @@ packages:
lilconfig: 2.0.6
postcss: 8.4.21
yaml: 1.10.2
dev: true
/postcss-nested/5.0.6_postcss@8.4.21:
resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==}
@@ -15118,7 +15097,6 @@ packages:
resolve: 1.22.1
transitivePeerDependencies:
- ts-node
dev: true
/tailwindcss/3.1.8_postcss@8.4.21:
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
@@ -15151,6 +15129,7 @@ packages:
resolve: 1.22.1
transitivePeerDependencies:
- ts-node
dev: true
/tapable/2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
@@ -15432,7 +15411,6 @@ packages:
typescript: 4.9.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-toolbelt/9.6.0:
resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==}
@@ -15934,7 +15912,7 @@ packages:
prepend-http: 2.0.0
dev: true
/urql/3.0.3_graphql@16.6.0:
/urql/3.0.3_onqnqwb3ubg5opvemcqf7c2qhy:
resolution: {integrity: sha512-aVUAMRLdc5AOk239DxgXt6ZxTl/fEmjr7oyU5OGo8uvpqu42FkeJErzd2qBzhAQ3DyusoZIbqbBLPlnKo/yy2A==}
peerDependencies:
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
@@ -15942,6 +15920,7 @@ packages:
dependencies:
'@urql/core': 3.1.1_graphql@16.6.0
graphql: 16.6.0
react: 18.2.0
wonka: 6.1.2
dev: false
@@ -16027,7 +16006,6 @@ packages:
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/v8-to-istanbul/9.0.1:
resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==}
@@ -16551,7 +16529,6 @@ packages:
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}