Compare commits

...

49 Commits

Author SHA1 Message Date
Eric Allam 63b1c2237a Changeset: release new version 2023-02-27 14:03:29 +00:00
Eric Allam c72120eab0 Removed accidental log statement 2023-02-27 14:03:15 +00:00
Eric Allam 313b7163e8 Change from npm create trigger to npx create-trigger 2023-02-27 13:49:04 +00:00
Eric Allam b0e64937a0 Changeset: new version 2023-02-27 13:04:15 +00:00
Eric Allam 3a2cf0ddb7 Fixed the missing error message when logging invalid API key and improved the error message 2023-02-27 13:03:14 +00:00
Eric Allam 1579cdbfb2 Changeset: release new version 2023-02-27 12:11:58 +00:00
James Ritchie f1e81137b7 Improved the copy for how to trigger your workflow 2023-02-27 12:10:50 +00:00
Eric Allam 45b2bea39f Changeset: version next.3 2023-02-27 11:35:08 +00:00
Eric Allam 4f47d03114 Added changeset 2023-02-27 11:35:08 +00:00
Eric Allam 618cca3304 Validate the API key (to better work with the templates and the CLI) 2023-02-27 11:35:08 +00:00
James Ritchie 5706c3ab52 Improved the API information link 2023-02-27 11:14:46 +00:00
James Ritchie e8c81cafb8 Improved some of the Template’s title copy 2023-02-27 11:14:46 +00:00
Eric Allam a0d062c977 Changeset: version next.2 2023-02-27 11:10:06 +00:00
Eric Allam 87a3bbee8e Added a more helpful message when adding an API key 2023-02-27 11:09:38 +00:00
Eric Allam a5ff50f737 Implement new API key sidebar and a couple other various fixes 2023-02-26 22:15:49 +00:00
James Ritchie 763dd8b55a Improved the environment panel in the side menu to be clearer 2023-02-26 19:10:06 +00:00
James Ritchie 9e5c687174 Improvements to the Org page api keys 2023-02-26 18:04:10 +00:00
James Ritchie dcbb0f5287 small padding fix 2023-02-26 17:54:52 +00:00
James Ritchie d64cfe5add Widened the pages displaying the Template grid 2023-02-26 17:40:21 +00:00
James Ritchie 1fa52f1c8c Added padding back to the template cards 2023-02-26 17:40:00 +00:00
James Ritchie ea28f3dd47 Added installation instructions to the npm copy button for a template 2023-02-26 17:29:00 +00:00
James Ritchie 2c563ce998 Improvements to the Container component for mobile 2023-02-26 17:28:31 +00:00
James Ritchie 7dd0102579 Fixed the public template page on mobile 2023-02-26 17:08:29 +00:00
James Ritchie 01bc7df3e2 Template Overview page sticky left column now works 2023-02-26 16:51:37 +00:00
James Ritchie 6d4f71d22c Improvements to the template grid 2023-02-26 16:46:01 +00:00
James Ritchie cc002ef707 Increased the width of the template overview side column so you can see more package info 2023-02-26 16:06:38 +00:00
James Ritchie f552af517b Removed redundant class 2023-02-26 15:58:57 +00:00
James Ritchie 8aff958bd3 Copy fix 2023-02-26 15:57:06 +00:00
James Ritchie ad189b1f4b Improved the scroll bars on modals 2023-02-26 15:54:48 +00:00
James Ritchie e54ba7cbed Improved the Create workflow page and moved some info into a modal 2023-02-26 15:39:10 +00:00
James Ritchie 1d056b5328 Updated the org page styles 2023-02-26 12:26:31 +00:00
Eric Allam cf7c413c87 Combine the workflow onboarding into one component and obfuscate the api key 2023-02-24 22:16:53 +00:00
Eric Allam ac87a634a3 Add in the apiKeys in the orgs list page, obfuscated, and obfuscate them in the SideMenu as well 2023-02-24 22:09:30 +00:00
James Ritchie c9e411be9b Simplified the info on the template cards 2023-02-24 18:51:38 +00:00
James Ritchie ee4e181fc3 Added ability to copy env vars from the Org page 2023-02-24 18:51:38 +00:00
James Ritchie f51f8f3ece Added more styles to the copy text panel 2023-02-24 18:51:38 +00:00
D-K-P 12f4f3bc68 Changed blank starter and hello world images 2023-02-24 18:02:21 +00:00
Eric Allam 894a5dcb5d Pass through command flags 2023-02-24 17:58:05 +00:00
Eric Allam 3af9b63a9c Changeset: release next.1 version 2023-02-24 17:34:09 +00:00
Eric Allam 0932ae7d89 Log out when a run first starts, as well as completes 2023-02-24 17:32:13 +00:00
Eric Allam 12056d888b Update the docs for the new create-trigger cli 2023-02-24 16:36:50 +00:00
Eric Allam 63e004594c No workflows in the blank starter 2023-02-24 16:20:25 +00:00
James Ritchie ec3e98ca9f Updated the Orgs with more distinct icons 2023-02-24 16:12:48 +00:00
Eric Allam f3ffffd50b implement the New Workflow page and add the api key to the command to copy for templates and the main one 2023-02-24 15:59:34 +00:00
Eric Allam e6550d156e Convert basic starter to hello world and added isLive to templates to filter out the existing basic starter 2023-02-24 15:29:27 +00:00
James Ritchie ae550df37d Updated the Workflows Overview page if you have no workflows 2023-02-24 15:25:45 +00:00
James Ritchie 26ac1fa57c CopyText ignores default and propagation clicks 2023-02-24 15:25:45 +00:00
James Ritchie 14031a8c6e Title component now accepts className 2023-02-24 15:25:45 +00:00
James Ritchie 719ef2a3a9 Improved the grid template styles 2023-02-24 15:25:45 +00:00
73 changed files with 1308 additions and 509 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/sdk": patch
---
Make the schema an optional param for customEvent and webhookEvent
-5
View File
@@ -1,5 +0,0 @@
---
"create-trigger": minor
---
Easily scaffold out standalone trigger.dev projects using create-trigger and our templates
-31
View File
@@ -1,31 +0,0 @@
{
"mode": "pre",
"tag": "next",
"initialVersions": {
"integrations": "1.0.0",
"webapp": "1.0.0",
"wss": "1.0.0",
"@trigger.dev/airtable": "0.1.22",
"@trigger.dev/sendgrid": "0.1.22",
"@trigger.dev/github": "0.1.22",
"@trigger.dev/resend": "0.1.22",
"@trigger.dev/shopify": "0.1.22",
"@trigger.dev/slack": "0.1.22",
"@trigger.dev/whatsapp": "0.1.20",
"@trigger.dev/common-schemas": "0.1.1",
"create-trigger": "0.1.0",
"emails": "1.0.0",
"integration-catalog": "0.1.16",
"@trigger.dev/integration-sdk": "0.1.17",
"internal-bridge": "0.0.1",
"internal-cli": "0.0.1",
"internal-platform": "0.0.3",
"internal-pulsar": "0.0.1",
"@trigger.dev/sdk": "0.2.15"
},
"changesets": [
"big-apples-reflect",
"new-students-double",
"ten-dancers-hang"
]
}
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/sdk": patch
---
Added handly links to the dashboard in log feedback
+11 -6
View File
@@ -13,12 +13,17 @@ export function CopyText({
className,
onCopied,
}: CopyTextProps) {
const onClick = useCallback(() => {
navigator.clipboard.writeText(value);
if (onCopied) {
onCopied();
}
}, [value, onCopied]);
const onClick = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
navigator.clipboard.writeText(value);
if (onCopied) {
onCopied();
}
},
[value, onCopied]
);
return (
<div onClick={onClick} className={`${className}`}>
+30 -10
View File
@@ -1,6 +1,7 @@
import { CheckIcon, ClipboardIcon } from "@heroicons/react/24/outline";
import classNames from "classnames";
import { useCallback, useState } from "react";
import { EnvironmentIcon } from "~/routes/resources/environment";
import { CopyText } from "./CopyText";
const variantStyle = {
@@ -16,6 +17,7 @@ const variantStyle = {
export type CopyTextButtonProps = {
value: string;
text?: string;
className?: string;
variant?: "slate" | "blue" | "darkTransparent" | "lightTransparent" | "text";
};
@@ -23,6 +25,7 @@ export type CopyTextButtonProps = {
export function CopyTextButton({
value,
className,
text,
variant = "blue",
}: CopyTextButtonProps) {
const [copied, setCopied] = useState(false);
@@ -58,7 +61,27 @@ export function CopyTextButton({
);
}
export function CopyTextPanel({ value, className }: CopyTextButtonProps) {
const panelVariantStyle = {
primary:
"truncate text-indigo-300 bg-indigo-700/50 pl-3.5 pr-2 py-3 rounded border border-indigo-600 flex items-center justify-between gap-2 hover:cursor-pointer hover:bg-indigo-600/50 hover:border-indigo-600 transition",
slate:
"flex w-full items-center justify-between gap-2 truncate rounded bg-slate-850 py-2 pl-2.5 pr-1 transition hover:cursor-pointer hover:bg-slate-800 hover:text-slate-300",
text: "flex w-full items-center justify-between gap-2 truncate rounded bg-transparent py-2 pl-2.5 pr-1 transition hover:cursor-pointer hover:border-slate-700/50 hover:bg-slate-700/50",
};
export type CopyTextPanelProps = {
value: string;
text?: string;
className?: string;
variant?: "primary" | "slate" | "text";
};
export function CopyTextPanel({
value,
text,
className,
variant = "primary",
}: CopyTextPanelProps) {
const [copied, setCopied] = useState(false);
const onCopied = useCallback(() => {
setCopied(true);
@@ -67,21 +90,18 @@ export function CopyTextPanel({ value, className }: CopyTextButtonProps) {
}, 1500);
}, [setCopied]);
return (
<CopyText className={`${className}`} value={value} onCopied={onCopied}>
<CopyText value={value} onCopied={onCopied} className="w-full">
{copied ? (
<div className={copyTextPanelStyles}>
<span className="truncate font-mono text-sm">{value}</span>
<div className={classNames(className, panelVariantStyle[variant])}>
<span className="truncate font-mono text-sm">{text ?? value}</span>
<CheckIcon className="h-5 w-5 min-w-[1.25rem] text-green-500" />
</div>
) : (
<div className={copyTextPanelStyles}>
<span className="truncate font-mono text-sm">{value}</span>
<ClipboardIcon className="h-5 w-5 min-w-[1.25rem]" />
<div className={classNames(className, panelVariantStyle[variant])}>
<span className="truncate font-mono text-sm">{text ?? value}</span>
<ClipboardIcon className="h-4 w-4 min-w-[1.25rem]" />
</div>
)}
</CopyText>
);
}
const copyTextPanelStyles =
"truncate bg-indigo-700/50 pl-3.5 pr-2 py-3 rounded border border-indigo-600 truncate flex items-center justify-between gap-2 hover:cursor-pointer hover:bg-indigo-600/50 hover:border-indigo-600 transition";
@@ -1,6 +1,6 @@
import classNames from "classnames";
const baseClasses = "px-12 py-10";
const baseClasses = "px-4 py-4 md:px-8 md:py-6 lg:px-12 lg:py-10";
export function Container({
children,
@@ -1,5 +1,10 @@
import { Popover, Transition } from "@headlessui/react";
import { BookmarkIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
import {
BookmarkIcon,
BuildingOffice2Icon,
ChevronUpDownIcon,
UserIcon,
} from "@heroicons/react/24/outline";
import { CheckIcon, PlusIcon } from "@heroicons/react/24/solid";
import { Link } from "@remix-run/react";
import classNames from "classnames";
@@ -33,10 +38,10 @@ export function OrganizationMenu() {
<Popover.Button
className={`
${open ? "" : "text-opacity-90"}
inline-flex justify-between items-center rounded text-white bg-transparent pl-2.5 pr-2 py-2 text-sm hover:bg-slate-800 focus:outline-none`}
inline-flex items-center justify-between rounded bg-transparent py-2 pl-2.5 pr-2 text-sm text-white hover:bg-slate-800 focus:outline-none`}
>
<BookmarkIcon
className={`h-5 w-5 mr-2 ${dimmedClassNames}`}
<BuildingOffice2Icon
className={`mr-2 h-5 w-5 ${dimmedClassNames}`}
aria-hidden="true"
/>
<span className="transition">
@@ -67,9 +72,9 @@ export function OrganizationMenu() {
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-0 z-30 mt-3 w-screen min-w-max max-w-xs max-h-[70vh] translate-x-0 transform px-4 sm:px-0">
<Popover.Panel className="absolute left-0 z-30 mt-3 max-h-[70vh] w-screen min-w-max max-w-xs translate-x-0 transform px-4 sm:px-0">
<div className="overflow-hidden rounded-lg ring-1 ring-black ring-opacity-5">
<div className="relative grid gap-y-1 py-1 bg-slate-700 grid-cols-1">
<div className="relative grid grid-cols-1 gap-y-1 bg-slate-700 py-1">
{organizations.map((organization) => {
return (
<Popover.Button
@@ -77,16 +82,23 @@ export function OrganizationMenu() {
as={Link}
to={`/orgs/${organization.slug}`}
className={classNames(
"flex items-center justify-between gap-1.5 mx-1 px-3 py-2 text-white rounded hover:bg-slate-800 transition",
"mx-1 flex items-center justify-between gap-1.5 rounded px-3 py-2 text-white transition hover:bg-slate-800",
organization.slug === currentOrganization?.slug &&
"!bg-slate-800"
)}
>
<div className="flex items-center gap-2">
<BookmarkIcon
className="h-5 w-5 z-100"
aria-hidden="true"
/>
{organization.title === "Personal Workspace" ? (
<UserIcon
className="z-100 h-5 w-5 text-slate-400"
aria-hidden="true"
/>
) : (
<BuildingOffice2Icon
className="z-100 h-5 w-5 text-slate-400"
aria-hidden="true"
/>
)}
<span className="block truncate">
{organization.title}
</span>
@@ -99,7 +111,7 @@ export function OrganizationMenu() {
);
})}
<Popover.Button as={Link} to={`/orgs/new`}>
<div className="flex items-center gap-2 mx-1 pl-2.5 py-2 rounded hover:bg-slate-800 transition">
<div className="mx-1 flex items-center gap-2 rounded py-2 pl-2.5 transition hover:bg-slate-800">
<PlusIcon
className="h-5 w-5 text-green-500"
aria-hidden="true"
@@ -1,29 +1,37 @@
import {
SquaresPlusIcon,
EyeIcon,
EyeSlashIcon,
InformationCircleIcon,
} from "@heroicons/react/20/solid";
import {
ArrowsRightLeftIcon,
UsersIcon,
ForwardIcon,
ChevronLeftIcon,
ArrowTopRightOnSquareIcon,
PhoneArrowUpRightIcon,
EnvelopeIcon,
BeakerIcon,
ClipboardDocumentCheckIcon,
Squares2X2Icon,
ChevronLeftIcon,
Cog6ToothIcon,
EnvelopeIcon,
ForwardIcon,
PhoneArrowUpRightIcon,
PlusCircleIcon,
Squares2X2Icon,
SquaresPlusIcon,
UsersIcon,
} from "@heroicons/react/24/outline";
import { Link, NavLink } from "@remix-run/react";
import { useState } from "react";
import invariant from "tiny-invariant";
import { useCurrentEnvironment } from "~/hooks/useEnvironments";
import {
useCurrentOrganization,
useOrganizations,
} from "~/hooks/useOrganizations";
import { useCurrentWorkflow } from "~/hooks/useWorkflows";
import { EnvironmentIcon } from "~/routes/resources/environment";
import { titleCase } from "~/utils";
import { CopyTextPanel } from "../CopyTextButton";
import { TertiaryA, TertiaryButton } from "../primitives/Buttons";
import { Body } from "../primitives/text/Body";
import { Header1 } from "../primitives/text/Headers";
import invariant from "tiny-invariant";
import { CopyText } from "../CopyText";
import { useCurrentEnvironment } from "~/hooks/useEnvironments";
export function SideMenuContainer({ children }: { children: React.ReactNode }) {
return <div className="grid h-full grid-cols-[300px_2fr]">{children}</div>;
@@ -160,6 +168,8 @@ function SideMenu({
const organization = useCurrentOrganization();
invariant(organization, "Organization must be defined");
const [isShowingKeys, setIsShowingKeys] = useState(false);
return (
<div className="flex min-h-0 flex-1 flex-col border-r border-slate-800 bg-slate-950">
<div className="flex flex-1 flex-col overflow-y-auto pb-4">
@@ -199,34 +209,59 @@ function SideMenu({
))}
</div>
<div className="flex flex-col gap-6">
<ul className="ml-3 mr-2 flex flex-col gap-6">
<ul className="ml-3 mr-2 flex flex-col gap-2">
<li className="flex w-full items-center justify-between">
<TertiaryA
href="https://docs.trigger.dev/guides/environments"
target="_blank"
className="group flex items-center gap-1 transition"
>
<InformationCircleIcon className="h-4 w-4 text-slate-500 transition group-hover:text-slate-400" />
<Body
size="extra-small"
className={`overflow-hidden text-slate-300 transition group-hover:text-slate-400 ${menuSmallTitleStyle}`}
>
API keys
</Body>
</TertiaryA>
{!isShowingKeys ? (
<TertiaryButton
onClick={() => setIsShowingKeys(true)}
className="group mr-1.5 transition before:text-xs before:text-slate-400 hover:before:content-['Show_keys']"
>
<EyeIcon className="h-4 w-4 text-slate-500 transition group-hover:text-slate-400" />
</TertiaryButton>
) : (
<TertiaryButton
onClick={() => setIsShowingKeys(false)}
className="group mr-1.5 transition before:text-xs before:text-slate-400 hover:before:content-['Hide_keys']"
>
<EyeSlashIcon className="h-4 w-4 text-slate-500 transition group-hover:text-slate-400" />
</TertiaryButton>
)}
</li>
{organization.environments.map((environment) => {
return (
<li
key={environment.id}
className="flex w-full flex-col justify-between gap-1.5"
className="flex w-full flex-col justify-between"
>
<div className="flex justify-between">
<Body
size="extra-small"
className={`overflow-hidden text-slate-300 ${menuSmallTitleStyle}`}
>
{environment.slug} api key
</Body>
{/* <CopyTextButton
variant="text"
<div className="relative flex items-center">
<EnvironmentIcon
slug={environment.slug}
className="absolute top-4 left-2"
/>
<CopyTextPanel
value={environment.apiKey}
/> */}
</div>
<div className="relative select-all overflow-hidden rounded-sm border border-slate-800 p-1 pl-2 text-sm text-slate-400">
<span className="pointer-events-none absolute right-7 top-0 block h-6 w-20 bg-gradient-to-r from-transparent to-slate-950"></span>
<CopyText
value={environment.apiKey}
className="group absolute right-0 top-0 flex h-full w-7 items-center justify-center rounded-sm border-l border-slate-800 bg-slate-950 transition hover:cursor-pointer hover:bg-slate-900 active:bg-green-900"
>
<ClipboardDocumentCheckIcon className="h-5 w-5 group-active:text-green-500" />
</CopyText>
{environment.apiKey}
text={
isShowingKeys
? environment.apiKey
: `${titleCase(environment.slug)}`
}
variant="slate"
className="pl-6 text-slate-300 hover:text-slate-300"
/>
</div>
</li>
);
@@ -1,8 +1,15 @@
import classNames from "classnames";
import { Header1 } from "./Headers";
export function Title({ children }: { children: React.ReactNode }) {
export function Title({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<Header1 size="extra-large" className="mb-6 text-slate-200">
<Header1 size="extra-large" className={classNames("mb-6 text-slate-200")}>
{children}
</Header1>
);
@@ -35,7 +35,7 @@ export function TemplateCard({
{template.description}
</Body>
</div>
<div className="flex flex-row gap-x-1">
<div className="flex gap-x-1">
{template.services.map((service) => (
<div key={service.service} className="">
<ApiLogoIcon
@@ -1,3 +1,4 @@
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import classNames from "classnames";
import { Fragment } from "react";
import type { TemplateListItem } from "~/presenters/templateListPresenter.server";
@@ -10,9 +11,11 @@ import { Header1 } from "../primitives/text/Headers";
export function TemplateOverview({
template,
className,
commandFlags,
}: {
template: TemplateListItem;
className?: string;
commandFlags?: string;
}) {
const { docsHTML, imageUrl } = template;
@@ -20,21 +23,25 @@ export function TemplateOverview({
<div
className={classNames(
className,
"grid w-full grid-cols-1 gap-8 rounded-lg bg-slate-850 pl-8 text-left md:grid-cols-[20rem_minmax(0,_1fr)]"
"grid w-full grid-cols-1 gap-8 rounded-lg bg-slate-850 pl-8 text-left lg:grid-cols-[24rem_minmax(0,_1fr)]"
)}
>
<div className="sticky top-4 flex h-max flex-col rounded-r">
<TemplateDetails template={template} className="hidden md:flex" />
<div className="flex h-max flex-col rounded-r lg:sticky lg:top-4">
<TemplateDetails
template={template}
commandFlags={commandFlags}
className="hidden lg:flex"
/>
</div>
<div className="flex h-full w-full flex-col rounded">
<div className="z-90 h-fit w-full transition group-hover:opacity-90">
<div className="hidden h-fit w-full transition group-hover:opacity-90 lg:block">
<img
src={imageUrl}
alt=""
className="h-full w-full rounded-t object-cover"
alt="Template hero image"
className="h-full w-full rounded-t-md object-cover"
/>
</div>
<TemplateDetails template={template} className="md:hidden" />
<TemplateDetails template={template} className="lg:hidden" />
<div className="flex rounded-b bg-slate-900/75 p-8">
<div
className="prose prose-sm prose-invert min-w-full [&>pre]:bg-[rgb(17,23,41)]"
@@ -51,9 +58,11 @@ export function TemplateOverview({
function TemplateDetails({
className,
template,
commandFlags,
}: {
className?: string;
template: TemplateListItem;
commandFlags?: string;
}) {
const { title, description, repositoryUrl, id } = template;
return (
@@ -104,6 +113,7 @@ function TemplateDetails({
className="!max-w-full"
>
View Repo
<ArrowTopRightOnSquareIcon className="ml-1 h-4 w-4" />
</SecondaryA>
<SecondaryA
href="https://docs.trigger.dev"
@@ -111,6 +121,7 @@ function TemplateDetails({
className="!max-w-full"
>
View Docs
<ArrowTopRightOnSquareIcon className="ml-1 h-4 w-4" />
</SecondaryA>
</div>
<div className="mb-2 flex items-center">
@@ -122,7 +133,17 @@ function TemplateDetails({
</Body>
<div className="ml-2 h-px w-full bg-slate-800" />
</div>
<CopyTextPanel value={`npm create trigger@latest ${template.slug}`} />
<Body className="mb-4 text-slate-400">
Run this command in your terminal to create a new project using this
template.
</Body>
<CopyTextPanel
text={`npx create-trigger ${template.slug}`}
value={`npx create-trigger@latest ${template.slug} ${
commandFlags ? ` ${commandFlags}` : ""
}`}
className="mb-8"
/>
</div>
);
}
@@ -1,19 +1,21 @@
import { XCircleIcon } from "@heroicons/react/24/solid";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { Link } from "@remix-run/react";
import classNames from "classnames";
import { Fragment, useState } from "react";
import type { TemplateListItem } from "~/presenters/templateListPresenter.server";
import { CopyTextPanel } from "../CopyTextButton";
import { StyledDialog } from "../primitives/Dialog";
import { Body } from "../primitives/text/Body";
import { Header1 } from "../primitives/text/Headers";
import { TemplateOverview } from "./TemplateOverview";
export function TemplatesGrid({
templates,
openInNewPage,
commandFlags,
}: {
templates: Array<TemplateListItem>;
openInNewPage: boolean;
commandFlags?: string;
}) {
const [openedTemplate, setOpenedTemplate] = useState<TemplateListItem | null>(
null
@@ -28,17 +30,22 @@ export function TemplatesGrid({
show={isOpen}
as={Fragment}
>
<StyledDialog.Panel className="relative mx-auto flex max-h-[80vh] max-w-5xl items-start gap-2 overflow-hidden overflow-y-auto rounded-md">
{openedTemplate && <TemplateOverview template={openedTemplate} />}
<StyledDialog.Panel className="relative mx-auto flex max-h-[80vh] max-w-6xl items-start gap-2 overflow-hidden overflow-y-auto rounded-md border border-slate-700">
{openedTemplate && (
<TemplateOverview
template={openedTemplate}
commandFlags={commandFlags}
/>
)}
<button
onClick={() => setOpenedTemplate(null)}
className="sticky top-0 text-slate-600 transition hover:text-slate-500"
className="group sticky top-2 -ml-[48px] rounded text-slate-400 transition hover:bg-slate-800/70 hover:text-slate-500"
>
<XCircleIcon className="h-10 w-10" />
<XMarkIcon className="h-8 w-8 transition group-hover:text-slate-300" />
</button>
</StyledDialog.Panel>
</StyledDialog.Dialog>
<div className="grid w-full grid-cols-1 items-start justify-start gap-4 md:grid-cols-2 lg:grid-cols-3">
<div className="grid w-full grid-cols-1 items-start justify-start gap-5 md:grid-cols-2 lg:grid-cols-3">
{templates.map((template) => {
return (
<TemplateButtonOrLink
@@ -46,26 +53,25 @@ export function TemplatesGrid({
template={template}
openInNewPage={openInNewPage}
onClick={() => setOpenedTemplate(template)}
className="p-5"
>
<div className="w-full bg-slate-600 transition group-hover:opacity-90">
<div className="w-full transition group-hover:opacity-90 group-hover:shadow-lg">
<img
src={template.imageUrl}
alt={template.title}
className="h-32 w-full object-cover"
className="h-32 w-full rounded-md object-cover"
/>
</div>
<div className="flex h-full w-full flex-col justify-between p-5">
<div className="flex flex-col gap-y-2">
<Header1 size="small" className="font-semibold">
{template.title}
</Header1>
<Body size="small" className="text-slate-400">
{template.description}
</Body>
</div>
<div className="flex h-full w-full flex-col justify-between">
<Header1 size="regular" className="py-6 text-slate-100">
{template.title}
</Header1>
<CopyTextPanel
value={`npm create trigger@latest ${template.slug}`}
className="mt-5"
value={`npx create-trigger@latest ${template.slug}${
commandFlags ? ` ${commandFlags}` : ``
}`}
text={`npx create-trigger ${template.slug}`}
className=""
/>
</div>
</TemplateButtonOrLink>
@@ -81,18 +87,20 @@ function TemplateButtonOrLink({
openInNewPage,
onClick,
children,
className,
}: {
template: TemplateListItem;
openInNewPage: boolean;
onClick: (e: React.MouseEvent) => void;
children: React.ReactNode;
className?: string;
}) {
const classNames =
"group flex w-full flex-col self-stretch overflow-hidden rounded-md border border-slate-700 bg-slate-800 text-left text-slate-200 shadow-md transition hover:cursor-pointer hover:border-slate-600 hover:bg-slate-700/50 disabled:opacity-50";
const cardStyles =
"group flex w-full p-5 flex-col self-stretch overflow-hidden rounded-md border border-slate-700/70 bg-slate-800 text-left text-slate-200 shadow-md transition hover:cursor-pointer hover:border-slate-600 hover:bg-slate-700/50 disabled:opacity-50";
if (openInNewPage) {
return (
<Link to={template.slug} className={classNames}>
<Link to={template.slug} className={classNames(cardStyles, className)}>
{children}
</Link>
);
@@ -102,7 +110,7 @@ function TemplateButtonOrLink({
key={template.title}
type="button"
onClick={onClick}
className="group flex w-full flex-col self-stretch overflow-hidden rounded-md border border-slate-700 bg-slate-800 text-left text-slate-200 shadow-md transition hover:cursor-pointer hover:border-slate-500 hover:bg-slate-700/30 disabled:opacity-50"
className={cardStyles}
>
{children}
</button>
@@ -0,0 +1,102 @@
import { InformationCircleIcon } from "@heroicons/react/20/solid";
import {
ArrowTopRightOnSquareIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useState } from "react";
import type { TemplateListItem } from "~/presenters/templateListPresenter.server";
import { CopyTextPanel } from "../CopyTextButton";
import { PrimaryA, TertiaryButton } from "../primitives/Buttons";
import { StyledDialog } from "../primitives/Dialog";
import { Body } from "../primitives/text/Body";
import { Header4 } from "../primitives/text/Headers";
import { SubTitle } from "../primitives/text/SubTitle";
import { TemplatesGrid } from "../templates/TemplatesGrid";
export function WorkflowOnboarding({
apiKey,
templates,
}: {
apiKey: string;
templates: TemplateListItem[];
}) {
let [isOpen, setIsOpen] = useState(false);
return (
<>
<StyledDialog.Dialog
onClose={(e) => setIsOpen(false)}
appear
show={isOpen}
as={Fragment}
>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center">
<StyledDialog.Panel className="mx-auto flex max-w-2xl flex-col justify-center overflow-hidden rounded-md border border-slate-700 bg-slate-850 text-left">
<div className="flex w-full items-center justify-between py-3 pr-3 pl-5">
<div className="flex w-full items-center gap-2">
<Header4 size="small" className="text-slate-300">
Setup Trigger.dev manually
</Header4>
</div>
<button
onClick={(e) => setIsOpen(false)}
className="group rounded p-1 text-slate-600 transition hover:bg-slate-700 hover:text-slate-500"
>
<XMarkIcon className="h-6 w-6 text-slate-600 transition group-hover:text-slate-400" />
</button>
</div>
<div className="flex h-full w-full flex-col overflow-hidden bg-slate-800 p-5">
<div className="grid grid-cols-[minmax(0,_1fr),_4rem,_minmax(0,_1fr)]">
<div className="flex h-full flex-col justify-between">
<Body className="mb-4 text-slate-400">
Add Trigger.dev to an existing Node.js repo.
</Body>
<PrimaryA
href="https://docs.trigger.dev/getting-started#manual-setup"
target="_blank"
>
Manual setup docs
<ArrowTopRightOnSquareIcon className="ml-1 h-4 w-4" />
</PrimaryA>
</div>
<div className="flex flex-col items-center gap-2">
<div className="h-full w-px bg-slate-700"></div>
<Body size="small" className="uppercase text-slate-600">
or
</Body>
<div className="h-full w-px bg-slate-700"></div>
</div>
<div className="flex h-full flex-col justify-between">
<Body className="mb-4 text-slate-400">
Setup a new Node.js project ready for Trigger.dev by
running this command.
</Body>
<CopyTextPanel
value={`npx create-trigger@latest --apiKey ${apiKey}`}
text={`npx create-trigger --apiKey ${apiKey}`}
/>
</div>
</div>
</div>
</StyledDialog.Panel>
</div>
</div>
</StyledDialog.Dialog>
<div className="mb-2 flex w-full items-center justify-between">
<SubTitle className="mb-0">Get started with a template</SubTitle>
<TertiaryButton onClick={(e) => setIsOpen(true)}>
<InformationCircleIcon className="h-4 w-4" />
Setup manually instead
</TertiaryButton>
</div>
<div>
<TemplatesGrid
openInNewPage={false}
templates={templates}
commandFlags={`-k ${apiKey}`}
/>
</div>
</>
);
}
+2
View File
@@ -0,0 +1,2 @@
export const LIVE_ENVIRONMENT = "live";
export const DEV_ENVIRONMENT = "development";
+15
View File
@@ -1,3 +1,4 @@
import { DEV_ENVIRONMENT } from "~/consts";
import type { RuntimeEnvironment } from "~/models/runtimeEnvironment.server";
import { useMatchesData } from "~/utils";
@@ -48,3 +49,17 @@ export function useCurrentEnvironment(): RuntimeEnvironment | undefined {
);
return currentEnvironment;
}
export function useDevEnvironment(): RuntimeEnvironment | undefined {
const routeMatch = useMatchesData("routes/__app/orgs/$organizationSlug");
if (
!routeMatch ||
!isRuntimeEnvironments(routeMatch.data.organization.environments)
) {
return undefined;
}
return routeMatch.data.organization.environments.find(
(environment: any) => environment.slug === DEV_ENVIRONMENT
);
}
+7 -6
View File
@@ -3,11 +3,12 @@ import type { loader as appLoader } from "~/routes/__app";
import type { loader as orgLoader } from "~/routes/__app/orgs/$organizationSlug";
import { hydrateObject, useMatchesData } from "~/utils";
export type MatchedOrganization = UseDataFunctionReturn<
typeof appLoader
>["organizations"][number];
export function useOrganizations() {
return (
getOrganizationsFromMatchesData("routes/__app") ??
getOrganizationsFromMatchesData("routes/__public")
);
return useOrganizationsFromMatchesData(["routes/__app", "routes/__public"]);
}
export function useCurrentOrganization() {
@@ -34,8 +35,8 @@ export function useIsNewOrganizationPage(): boolean {
return !!routeMatch;
}
function getOrganizationsFromMatchesData(path: string) {
const routeMatch = useMatchesData(path);
function useOrganizationsFromMatchesData(paths: string[]) {
const routeMatch = useMatchesData(paths);
if (!routeMatch || !routeMatch.data.organizations) {
return undefined;
@@ -4,6 +4,7 @@ import slug from "slug";
import { customAlphabet } from "nanoid";
import { generateTwoRandomWords } from "~/utils/randomWords";
import { taskQueue } from "~/services/messageBroker.server";
import { DEV_ENVIRONMENT, LIVE_ENVIRONMENT } from "~/consts";
export type { Organization } from ".prisma/client";
@@ -67,6 +68,11 @@ export function getOrganizations({ userId }: { userId: User["id"] }) {
return prisma.organization.findMany({
where: { users: { some: { id: userId } } },
orderBy: { createdAt: "desc" },
include: {
environments: {
orderBy: { slug: "asc" },
},
},
});
}
@@ -144,8 +150,8 @@ export async function createOrganization({
if (organization) {
// Create the dev and prod environments
await createEnvironment(organization, "development");
await createEnvironment(organization, "live");
await createEnvironment(organization, DEV_ENVIRONMENT);
await createEnvironment(organization, LIVE_ENVIRONMENT);
await taskQueue.publish("ORGANIZATION_CREATED", {
id: organization.id,
@@ -5,6 +5,7 @@ import { renderMarkdown } from "~/services/renderMarkdown.server";
import type { TemplateListItem } from "./templateListPresenter.server";
import { WorkflowsPresenter } from "../presenters/workflowsPresenter.server";
import { getServiceMetadatas } from "~/models/integrations.server";
import { DEV_ENVIRONMENT, LIVE_ENVIRONMENT } from "~/consts";
export class OrganizationTemplatePresenter {
#prismaClient: PrismaClient;
@@ -71,10 +72,10 @@ export class OrganizationTemplatePresenter {
const developmentApiKey =
organizationTemplate.organization.environments.find(
(e) => e.slug === "development"
(e) => e.slug === DEV_ENVIRONMENT
)?.apiKey;
const liveApiKey = organizationTemplate.organization.environments.find(
(e) => e.slug === "live"
(e) => e.slug === LIVE_ENVIRONMENT
)?.apiKey;
return {
@@ -20,6 +20,7 @@ export class TemplateListPresenter {
async data(): Promise<{ templates: Array<TemplateListItem> }> {
const templates = await this.#prismaClient.template.findMany({
orderBy: { priority: "asc" },
where: { isLive: true },
});
const serviceMetadatas = await getServiceMetadatas(true);
@@ -3,10 +3,11 @@ import type { PrismaClient } from "~/db.server";
import { prisma } from "~/db.server";
import { getRuntimeEnvironment } from "~/models/runtimeEnvironment.server";
import { WorkflowsPresenter } from "../presenters/workflowsPresenter.server";
import { TemplateListPresenter } from "./templateListPresenter.server";
export type WorkflowListItem = Awaited<
ReturnType<WorkflowListPresenter["data"]>
>[number];
>["workflows"][number];
export class WorkflowListPresenter {
#prismaClient: PrismaClient;
@@ -28,11 +29,19 @@ export class WorkflowListPresenter {
});
invariant(runtimeEnvironment, "Runtime environment not found");
const templatesPresenter = new TemplateListPresenter();
const workflowsPresenter = new WorkflowsPresenter();
return workflowsPresenter.data(
const workflows = await workflowsPresenter.data(
{ organization: { slug: organizationSlug }, isArchived: false },
runtimeEnvironment.id
);
const { templates } = await templatesPresenter.data();
return {
workflows,
templates,
};
}
}
@@ -45,6 +45,7 @@ export class WorkflowStartPresenter {
orderBy: {
priority: "asc",
},
where: { isLive: true },
});
return {
+50 -15
View File
@@ -1,17 +1,29 @@
import { BookmarkIcon, PlusIcon } from "@heroicons/react/24/outline";
import {
BuildingOffice2Icon,
PlusIcon,
UserIcon,
} from "@heroicons/react/24/outline";
import { Link } from "@remix-run/react";
import classNames from "classnames";
import { CopyTextPanel } from "~/components/CopyTextButton";
import { Body } from "~/components/primitives/text/Body";
import { Header4 } from "~/components/primitives/text/Headers";
import type { MatchedOrganization } from "~/hooks/useOrganizations";
import { useOrganizations } from "~/hooks/useOrganizations";
import type { Organization } from "~/models/organization.server";
import { environmentShortName } from "~/utils";
export default function AppLayout() {
const organizations = useOrganizations();
return (
<>
<div className="flex items-center justify-center m-20">
<ul className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 max-w-8xl gap-2">
<div className="flex h-80 w-full items-center justify-center bg-slate-900/50">
<h1 className="relative bottom-6 text-4xl text-slate-400">
Your Organizations
</h1>
</div>
<div className="flex items-center justify-center">
<ul className="-mt-24 grid max-w-7xl grid-cols-2 gap-2 lg:grid-cols-3">
{organizations ? (
<OrganizationGrid organizations={organizations} />
) : (
@@ -23,12 +35,14 @@ export default function AppLayout() {
<Link
to="orgs/new"
className={classNames(
"border-2 border-slate-800 text-center hover:border-transparent hover:bg-slate-800/50 hover:shadow-md",
"h-full border border-slate-700 hover:border-transparent hover:bg-[rgb(38,51,71)] hover:shadow-md",
boxClasses
)}
>
<PlusIcon className="h-10 w-10 text-green-500" />
New Organization
<Header4 size="small" className="mb-10">
New Organization
</Header4>
</Link>
</li>
</ul>
@@ -40,7 +54,7 @@ export default function AppLayout() {
function OrganizationGrid({
organizations,
}: {
organizations: Organization[];
organizations: MatchedOrganization[];
}) {
return (
<>
@@ -54,26 +68,47 @@ function OrganizationGrid({
);
}
const boxClasses =
"flex flex-col gap-4 items-center justify-center min-h-40 rounded-lg px-6 py-6 min-h-[15rem] transition";
function OrganizationGridItem({
organization,
}: {
organization: Organization;
organization: MatchedOrganization;
}) {
return (
<li key={organization.id} className="w-full h-full">
<li key={organization.id} className="h-full w-full">
<Link
to={`orgs/${organization.slug}`}
className={classNames(
"bg-slate-800 shadow-md text-center hover:bg-slate-800/50",
"border border-slate-700 bg-slate-800 hover:bg-[rgb(38,51,71)]",
boxClasses
)}
>
<BookmarkIcon className="h-10 w-10" />
{organization.title}{" "}
{organization.title === "Personal Workspace" ? (
<UserIcon className="h-10 w-10 text-slate-300" aria-hidden="true" />
) : (
<BuildingOffice2Icon
className="h-10 w-10 text-blue-500"
aria-hidden="true"
/>
)}
<Header4 size="large" className="mb-10 text-slate-300">
{organization.title}
</Header4>
<div className="grid w-full grid-cols-2 gap-2">
{organization.environments.map((environment) => (
<div key={environment.id} className="flex w-full items-center">
<CopyTextPanel
value={environment.apiKey}
text={`${environmentShortName(environment.slug)} API Key`}
variant="slate"
className="w-full text-slate-500"
/>
</div>
))}
</div>
</Link>
</li>
);
}
const boxClasses =
"flex flex-col gap-4 w-80 text-center shadow-md items-center justify-center rounded-lg px-2 pb-2 pt-14 min-h-full transition";
@@ -3,11 +3,11 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { CreateNewWorkflow } from "~/components/CreateNewWorkflow";
import { Container } from "~/components/layout/Container";
import { PanelInfo } from "~/components/layout/PanelInfo";
import { PrimaryLink } from "~/components/primitives/Buttons";
import { SubTitle } from "~/components/primitives/text/SubTitle";
import { Title } from "~/components/primitives/text/Title";
import { WorkflowList } from "~/components/workflows/workflowList";
import { WorkflowOnboarding } from "~/components/workflows/WorkflowOnboarding";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useCurrentOrganization } from "~/hooks/useOrganizations";
import { getRuntimeEnvironmentFromRequest } from "~/models/runtimeEnvironment.server";
import { WorkflowListPresenter } from "~/presenters/workflowListPresenter.server";
@@ -22,8 +22,7 @@ export const loader = async ({ request, params }: LoaderArgs) => {
const presenter = new WorkflowListPresenter();
try {
const workflows = await presenter.data(params.organizationSlug, currentEnv);
return typedjson({ workflows });
return typedjson(await presenter.data(params.organizationSlug, currentEnv));
} catch (error: any) {
console.error(error);
throw new Response("Error ", { status: 400 });
@@ -31,30 +30,33 @@ export const loader = async ({ request, params }: LoaderArgs) => {
};
export default function Page() {
const { workflows } = useTypedLoaderData<typeof loader>();
const { workflows, templates } = useTypedLoaderData<typeof loader>();
const currentOrganization = useCurrentOrganization();
const currentEnv = useDevEnvironment();
if (currentOrganization === undefined) {
return <></>;
}
if (currentEnv === undefined) {
return <></>;
}
return (
<Container>
<Title>Workflows</Title>
{workflows.length === 0 ? (
<>
<SubTitle>0 workflows</SubTitle>
<PanelInfo
message="You don't have any workflows yet. They will appear here once
connected."
className="mb-4 max-w-4xl p-4 pr-6"
>
<PrimaryLink to={`/orgs/${currentOrganization.slug}/workflows/new`}>
Create first workflow
</PrimaryLink>
</PanelInfo>
<Title>Create your first workflow</Title>
<div className="max-w-6xl">
<WorkflowOnboarding
templates={templates}
apiKey={currentEnv.apiKey}
/>
</div>
</>
) : (
<>
<Title>Workflows</Title>
<SubTitle>
{workflows.length} active workflow{workflows.length > 1 ? "s" : ""}
</SubTitle>
@@ -109,7 +109,7 @@ export default function Integrations() {
</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>
<span className="px-6 text-base text-slate-200">
<span className="mb-4 px-6 text-base text-slate-200">
Let us know!
</span>
</div>
@@ -1,6 +1,7 @@
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { SubTitle } from "~/components/primitives/text/SubTitle";
import { TemplatesGrid } from "~/components/templates/TemplatesGrid";
import { WorkflowOnboarding } from "~/components/workflows/WorkflowOnboarding";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useCurrentOrganization } from "~/hooks/useOrganizations";
import { TemplateListPresenter } from "~/presenters/templateListPresenter.server";
export const loader = async () => {
@@ -10,12 +11,19 @@ export const loader = async () => {
export default function NewWorkflowStep1Page() {
const { templates } = useTypedLoaderData<typeof loader>();
const currentOrganization = useCurrentOrganization();
const currentEnv = useDevEnvironment();
if (currentOrganization === undefined) {
return <></>;
}
if (currentEnv === undefined) {
return <></>;
}
return (
<div className="max-w-5xl">
<SubTitle>
Install one of these Templates directly into your codebase
</SubTitle>
<TemplatesGrid templates={templates} openInNewPage={false} />
<div className="max-w-6xl">
<WorkflowOnboarding templates={templates} apiKey={currentEnv.apiKey} />
</div>
);
}
@@ -41,6 +41,7 @@ import { RunsTable } from "~/components/runs/RunsTable";
import { TriggerBody } from "~/components/triggers/Trigger";
import { TriggerTypeIcon } from "~/components/triggers/TriggerIcons";
import { triggerLabel } from "~/components/triggers/triggerLabel";
import { DEV_ENVIRONMENT } from "~/consts";
import { useConnectionSlots } from "~/hooks/useConnectionSlots";
import { useCurrentEnvironment } from "~/hooks/useEnvironments";
import { useCurrentOrganization } from "~/hooks/useOrganizations";
@@ -107,7 +108,7 @@ export default function Page() {
className="mb-6"
message={`This workflow hasn't been connected in the ${environment.slug} environment yet.`}
></PanelWarning>
{environment.slug === "development" ? (
{environment.slug === DEV_ENVIRONMENT ? (
<ConnectToDevelopmentInstructions environment={environment} />
) : (
<ConnectToLiveInstructions environment={environment} />
@@ -399,7 +400,7 @@ export default function Page() {
<Disclosure.Panel className="p-6">
<div className="mb-1 flex items-baseline gap-2">
<SubTitle className="text-slate-300">
Trigger your workflow from the source
Trigger your workflow for real
</SubTitle>
<span className="relative -top-0.5 rounded-full bg-blue-700 px-2 pt-1 pb-0.5 text-[0.6rem] font-medium uppercase tracking-wider text-blue-200">
Recommended
@@ -1,10 +1,9 @@
import { Outlet } from "@remix-run/react";
import { Container } from "~/components/layout/Container";
export default function TemplatesLayout() {
return (
<Container>
<div className="px-4 py-6 md:px-8 md:py-6 lg:px-12 lg:py-10">
<Outlet />
</Container>
</div>
);
}
@@ -26,7 +26,7 @@ export default function TemplateSlugPage() {
<div className="mx-auto max-w-[1188px]">
<Link
to="/templates"
className="mb-4 -ml-1 flex w-max items-center justify-start gap-2 text-sm text-slate-500 transition hover:text-slate-300"
className="mb-4 ml-4 flex w-max items-center justify-start gap-2 text-sm text-slate-500 transition hover:text-slate-300 lg:-ml-1"
>
<ArrowLeftIcon className="h-3 w-3" />
Choose a different Template
@@ -15,10 +15,10 @@ export default function TemplateList() {
return (
<div className="mx-auto mt-4 flex max-w-6xl flex-col lg:mt-6">
<h1 className="mb-6 text-center font-title text-5xl font-semibold text-slate-200">
Choose your Template
Choose a template
</h1>
<Header2 size="small" className="mb-8 text-center text-slate-400">
Quickly get started with your workflow by using a pre-built example.
<Header2 size="small" className="mb-16 text-center text-slate-400">
Quickly get started with your workflow by using a pre-built template.
</Header2>
<TemplatesGrid templates={templates} openInNewPage={true} />
</div>
@@ -4,6 +4,7 @@ import { prisma } from "~/db.server";
export async function loader() {
const templates = await prisma.template.findMany({
orderBy: { priority: "asc" },
where: { isLive: true },
});
return json(templates);
@@ -78,7 +78,7 @@ export function EnvironmentMenu() {
<Popover.Button
className={`
${open ? "" : ""}
inline-flex justify-between gap-2 items-center rounded text-white bg-transparent pl-3.5 pr-2 py-2 text-sm hover:bg-slate-800 focus:outline-none`}
inline-flex items-center justify-between gap-2 rounded bg-transparent py-2 pl-3.5 pr-2 text-sm text-white hover:bg-slate-800 focus:outline-none`}
>
<EnvironmentIcon slug={currentEnvironment.slug} />
<span className="transition">
@@ -90,7 +90,7 @@ export function EnvironmentMenu() {
</span>
<ChevronUpDownIcon
className={`${open ? "" : ""}
ml-1 h-5 w-5 transition duration-150 ease-in-out text-slate-500`}
ml-1 h-5 w-5 text-slate-500 transition duration-150 ease-in-out`}
aria-hidden="true"
/>
</Popover.Button>
@@ -105,7 +105,7 @@ export function EnvironmentMenu() {
>
<Popover.Panel className="absolute left-0 z-30 mt-3 w-screen min-w-max max-w-xs translate-x-0 transform px-4 sm:px-0">
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
<div className="relative grid gap-y-1 py-1 bg-slate-700 grid-cols-1">
<div className="relative grid grid-cols-1 gap-y-1 bg-slate-700 py-1">
{environments.map((environment) => {
return (
<Popover.Button
@@ -115,7 +115,7 @@ export function EnvironmentMenu() {
name="environment"
value={environment.slug}
className={classNames(
"flex items-center justify-between gap-1.5 mx-1 px-3 py-2 text-white rounded hover:bg-slate-800 transition",
"mx-1 flex items-center justify-between gap-1.5 rounded px-3 py-2 text-white transition hover:bg-slate-800",
environment.slug === currentEnvironment?.slug &&
"!bg-slate-800"
)}
@@ -144,7 +144,13 @@ export function EnvironmentMenu() {
);
}
function EnvironmentIcon({ slug }: { slug: string }) {
export function EnvironmentIcon({
slug,
className,
}: {
slug: string;
className?: string;
}) {
let color = "bg-emerald-500";
if (slug === "live") {
color = "bg-orange-500";
@@ -152,8 +158,9 @@ function EnvironmentIcon({ slug }: { slug: string }) {
return (
<span
className={classNames(
"rounded-full block w-[0.35rem] h-[0.35rem]",
color
"block h-[0.35rem] w-[0.35rem] rounded-full",
color,
className
)}
/>
);
@@ -791,6 +791,7 @@ function createTaskQueue() {
"x-ttl": run.workflow.triggerTtlInSeconds,
"x-is-test": run.isTest ? "true" : "false",
"x-app-origin": env.APP_ORIGIN,
"x-attempt": String(run.attemptCount),
},
{
eventTimestamp: run.event.timestamp.getTime(),
+20 -6
View File
@@ -1,6 +1,6 @@
import type { RouteMatch } from "@remix-run/react";
import { useMatches } from "@remix-run/react";
import { useMemo } from "react";
import { DEV_ENVIRONMENT } from "./consts";
const DEFAULT_REDIRECT = "/";
@@ -33,18 +33,23 @@ export function safeRedirect(
* @returns {JSON|undefined} The router data or undefined if not found
*/
export function useMatchesData(
id: string,
id: string | string[],
debug: boolean = false
): RouteMatch | undefined {
const matchingRoutes = useMatches();
if (debug) {
console.log("matchingRoutes", matchingRoutes);
}
const route = useMemo(
() => matchingRoutes.find((route) => route.id === id),
[matchingRoutes, id]
);
const paths = Array.isArray(id) ? id : [id];
// Get the first matching route
const route = paths.reduce((acc, path) => {
if (acc) return acc;
return matchingRoutes.find((route) => route.id === path);
}, undefined as RouteMatch | undefined);
return route;
}
@@ -124,3 +129,12 @@ export function titleCase(original: string): string {
export function dateDifference(date1: Date, date2: Date) {
return Math.abs(date1.getTime() - date2.getTime());
}
export const environmentShortName = (slug: string) =>
slug === DEV_ENVIRONMENT ? "Dev" : "Live";
// Takes an api key (either trigger_live_xxxx or trigger_development_xxxx) and returns trigger_live_********
export const obfuscateApiKey = (apiKey: string) => {
const [prefix, slug, secretPart] = apiKey.split("_");
return `${prefix}_${slug}_${"*".repeat(secretPart.length)}`;
};
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Template" ADD COLUMN "isLive" BOOLEAN NOT NULL DEFAULT true;
+7 -5
View File
@@ -342,7 +342,7 @@ model IntegrationRequest {
params Json
endpoint String
version String @default("1")
version String @default("1")
externalService ExternalService @relation(fields: [externalServiceId], references: [id], onDelete: Cascade, onUpdate: Cascade)
externalServiceId String
@@ -485,10 +485,10 @@ model WorkflowRunStep {
idempotencyKey String
ts String
type WorkflowRunStepType
input Json?
output Json?
context Json
type WorkflowRunStepType
input Json?
output Json?
context Json
displayProperties Json?
createdAt DateTime @default(now())
@@ -625,6 +625,8 @@ model Template {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isLive Boolean @default(true)
organizationTemplates OrganizationTemplate[]
}
+52 -20
View File
@@ -22,18 +22,32 @@ async function readTemplateDocsFile(slug: string) {
async function seed() {
console.log(`Database has been seeded. 🌱`);
const basicStarter = {
repositoryUrl: "https://github.com/triggerdotdev/basic-starter",
const blankStarter = {
repositoryUrl: "https://github.com/triggerdotdev/blank-starter",
imageUrl:
"https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/51a2a621-577a-4648-a087-bc5381259a00/public",
title: "A blank starter project with a simple Custom Event trigger",
shortTitle: "Basic Starter",
description: "This is a great place to start if you're new to Trigger.",
"https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/3812f076-38a2-4456-b809-2fc61dd77800/public",
title: "A blank starter ready to run your own workflow",
shortTitle: "Blank Starter",
description:
"This is a great place to start if you want to build your own workflow from scratch.",
priority: 0,
services: [],
workflowIds: ["basic-starter"],
markdownDocs: await readTemplateDocsFile("basic-starter"),
runLocalDocs: await readTemplateDocsFile("basic-starter-local"),
workflowIds: [],
markdownDocs: await readTemplateDocsFile("blank-starter"),
};
const helloWorld = {
repositoryUrl: "https://github.com/triggerdotdev/hello-world",
imageUrl:
"https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/634cfd11-e499-48ca-aaf5-b2404642f600/public",
title: "A Hello World with a simple custom event trigger",
shortTitle: "Hello World",
description:
"This is a great place to start if you're new to Trigger.dev and want to learn how to build a simple workflow.",
priority: 10,
services: [],
workflowIds: ["hello-world"],
markdownDocs: await readTemplateDocsFile("hello-world"),
};
const scheduledHealthcheck = {
@@ -44,7 +58,7 @@ async function seed() {
shortTitle: "Scheduled Healthcheck",
description:
"This will run every 5 minutes and send a Slack message if a website url returns a non-200 response.",
priority: 1,
priority: 20,
services: ["slack"],
workflowIds: ["scheduled-healthcheck"],
markdownDocs: await readTemplateDocsFile("scheduled-healthcheck"),
@@ -55,11 +69,11 @@ async function seed() {
repositoryUrl: "https://github.com/triggerdotdev/github-stars-to-slack",
imageUrl:
"https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/5b3964be-9a7b-4a7e-1837-b283e501b900/public",
title: "Slack notifications when a GitHub repo is starred",
title: "Post to Slack every time a GitHub repo is starred",
shortTitle: "GitHub stars to Slack",
description:
"When a GitHub repo is starred, post information about the user to Slack.",
priority: 1,
priority: 30,
services: ["github", "slack"],
workflowIds: ["github-stars-to-slack"],
markdownDocs: await readTemplateDocsFile("github-stars-to-slack"),
@@ -74,7 +88,7 @@ async function seed() {
shortTitle: "GitHub issues to Slack",
description:
"When a GitHub issue is created or modified, post a message and link to the issue in a specific Slack channel.",
priority: 1,
priority: 40,
services: ["github", "slack"],
workflowIds: ["github-issues-to-slack"],
markdownDocs: await readTemplateDocsFile("github-issues-to-slack"),
@@ -86,11 +100,11 @@ async function seed() {
"https://github.com/triggerdotdev/resend-welcome-drip-campaign",
imageUrl:
"https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/cce3b770-b6f9-40ef-baf3-f01c20686700/public",
title: "Send a welcome drip campaign to new users",
title: "Send an email drip campaign when a new user signs up",
shortTitle: "Resend.com drip campaign",
description:
"When a new user is created, send them a welcome drip campaign from Resend.com and react.email.",
priority: 2,
priority: 50,
services: ["resend"],
workflowIds: ["resend-welcome-drip-campaign"],
markdownDocs: await readTemplateDocsFile("resend-welcome-drip-campaign"),
@@ -99,12 +113,30 @@ async function seed() {
),
};
await prisma.template.upsert({
where: { slug: "basic-starter" },
update: basicStarter,
create: {
await prisma.template.updateMany({
where: {
slug: "basic-starter",
...basicStarter,
},
data: {
isLive: false,
},
});
await prisma.template.upsert({
where: { slug: "blank-starter" },
update: blankStarter,
create: {
slug: "blank-starter",
...blankStarter,
},
});
await prisma.template.upsert({
where: { slug: "hello-world" },
update: helloWorld,
create: {
slug: "hello-world",
...helloWorld,
},
});
@@ -1,31 +0,0 @@
## 💻 Run locally
First, in your terminal of choice, clone the repo and install dependencies:
```sh
git clone https://github.com/triggerdotdev/basic-starter.git
cd basic-starter
npm install
```
Then execute the following command to create a `.env` file with your development Trigger.dev API Key:
```sh
echo "TRIGGER_API_KEY=<APIKEY>" >> .env
```
And finally you are ready to run the process:
```sh
npm run dev
```
You should see a message output in your terminal like the following:
```
[trigger.dev] ✨ Connected and listening for events [basic-starter]
```
## 🧪 Test it
The [Basic Starter README](https://github.com/triggerdotdev/basic-starter) has more details on how to test this template.
@@ -1,32 +1,52 @@
This repo is a very simple starting point for creating your Trigger.dev workflows.
Currently this repo only has a single [customEvent](https://docs.trigger.dev/triggers/custom-events) trigger:
```ts
import { Trigger, customEvent } from "@trigger.dev/sdk";
import { z } from "zod";
new Trigger({
// Give your Trigger a stable ID
id: "basic-starter",
name: "Basic Starter",
// Trigger on a custom event, see https://docs.trigger.dev/triggers/custom-events
id: "hello-world",
name: "Template: Hello World",
// Trigger on the custom event named "your.event", see https://docs.trigger.dev/triggers/custom-events
on: customEvent({
name: "basic.starter",
// Use zod to verify event payload. See https://docs.trigger.dev/guides/zod
schema: z.object({ id: z.string() }),
name: "your.event",
}),
// The run functions gets called once per "basic.starter" event
// The run functions gets called once per "your.event" event
async run(event, ctx) {
// Call external services, add delays, and more here.
await ctx.logger.info("Hello world from inside trigger.dev");
await ctx.waitFor("waiting...", { seconds: 10 });
// Returned data will become the run "output" and is optional
return event;
await ctx.logger.info("Hello world from inside trigger.dev");
},
}).listen();
```
## 📺 Go Live
After you are happy with your campaign and deploy it live to Render.com (or some other hosting service), you can send custom events that Trigger your workflow using the [sendEvent](https://docs.trigger.dev/reference/send-event) function from the `@trigger.dev/sdk`, or simply by making requests to our [`events`](https://docs.trigger.dev/api-reference/events/sendEvent) API endpoint.
Here is an example of sending the custom event to trigger the workflow contained in this repo using `fetch`:
```ts
const event = {
name: "your.event",
payload: {
hello: "world",
},
};
const response = await fetch("https://app.trigger.dev/api/v1/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.TRIGGER_API_KEY}`,
},
body: JSON.stringify({
id: randomUUID(),
event,
}),
});
```
## ✍️ Customize
You can easily adapt this workflow to a different event relevant to your app. For example, we have a workflow that runs when a user is created and it looks like this:
@@ -57,32 +77,3 @@ new Trigger({
```
Be sure to check out more over on our [docs](https://docs.trigger.dev)
## 📺 Go Live
After you are happy with your campaign and deploy it live to Render.com (or some other hosting service), you can send custom events that Trigger your workflow using the [sendEvent](https://docs.trigger.dev/reference/send-event) function from the `@trigger.dev/sdk`, or simply by making requests to our [`events`](https://docs.trigger.dev/api-reference/events/sendEvent) API endpoint.
Here is an example of sending the custom event to trigger the workflow contained in this repo using `fetch`:
```ts
const eventId = ulid(); // Generate a unique event ID
const event = {
name: "basic.starter",
payload: {
// This should match the zod schema provided in the `customEvent.schema` option
id: "user_1234",
},
};
const response = await fetch("https://app.trigger.dev/api/v1/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.TRIGGER_API_KEY}`,
},
body: JSON.stringify({
id: eventId,
event,
}),
});
```
@@ -1,6 +1,4 @@
## ✨ Trigger.dev GitHub Issues to Slack
This repo contains a [GitHub IssueEvent](https://docs.trigger.dev/integrations/apis/github/events/issues) Trigger that will run whenever an issue action is performed in a GitHub repository:
This template contains a [GitHub IssueEvent](https://docs.trigger.dev/integrations/apis/github/events/issues) Trigger that will run whenever an issue action is performed in a GitHub repository:
```ts
import { Trigger } from "@trigger.dev/sdk";
@@ -1,4 +1,4 @@
This repo contains a [GitHub newStarEvent](https://docs.trigger.dev/integrations/apis/github/events/new-star) Trigger that will run whenever the specified repository gets a new ⭐️:
This template contains a [GitHub newStarEvent](https://docs.trigger.dev/integrations/apis/github/events/new-star) Trigger that will run whenever the specified repository gets a new ⭐️:
```ts
import { Trigger } from "@trigger.dev/sdk";
+79
View File
@@ -0,0 +1,79 @@
Currently this template only has a single [customEvent](https://docs.trigger.dev/triggers/custom-events) trigger:
```ts
import { Trigger, customEvent } from "@trigger.dev/sdk";
new Trigger({
// Give your Trigger a stable ID
id: "hello-world",
name: "Template: Hello World",
// Trigger on the custom event named "your.event", see https://docs.trigger.dev/triggers/custom-events
on: customEvent({
name: "your.event",
}),
// The run functions gets called once per "your.event" event
async run(event, ctx) {
await ctx.waitFor("waiting...", { seconds: 10 });
await ctx.logger.info("Hello world from inside trigger.dev");
},
}).listen();
```
## 📺 Go Live
After you are happy with your campaign and deploy it live to Render.com (or some other hosting service), you can send custom events that Trigger your workflow using the [sendEvent](https://docs.trigger.dev/reference/send-event) function from the `@trigger.dev/sdk`, or simply by making requests to our [`events`](https://docs.trigger.dev/api-reference/events/sendEvent) API endpoint.
Here is an example of sending the custom event to trigger the workflow contained in this repo using `fetch`:
```ts
const event = {
name: "your.event",
payload: {
hello: "world",
},
};
const response = await fetch("https://app.trigger.dev/api/v1/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.TRIGGER_API_KEY}`,
},
body: JSON.stringify({
id: randomUUID(),
event,
}),
});
```
## ✍️ Customize
You can easily adapt this workflow to a different event relevant to your app. For example, we have a workflow that runs when a user is created and it looks like this:
```ts
import { Trigger, customEvent } from "@trigger.dev/sdk";
import * as slack from "@trigger.dev/slack";
import { z } from "zod";
new Trigger({
id: "new-user",
name: "New user",
on: customEvent({
name: "user.created",
schema: z.object({ id: z.string() }),
}),
async run(event, ctx) {
const user = await prisma.user.find({
where: { id: event.id },
});
await slack.postMessage("🚨", {
channelName: "new-users",
text: `New user signed up: ${user.email}`,
});
},
}).listen();
```
Be sure to check out more over on our [docs](https://docs.trigger.dev)
@@ -1,4 +1,4 @@
This repo contains a [customEvent](https://docs.trigger.dev/triggers/custom-events) Trigger that will send an example drip email campaign using [Resend.com](https://resend.com/) and [react.email](https://react.email/)
This template contains a [customEvent](https://docs.trigger.dev/triggers/custom-events) Trigger that will send an example drip email campaign using [Resend.com](https://resend.com/) and [react.email](https://react.email/)
> Resend.com is currently in private beta, but if you signup for their waitlist, give us a shout on [our Discord](https://discord.gg/CzBqJnYq9r) and we'll help you get in.
@@ -1,6 +1,4 @@
## ✨ Trigger.dev Scheduled Healthcheck
This repo contains a [Scheduled](https://docs.trigger.dev/triggers/scheduled) Trigger that will run every 5 minutes and send a Slack message if a website url returns a non-200 response:
This template contains a [Scheduled](https://docs.trigger.dev/triggers/scheduled) Trigger that will run every 5 minutes and send a Slack message if a website url returns a non-200 response:
```ts
new Trigger({
@@ -40,15 +38,3 @@ new Trigger({
- Update the frequency (you can go as frequent as once per minute)
Be sure to check out more over on our [docs](https://docs.trigger.dev)
## 🚀 Deploy
We've made it really easy to deploy this repo to Render.com, if you don't already have a Node.js server to host your triggers.
[Render.com](https://render.com) is a super-fast way to deploy webapps and servers (think of it like a modern Heroku)
<a href="https://render.com/deploy?repo=https://github.com/triggerdotdev/scheduled-healthcheck">
<img width="144px" src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render">
</a>
> **Note** Make sure you use your "live" trigger.dev API Key when deploying to a server
+2
View File
@@ -26,6 +26,7 @@ export type WorkflowRunControllerOptions = {
organizationId: string;
isTest: boolean;
appOrigin: string;
attempt: number;
};
};
@@ -41,6 +42,7 @@ export class WorkflowRunController {
organizationId: string;
isTest: boolean;
appOrigin: string;
attempt: number;
};
#logger: Logger;
+8 -1
View File
@@ -350,7 +350,10 @@ export class TriggerServer {
}
);
this.#socket.close(4001, "Client not authenticated");
this.#socket.close(
4001,
"Could not authenticate to the server because the API key is invalid"
);
}
}
@@ -468,6 +471,10 @@ export class TriggerServer {
typeof properties["x-is-test"] === "string"
? properties["x-is-test"] === "true"
: false,
attempt:
typeof properties["x-attempt"] === "string"
? Number(properties["x-attempt"])
: 0,
},
});
+152 -152
View File
@@ -15,7 +15,7 @@ const userCreatedEvent = z.object({
const trigger = new Trigger({
id: "my-workflow",
name: "My workflow",
apiKey: "trigger_dev_zC25mKNn6c0q",
// apiKey: "<enter your API key here>",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
triggerTTL: 60 * 60 * 24,
@@ -44,159 +44,159 @@ const trigger = new Trigger({
trigger.listen();
new Trigger({
id: "smoke-test",
name: "Smoke Test",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: customEvent({
name: "smoke.test",
schema: z.object({ baz: z.string() }),
}),
run: async (event, ctx) => {
await ctx.logger.info("Inside the smoke test workflow, received event", {
event,
myDate: new Date(),
});
},
}).listen();
// new Trigger({
// id: "smoke-test",
// name: "Smoke Test",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// endpoint: "ws://localhost:8889/ws",
// logLevel: "debug",
// on: customEvent({
// name: "smoke.test",
// schema: z.object({ baz: z.string() }),
// }),
// run: async (event, ctx) => {
// await ctx.logger.info("Inside the smoke test workflow, received event", {
// event,
// myDate: new Date(),
// });
// },
// }).listen();
new Trigger({
id: "log-tests",
name: "My logs",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: customEvent({ name: "user.created", schema: z.any() }),
run: async (event, ctx) => {
await ctx.logger.info("It's been 5 minutes since the last run!");
await ctx.logger.debug("This is a debug log");
await ctx.logger.warn("This is a warning");
await ctx.logger.error("This is an error");
},
}).listen();
// new Trigger({
// id: "log-tests",
// name: "My logs",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// endpoint: "ws://localhost:8889/ws",
// logLevel: "debug",
// on: customEvent({ name: "user.created", schema: z.any() }),
// run: async (event, ctx) => {
// await ctx.logger.info("It's been 5 minutes since the last run!");
// await ctx.logger.debug("This is a debug log");
// await ctx.logger.warn("This is a warning");
// await ctx.logger.error("This is an error");
// },
// }).listen();
export const bookingPayloadSchema = z.object({
triggerEvent: z.string(),
createdAt: z.coerce.date(),
payload: z.object({
type: z.string(),
title: z.string(),
description: z.string(),
additionalNotes: z.string(),
customInputs: z.object({}),
startTime: z.coerce.date(),
endTime: z.coerce.date(),
organizer: z.object({
id: z.number(),
name: z.string(),
email: z.string(),
timeZone: z.string(),
language: z.object({ locale: z.string() }),
}),
attendees: z.array(
z.object({
email: z.string(),
name: z.string(),
timeZone: z.string(),
language: z.object({ locale: z.string() }),
})
),
location: z.string(),
destinationCalendar: z.object({
id: z.number(),
integration: z.string(),
externalId: z.string(),
userId: z.number(),
eventTypeId: z.null(),
credentialId: z.number(),
}),
hideCalendarNotes: z.boolean(),
requiresConfirmation: z.null(),
eventTypeId: z.number(),
seatsShowAttendees: z.boolean(),
uid: z.string(),
conferenceData: z.object({
createRequest: z.object({ requestId: z.string() }),
}),
videoCallData: z.object({
type: z.string(),
id: z.string(),
password: z.string(),
url: z.string(),
}),
appsStatus: z.array(
z.object({
appName: z.string(),
type: z.string(),
success: z.number(),
failures: z.number(),
errors: z.array(z.any()).optional(),
warnings: z.array(z.any()).optional(),
})
),
eventTitle: z.string(),
eventDescription: z.null(),
price: z.number(),
currency: z.string(),
length: z.number(),
bookingId: z.number(),
metadata: z.object({ videoCallUrl: z.string() }),
status: z.string(),
}),
});
// export const bookingPayloadSchema = z.object({
// triggerEvent: z.string(),
// createdAt: z.coerce.date(),
// payload: z.object({
// type: z.string(),
// title: z.string(),
// description: z.string(),
// additionalNotes: z.string(),
// customInputs: z.object({}),
// startTime: z.coerce.date(),
// endTime: z.coerce.date(),
// organizer: z.object({
// id: z.number(),
// name: z.string(),
// email: z.string(),
// timeZone: z.string(),
// language: z.object({ locale: z.string() }),
// }),
// attendees: z.array(
// z.object({
// email: z.string(),
// name: z.string(),
// timeZone: z.string(),
// language: z.object({ locale: z.string() }),
// })
// ),
// location: z.string(),
// destinationCalendar: z.object({
// id: z.number(),
// integration: z.string(),
// externalId: z.string(),
// userId: z.number(),
// eventTypeId: z.null(),
// credentialId: z.number(),
// }),
// hideCalendarNotes: z.boolean(),
// requiresConfirmation: z.null(),
// eventTypeId: z.number(),
// seatsShowAttendees: z.boolean(),
// uid: z.string(),
// conferenceData: z.object({
// createRequest: z.object({ requestId: z.string() }),
// }),
// videoCallData: z.object({
// type: z.string(),
// id: z.string(),
// password: z.string(),
// url: z.string(),
// }),
// appsStatus: z.array(
// z.object({
// appName: z.string(),
// type: z.string(),
// success: z.number(),
// failures: z.number(),
// errors: z.array(z.any()).optional(),
// warnings: z.array(z.any()).optional(),
// })
// ),
// eventTitle: z.string(),
// eventDescription: z.null(),
// price: z.number(),
// currency: z.string(),
// length: z.number(),
// bookingId: z.number(),
// metadata: z.object({ videoCallUrl: z.string() }),
// status: z.string(),
// }),
// });
new Trigger({
id: "calcom-booking-custom-event",
name: "Cal.com booking custom event",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
triggerTTL: 60 * 60 * 24,
on: customEvent({ name: "calcom.booking", schema: bookingPayloadSchema }),
run: async (event, ctx) => {
return event;
},
}).listen();
// new Trigger({
// id: "calcom-booking-custom-event",
// name: "Cal.com booking custom event",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// endpoint: "ws://localhost:8889/ws",
// logLevel: "debug",
// triggerTTL: 60 * 60 * 24,
// on: customEvent({ name: "calcom.booking", schema: bookingPayloadSchema }),
// run: async (event, ctx) => {
// return event;
// },
// }).listen();
new Trigger({
id: "testing-schedule-test-events",
name: "Testing scheduled test payloads",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
triggerTTL: 60 * 60 * 24,
on: scheduleEvent({
rateOf: { hours: 1 },
}),
run: async (event, ctx) => {
return event;
},
}).listen();
// new Trigger({
// id: "testing-schedule-test-events",
// name: "Testing scheduled test payloads",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// endpoint: "ws://localhost:8889/ws",
// logLevel: "debug",
// triggerTTL: 60 * 60 * 24,
// on: scheduleEvent({
// rateOf: { hours: 1 },
// }),
// run: async (event, ctx) => {
// return event;
// },
// }).listen();
new Trigger({
id: "smoke-test-webhook-schema-test",
name: "Smoke Test Webhook Schema Test",
apiKey: "trigger_dev_zC25mKNn6c0q",
endpoint: "ws://localhost:8889/ws",
logLevel: "debug",
on: webhookEvent({
service: "cal.com",
eventName: "BOOKING_CREATED",
filter: {
triggerEvent: ["BOOKING_CREATED"],
},
schema: bookingPayloadSchema,
verifyPayload: {
enabled: true,
header: "X-Cal-Signature-256",
},
}),
run: async (event, ctx) => {
await ctx.logger.info("Received a cal.com booking", {
event,
wallTime: new Date(),
});
},
}).listen();
// new Trigger({
// id: "smoke-test-webhook-schema-test",
// name: "Smoke Test Webhook Schema Test",
// apiKey: "trigger_dev_zC25mKNn6c0q",
// endpoint: "ws://localhost:8889/ws",
// logLevel: "debug",
// on: webhookEvent({
// service: "cal.com",
// eventName: "BOOKING_CREATED",
// filter: {
// triggerEvent: ["BOOKING_CREATED"],
// },
// schema: bookingPayloadSchema,
// verifyPayload: {
// enabled: true,
// header: "X-Cal-Signature-256",
// },
// }),
// run: async (event, ctx) => {
// await ctx.logger.info("Received a cal.com booking", {
// event,
// wallTime: new Date(),
// });
// },
// }).listen();
@@ -1,5 +1,51 @@
# @trigger.dev/airtable
## 0.1.25
### Patch Changes
- Updated dependencies [c72120ea]
- @trigger.dev/sdk@0.2.18
## 0.1.24
### Patch Changes
- Updated dependencies [3a2cf0dd]
- @trigger.dev/sdk@0.2.17
## 0.1.23
### Patch Changes
- Updated dependencies [ee20f921]
- Updated dependencies [4f47d031]
- Updated dependencies [87a3bbee]
- Updated dependencies [51f9bc9d]
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16
## 0.1.23-next.3
### Patch Changes
- Updated dependencies
- @trigger.dev/sdk@0.2.16-next.3
## 0.1.23-next.2
### Patch Changes
- Updated dependencies [87a3bbee]
- @trigger.dev/sdk@0.2.16-next.2
## 0.1.23-next.1
### Patch Changes
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16-next.1
## 0.1.23-next.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/airtable",
"version": "0.1.23-next.0",
"version": "0.1.25",
"description": "The official Airtable integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -1,5 +1,51 @@
# @trigger.dev/sendgrid
## 0.1.25
### Patch Changes
- Updated dependencies [c72120ea]
- @trigger.dev/sdk@0.2.18
## 0.1.24
### Patch Changes
- Updated dependencies [3a2cf0dd]
- @trigger.dev/sdk@0.2.17
## 0.1.23
### Patch Changes
- Updated dependencies [ee20f921]
- Updated dependencies [4f47d031]
- Updated dependencies [87a3bbee]
- Updated dependencies [51f9bc9d]
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16
## 0.1.23-next.3
### Patch Changes
- Updated dependencies
- @trigger.dev/sdk@0.2.16-next.3
## 0.1.23-next.2
### Patch Changes
- Updated dependencies [87a3bbee]
- @trigger.dev/sdk@0.2.16-next.2
## 0.1.23-next.1
### Patch Changes
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16-next.1
## 0.1.23-next.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sendgrid",
"version": "0.1.23-next.0",
"version": "0.1.25",
"description": "The official SendGrid integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+46
View File
@@ -1,5 +1,51 @@
# @trigger.dev/github
## 0.1.25
### Patch Changes
- Updated dependencies [c72120ea]
- @trigger.dev/sdk@0.2.18
## 0.1.24
### Patch Changes
- Updated dependencies [3a2cf0dd]
- @trigger.dev/sdk@0.2.17
## 0.1.23
### Patch Changes
- Updated dependencies [ee20f921]
- Updated dependencies [4f47d031]
- Updated dependencies [87a3bbee]
- Updated dependencies [51f9bc9d]
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16
## 0.1.23-next.3
### Patch Changes
- Updated dependencies
- @trigger.dev/sdk@0.2.16-next.3
## 0.1.23-next.2
### Patch Changes
- Updated dependencies [87a3bbee]
- @trigger.dev/sdk@0.2.16-next.2
## 0.1.23-next.1
### Patch Changes
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16-next.1
## 0.1.23-next.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "0.1.23-next.0",
"version": "0.1.25",
"description": "The official GitHub integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+46
View File
@@ -1,5 +1,51 @@
# @trigger.dev/resend
## 0.1.25
### Patch Changes
- Updated dependencies [c72120ea]
- @trigger.dev/sdk@0.2.18
## 0.1.24
### Patch Changes
- Updated dependencies [3a2cf0dd]
- @trigger.dev/sdk@0.2.17
## 0.1.23
### Patch Changes
- Updated dependencies [ee20f921]
- Updated dependencies [4f47d031]
- Updated dependencies [87a3bbee]
- Updated dependencies [51f9bc9d]
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16
## 0.1.23-next.3
### Patch Changes
- Updated dependencies
- @trigger.dev/sdk@0.2.16-next.3
## 0.1.23-next.2
### Patch Changes
- Updated dependencies [87a3bbee]
- @trigger.dev/sdk@0.2.16-next.2
## 0.1.23-next.1
### Patch Changes
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16-next.1
## 0.1.23-next.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "0.1.23-next.0",
"version": "0.1.25",
"description": "The official resend.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+46
View File
@@ -1,5 +1,51 @@
# @trigger.dev/shopify
## 0.1.25
### Patch Changes
- Updated dependencies [c72120ea]
- @trigger.dev/sdk@0.2.18
## 0.1.24
### Patch Changes
- Updated dependencies [3a2cf0dd]
- @trigger.dev/sdk@0.2.17
## 0.1.23
### Patch Changes
- Updated dependencies [ee20f921]
- Updated dependencies [4f47d031]
- Updated dependencies [87a3bbee]
- Updated dependencies [51f9bc9d]
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16
## 0.1.23-next.3
### Patch Changes
- Updated dependencies
- @trigger.dev/sdk@0.2.16-next.3
## 0.1.23-next.2
### Patch Changes
- Updated dependencies [87a3bbee]
- @trigger.dev/sdk@0.2.16-next.2
## 0.1.23-next.1
### Patch Changes
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16-next.1
## 0.1.23-next.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/shopify",
"version": "0.1.23-next.0",
"version": "0.1.25",
"description": "The official Shopify integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+46
View File
@@ -1,5 +1,51 @@
# @trigger.dev/slack
## 0.1.25
### Patch Changes
- Updated dependencies [c72120ea]
- @trigger.dev/sdk@0.2.18
## 0.1.24
### Patch Changes
- Updated dependencies [3a2cf0dd]
- @trigger.dev/sdk@0.2.17
## 0.1.23
### Patch Changes
- Updated dependencies [ee20f921]
- Updated dependencies [4f47d031]
- Updated dependencies [87a3bbee]
- Updated dependencies [51f9bc9d]
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16
## 0.1.23-next.3
### Patch Changes
- Updated dependencies
- @trigger.dev/sdk@0.2.16-next.3
## 0.1.23-next.2
### Patch Changes
- Updated dependencies [87a3bbee]
- @trigger.dev/sdk@0.2.16-next.2
## 0.1.23-next.1
### Patch Changes
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16-next.1
## 0.1.23-next.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/slack",
"version": "0.1.23-next.0",
"version": "0.1.25",
"description": "The official Slack integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+46
View File
@@ -1,5 +1,51 @@
# @trigger.dev/whatsapp
## 0.1.23
### Patch Changes
- Updated dependencies [c72120ea]
- @trigger.dev/sdk@0.2.18
## 0.1.22
### Patch Changes
- Updated dependencies [3a2cf0dd]
- @trigger.dev/sdk@0.2.17
## 0.1.21
### Patch Changes
- Updated dependencies [ee20f921]
- Updated dependencies [4f47d031]
- Updated dependencies [87a3bbee]
- Updated dependencies [51f9bc9d]
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16
## 0.1.21-next.3
### Patch Changes
- Updated dependencies
- @trigger.dev/sdk@0.2.16-next.3
## 0.1.21-next.2
### Patch Changes
- Updated dependencies [87a3bbee]
- @trigger.dev/sdk@0.2.16-next.2
## 0.1.21-next.1
### Patch Changes
- Updated dependencies [0932ae7d]
- @trigger.dev/sdk@0.2.16-next.1
## 0.1.21-next.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/whatsapp",
"version": "0.1.21-next.0",
"version": "0.1.23",
"description": "The official WhatsApp Business integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+6
View File
@@ -1,5 +1,11 @@
# create-trigger
## 0.2.0
### Minor Changes
- 26e69cb6: Easily scaffold out standalone trigger.dev projects using create-trigger and our templates
## 0.2.0-next.0
### Minor Changes
+2 -2
View File
@@ -11,7 +11,7 @@ To scaffold out a new project using `create-trigger`, run any of the following t
### npm
```sh
npm create trigger@latest
npx create-trigger@latest
```
### yarn
@@ -29,7 +29,7 @@ pnpm create trigger@latest
You can also specify the [template](https://app.trigger.com/templates) you want to use by passing an argument to the command, like so:
```sh
npm create trigger@latest github-stars-to-slack
npx create-trigger@latest github-stars-to-slack
```
## Advanced Usage
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-trigger",
"version": "0.2.0-next.0",
"version": "0.2.0",
"description": "The Trigger.dev CLI to easily create and manage a Trigger.dev project",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+20 -4
View File
@@ -230,9 +230,14 @@ const promptApiKey = async (): Promise<string | undefined> => {
message: "Enter your development API key (optional)",
default: undefined,
validate: (input) => {
// Make sure they enter something like trigger_development_********
if (input && !input.startsWith("trigger_development_")) {
return "Please enter a valid API key (e.g. trigger_development_********) or leave blank to skip";
// Make sure they enter something like trigger_development_******** or trigger_dev_********
if (input) {
if (
!input.startsWith("trigger_development_") ||
!input.startsWith("trigger_dev_")
) {
return "Please enter a valid development API key or leave blank to skip";
}
}
return true;
@@ -241,7 +246,9 @@ const promptApiKey = async (): Promise<string | undefined> => {
if (apiKey) {
logger.success(
`Fantastic! We'll save the API key (trigger_development_********) in the .env file.`
`Fantastic! We'll save the API key (${obfuscateApiKey(
apiKey
)}) in the .env file.`
);
}
@@ -293,3 +300,12 @@ const promptInstall = async (): Promise<boolean> => {
return install;
};
export const obfuscateApiKey = (apiKey: string) => {
const [prefix, slug, secretPart] = apiKey.split("_") as [
string,
string,
string
];
return `${prefix}_${slug}_${"*".repeat(secretPart.length)}`;
};
+1 -1
View File
@@ -2,8 +2,8 @@
const logLevels = [
"disabled",
"log",
"error",
"log",
"warn",
"info",
"debug",
@@ -20,6 +20,7 @@ export const HostRPCSchema = {
apiKey: z.string(),
isTest: z.boolean().default(false),
appOrigin: z.string(),
attempt: z.number().optional(),
}),
}),
response: z.boolean(),
@@ -9,6 +9,7 @@ const Catalog = {
"x-ttl": z.coerce.number().optional(),
"x-is-test": z.string().default("false"),
"x-app-origin": z.string().default("https://app.trigger.dev"),
"x-attempt": z.string().optional(),
}),
},
};
+40
View File
@@ -1,5 +1,45 @@
# @trigger.dev/sdk
## 0.2.18
### Patch Changes
- c72120ea: Removed accidental log statement
## 0.2.17
### Patch Changes
- 3a2cf0dd: Fixed the missing error message when logging invalid API key and improved the error message
## 0.2.16
### Patch Changes
- ee20f921: Make the schema an optional param for customEvent and webhookEvent
- 4f47d031: Give a better error message when the API key is invalid
- 87a3bbee: Added a more helpful error message when missing an API key
- 51f9bc9d: Added handly links to the dashboard in log feedback
- 0932ae7d: Log out when a run first starts as well
## 0.2.16-next.3
### Patch Changes
- Give a better error message when the API key is invalid
## 0.2.16-next.2
### Patch Changes
- 87a3bbee: Added a more helpful error message when missing an API key
## 0.2.16-next.1
### Patch Changes
- 0932ae7d: Log out when a run first starts as well
## 0.2.16-next.0
### Patch Changes
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sdk",
"version": "0.2.16-next.0",
"version": "0.2.18",
"description": "trigger.dev Node.JS SDK",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -37,8 +37,10 @@
"build:tsup": "tsup --dts-resolve"
},
"dependencies": {
"chalk": "^5.2.0",
"debug": "^4.3.4",
"evt": "^2.4.13",
"get-caller-file": "^2.0.5",
"node-fetch": "2.6.x",
"slug": "^6.0.0",
"terminal-link": "^3.0.0",
+19 -5
View File
@@ -16,6 +16,7 @@ import { Trigger, TriggerOptions } from "./trigger";
import { TriggerContext, TriggerFetch } from "./types";
import { generateErrorMessage, ErrorMessageOptions } from "zod-error";
import terminalLink from "terminal-link";
import chalk from "chalk";
const zodErrorMessageOptions: ErrorMessageOptions = {
delimiter: {
@@ -180,11 +181,11 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
return;
}
this.#logger.error(`🚩 Could not connect to trigger.dev (code ${code})`);
if (reason) {
this.#logger.error("Reason:", reason);
}
this.#logger.error(
`${chalk.red("error")} Could not connect to trigger.dev${
reason ? `: ${reason}` : `(code ${code})`
}`
);
// If #isConnected is already false, that means we are already trying to reconnect
if (!this.#isConnected) return;
@@ -626,6 +627,19 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
() => {
this.#logger.debug("Running trigger...");
if (
typeof data.meta.attempt === "number" &&
data.meta.attempt === 0
) {
this.#logger.log(
`Run ${data.id} started 👉 ${terminalLink(
"View on dashboard",
`${this.#registerResponse!.url}/runs/${data.id}`,
{ fallback: (text, url) => `${text}: (${url})` }
)}`
);
}
serverRPC
.send("START_WORKFLOW_RUN", {
runId: data.id,
+57
View File
@@ -1,9 +1,11 @@
import { TriggerClient } from "../client";
import { LogLevel } from "internal-bridge";
import { TriggerEvent } from "../events";
import chalk from "chalk";
import type { TriggerContext } from "../types";
import { z } from "zod";
import terminalLink from "terminal-link";
export type TriggerOptions<TSchema extends z.ZodTypeAny> = {
id: string;
@@ -31,6 +33,44 @@ export class Trigger<TSchema extends z.ZodTypeAny> {
}
async listen() {
const apiKey = this.#getApiKey();
if (apiKey.status === "invalid") {
console.log(
`${chalk.red("Trigger.dev error")}: ${chalk.bold(
this.id
)} is has an invalid API key ("${chalk.italic(
apiKey.apiKey
)}"), please set the TRIGGER_API_KEY environment variable or pass the apiKey option to a valid value. ${terminalLink(
"Get your API key here",
"https://app.trigger.dev",
{
fallback(text, url) {
return `${text} 👉 ${url}`;
},
}
)}`
);
return;
} else if (apiKey.status === "missing") {
console.log(
`${chalk.red("Trigger.dev error")}: ${chalk.bold(
this.id
)} is missing an API key, please set the TRIGGER_API_KEY environment variable or pass the apiKey option to the Trigger constructor. ${terminalLink(
"Get your API key here",
"https://app.trigger.dev",
{
fallback(text, url) {
return `${text} 👉 ${url}`;
},
}
)}`
);
return;
}
if (!this.#client) {
this.#client = new TriggerClient(this, this.options);
}
@@ -53,4 +93,21 @@ export class Trigger<TSchema extends z.ZodTypeAny> {
get on() {
return this.options.on;
}
#getApiKey() {
const apiKey = this.options.apiKey ?? process.env.TRIGGER_API_KEY;
if (!apiKey) {
return { status: "missing" as const };
}
// Validate the api_key format (should be trigger_{env}_XXXXX)
const isValid = apiKey.match(/^trigger_[a-z]+_[a-zA-Z0-9]+$/);
if (!isValid) {
return { status: "invalid" as const, apiKey };
}
return { status: "valid" as const, apiKey };
}
}
+4
View File
@@ -1187,8 +1187,10 @@ importers:
'@types/slug': ^5.0.3
'@types/uuid': ^9.0.0
'@types/ws': ^8.5.3
chalk: ^5.2.0
debug: ^4.3.4
evt: ^2.4.13
get-caller-file: ^2.0.5
internal-bridge: workspace:*
node-fetch: 2.6.x
rimraf: ^3.0.2
@@ -1203,8 +1205,10 @@ importers:
zod-error: ^1.1.0
zod-to-json-schema: ^3.20.2
dependencies:
chalk: 5.2.0
debug: 4.3.4
evt: 2.4.13
get-caller-file: 2.0.5
node-fetch: 2.6.7
slug: 6.1.0
terminal-link: 3.0.0