Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f7b7ff6d6 | |||
| 603ffbc426 | |||
| beef7769c6 | |||
| e9088cf08c | |||
| 390bed0b9a | |||
| e3e3dab525 | |||
| b884a0ab53 | |||
| 871d6e1d52 | |||
| 7daa82fc7a | |||
| 9dd7fb2748 | |||
| c246578a81 | |||
| d395b95762 | |||
| f3bda59617 | |||
| ef8b52c20f | |||
| 2bedc779b9 | |||
| a5b5912270 | |||
| 275318d2df | |||
| 8d48ebdee8 | |||
| 7ef0fe79b5 | |||
| 921957958e | |||
| 73721fc55c | |||
| 4fcec3bd0d | |||
| 7f4b6b9974 | |||
| 950ab24e7f | |||
| c4da1b4a9a | |||
| 24debf0cee | |||
| 645643ea41 |
@@ -3,7 +3,10 @@ SESSION_SECRET=abcdef1234
|
||||
MAGIC_LINK_SECRET=abcdef1234
|
||||
ENCRYPTION_KEY=ae13021afef0819c3a307ad487071c06 # Must be a random 16 byte hex string. You can generate an encryption key by running `openssl rand -hex 16` in your terminal
|
||||
LOGIN_ORIGIN=http://localhost:3030
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres?schema=public
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres?schema=public
|
||||
# This sets the URL used for direct connections to the database and should only be needed in limited circumstances
|
||||
# See: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields:~:text=the%20shadow%20database.-,directUrl,-No
|
||||
DIRECT_URL=${DATABASE_URL}
|
||||
REMIX_APP_PORT=3030
|
||||
APP_ENV=development
|
||||
APP_ORIGIN=http://localhost:3030
|
||||
|
||||
@@ -122,7 +122,6 @@ jobs:
|
||||
# Setup environment variables
|
||||
cp ./.env.example ./.env
|
||||
cp ./examples/nextjs-test/.env.example ./examples/nextjs-test/.env.local
|
||||
cp ./packages/database/.env.example ./packages/database/.env
|
||||
|
||||
# Build packages
|
||||
pnpm run build --filter @examples/nextjs-test^...
|
||||
|
||||
@@ -37,11 +37,11 @@ branch are tagged into a release monthly.
|
||||
```
|
||||
pnpm i
|
||||
```
|
||||
4. Create your `.env` files
|
||||
4. Create your `.env` file
|
||||
```
|
||||
cp .env.example .env && cp packages/database/.env.example packages/database/.env
|
||||
cp .env.example .env
|
||||
```
|
||||
5. Open the root `.env` file and generate a new value for `ENCRYPTION_KEY`:
|
||||
5. Open it and generate a new value for `ENCRYPTION_KEY`:
|
||||
|
||||
`ENCRYPTION_KEY` is used to two-way encrypt OAuth access tokens and so you'll probably want to actually generate a unique value, and it must be a random 16 byte hex string. You can generate one with the following command:
|
||||
|
||||
@@ -178,7 +178,6 @@ To run the end-to-end tests, follow the steps below:
|
||||
```sh
|
||||
cp ./.env.example ./.env
|
||||
cp ./examples/nextjs-test/.env.example ./examples/nextjs-test/.env.local
|
||||
cp ./packages/database/.env.example ./packages/database/.env
|
||||
```
|
||||
|
||||
2. Set up dependencies
|
||||
@@ -190,6 +189,9 @@ pnpm --filter @trigger.dev/database generate
|
||||
|
||||
# Move trigger-cli bin to correct place
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# Install playwrite browsers (ONE TIME ONLY)
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
3. Set up the database
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
export function OneTreeIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_4914_58397)">
|
||||
<g filter="url(#filter0_d_4914_58397)">
|
||||
<path
|
||||
d="M9 14H11C11 14 11 15 11 17C11 19 11.5 20 11.5 20H8.5C8.5 20 9 19 9 17C9 15 9 14 9 14Z"
|
||||
fill="url(#paint0_linear_4914_58397)"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d_4914_58397)">
|
||||
<circle cx="5.5" cy="8.5" r="3.5" fill="currentColor" />
|
||||
</g>
|
||||
<g filter="url(#filter2_d_4914_58397)">
|
||||
<circle cx="8" cy="12" r="3" fill="currentColor" />
|
||||
</g>
|
||||
<circle cx="13" cy="11" r="4" fill="currentColor" />
|
||||
<circle cx="15.5" cy="7.5" r="2.5" fill="currentColor" />
|
||||
<g filter="url(#filter3_d_4914_58397)">
|
||||
<circle cx="9" cy="6" r="4" fill="currentColor" />
|
||||
</g>
|
||||
<circle cx="12.5" cy="5.5" r="3.5" fill="currentColor" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_4914_58397"
|
||||
x="4.5"
|
||||
y="14"
|
||||
width="11"
|
||||
height="14"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4914_58397" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4914_58397"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_4914_58397"
|
||||
x="-2"
|
||||
y="5"
|
||||
width="15"
|
||||
height="15"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4914_58397" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4914_58397"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_4914_58397"
|
||||
x="1"
|
||||
y="9"
|
||||
width="14"
|
||||
height="14"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4914_58397" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4914_58397"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3_d_4914_58397"
|
||||
x="1"
|
||||
y="-2"
|
||||
width="16"
|
||||
height="16"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4914_58397" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4914_58397"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4914_58397"
|
||||
x1="10"
|
||||
y1="14"
|
||||
x2="10"
|
||||
y2="20"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#402309" />
|
||||
<stop offset="1" stopColor="#713F12" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_4914_58397">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
export function SaplingIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_4906_23816)">
|
||||
<path
|
||||
d="M4.39538 17.7403C7.25332 16.3563 13.2867 16.4291 15.5096 17.7403C16.6666 18.4227 17.2938 19.0069 17.6294 19.4103C17.8542 19.6806 17.651 20 17.2994 20H2.34215C2.151 20 1.9749 19.8835 2.01519 19.6967C2.09465 19.3281 2.53308 18.6421 4.39538 17.7403Z"
|
||||
fill="url(#paint0_linear_4906_23816)"
|
||||
/>
|
||||
<g filter="url(#filter0_d_4906_23816)">
|
||||
<path
|
||||
d="M9.37762 9.80872C7.43627 7.16364 3.49903 8.13783 2.43836 8.66818C2.26159 9.02174 3.32907 8.80396 4.08851 10.8279C4.84794 12.8519 7.63503 13.1864 8.74087 12.0806C9.27121 11.5502 9.69141 10.2363 9.37762 9.80872Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d_4906_23816)">
|
||||
<path
|
||||
d="M9.03577 9.94522L9.99862 9.80223C9.99862 9.80223 10.0387 16.2908 10.9485 16.8179C11.8582 17.345 8.47933 17.1309 8.92618 16.8179C9.37303 16.505 9.03577 9.94522 9.03577 9.94522Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_4906_23816)">
|
||||
<path
|
||||
d="M9.36235 10.1127C9.36235 5.78131 13.3229 4.41473 15.9395 4.00389C16.1892 3.96468 16.3818 4.22916 16.2961 4.46698C16.0763 5.07608 15.7914 6.09975 15.6717 7.46762C15.5019 9.40897 14.8971 10.3478 14.3613 10.962C13.3664 12.1026 9.36235 12.2341 9.36235 10.1127Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_4906_23816"
|
||||
x="-1.58107"
|
||||
y="8.04684"
|
||||
width="15.0664"
|
||||
height="12.6571"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4906_23816" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4906_23816"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_4906_23816"
|
||||
x="4.88525"
|
||||
y="9.80223"
|
||||
width="10.2195"
|
||||
height="15.3357"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4906_23816" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4906_23816"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_4906_23816"
|
||||
x="5.36235"
|
||||
y="4"
|
||||
width="14.9544"
|
||||
height="15.7756"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4906_23816" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4906_23816"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4906_23816"
|
||||
x1="10"
|
||||
y1="16.7294"
|
||||
x2="10"
|
||||
y2="20"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.291667" stopColor="#402309" />
|
||||
<stop offset="1" stopColor="#713F12" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_4906_23816">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
export function TwoTreesIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_4906_58417)">
|
||||
<path
|
||||
d="M10 20V17"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.7239 16.3L14.8274 13H15.1171C15.3061 12.9973 15.4901 12.9371 15.6464 12.8271C15.8027 12.717 15.9243 12.5619 15.9963 12.3808C16.0683 12.1998 16.0873 12.001 16.0512 11.8088C16.015 11.6167 15.9252 11.4398 15.7929 11.3L12.8965 8H13.0896C13.2868 8.01843 13.4847 7.97361 13.6565 7.87162C13.8282 7.76963 13.9656 7.6154 14.0499 7.42984C14.1342 7.24428 14.1614 7.03636 14.1278 6.83425C14.0942 6.63215 14.0014 6.44564 13.862 6.3L10 2L7.29699 5.0096C8.25755 5.0792 9.16726 5.48607 9.85673 6.16512C10.5124 6.81087 10.9224 7.65741 11.023 8.56163C11.6295 8.91846 12.136 9.42887 12.4856 10.0473C12.9492 10.8671 13.1066 11.8229 12.9286 12.7472C12.7507 13.6714 12.2496 14.5014 11.5168 15.0945C10.8041 15.6712 9.91568 15.9888 8.99914 15.999V18H17.0481C17.237 17.9973 17.4211 17.9371 17.5774 17.8271C17.7337 17.717 17.8553 17.5618 17.9273 17.3808C17.9992 17.1998 18.0183 17.001 17.9822 16.8088C17.946 16.6167 17.8562 16.4398 17.7239 16.3ZM7.99914 18V15.9991H6.00231V18H7.99914ZM4.97036 15.9991H5.00231L5.00231 18H2.95193C2.76296 17.9973 2.57892 17.9371 2.42263 17.8271C2.26635 17.717 2.14468 17.5618 2.07272 17.3808C2.00076 17.1998 1.98168 17.001 2.01783 16.8088C2.05398 16.6167 2.14377 16.4398 2.27609 16.3L3.01417 15.4591C3.59927 15.795 4.26325 15.9821 4.94587 15.9988C4.95403 15.999 4.9622 15.9991 4.97036 15.9991ZM3.69171 14.6872C4.08702 14.8804 4.52307 14.9881 4.97036 14.9991H8.93032C9.64375 15.0045 10.3365 14.7632 10.8877 14.3171C11.4389 13.871 11.8137 13.2485 11.9466 12.5581C12.0796 11.8678 11.9623 11.1534 11.6151 10.5395C11.268 9.9256 10.7131 9.45119 10.0472 9.19898V8.99898C10.0472 8.20331 9.7263 7.44023 9.15504 6.87761C8.58378 6.31499 7.80899 5.99891 7.00111 5.99891C6.77881 5.99891 6.55901 6.02285 6.34528 6.06926L6.13804 6.3C5.99864 6.44564 5.90584 6.63215 5.87222 6.83425C5.8386 7.03636 5.86579 7.24428 5.9501 7.42984C6.03441 7.6154 6.17176 7.76963 6.34354 7.87162C6.51533 7.97361 6.71323 8.01843 6.91043 8H7.10353L4.20706 11.3C4.07475 11.4398 3.98496 11.6167 3.94881 11.8088C3.91266 12.001 3.93174 12.1998 4.0037 12.3808C4.07566 12.5619 4.19732 12.717 4.35361 12.8271C4.5099 12.9371 4.69394 12.9973 4.88291 13H5.17255L3.69171 14.6872Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M7 20L7 13.9814"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.0472 9V9.2C10.7131 9.4522 11.268 9.92661 11.6151 10.5405C11.9623 11.1544 12.0796 11.8687 11.9466 12.5591C11.8137 13.2494 11.4389 13.8719 10.8877 14.318C10.3365 14.7641 9.64374 15.0054 8.93032 15H4.97036C4.26546 14.9827 3.58847 14.7251 3.05487 14.2712C2.52126 13.8172 2.1641 13.195 2.0443 12.5106C1.92449 11.8263 2.04946 11.1222 2.3979 10.5185C2.74634 9.91474 3.29666 9.44875 3.95499 9.2V9C3.95499 8.20435 4.27592 7.44129 4.84718 6.87868C5.41843 6.31607 6.19323 6 7.00111 6C7.80899 6 8.58378 6.31607 9.15504 6.87868C9.7263 7.44129 10.0472 8.20435 10.0472 9Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4906_58417">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 181 KiB |
@@ -20,6 +20,8 @@ import {
|
||||
} from "./primitives/Select";
|
||||
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "./primitives/Sheet";
|
||||
import { TextArea } from "./primitives/TextArea";
|
||||
import { DiscordIcon } from "@trigger.dev/companyicons";
|
||||
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
type FeedbackProps = {
|
||||
button: ReactNode;
|
||||
@@ -55,14 +57,15 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
|
||||
<SheetContent size="sm">
|
||||
<SheetHeader className="justify-between">Help & feedback</SheetHeader>
|
||||
<SheetBody>
|
||||
<Paragraph variant="small" className="mb-4">
|
||||
Use this form to ask for help or give us feedback. We read every message and will get
|
||||
<DiscordBanner />
|
||||
<Paragraph variant="small" className="mb-4 border-t border-slate-800 pt-3">
|
||||
Or use this form to ask for help or give us feedback. We read every message and will get
|
||||
back to you as soon as we can.
|
||||
</Paragraph>
|
||||
<Form method="post" action="/resources/feedback" {...form.props}>
|
||||
<Fieldset>
|
||||
<Fieldset className="max-w-full">
|
||||
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
|
||||
<InputGroup>
|
||||
<InputGroup className="max-w-full">
|
||||
<Label>How can we help?</Label>
|
||||
<SelectGroup>
|
||||
<Select {...conform.input(feedbackType)} defaultValue={defaultValue}>
|
||||
@@ -80,14 +83,14 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
|
||||
</SelectGroup>
|
||||
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroup className="max-w-full">
|
||||
<Label>Message</Label>
|
||||
<TextArea {...conform.textarea(message)} />
|
||||
<FormError id={message.errorId}>{message.error}</FormError>
|
||||
</InputGroup>
|
||||
<FormError>{form.error}</FormError>
|
||||
<FormButtons
|
||||
className="max-w-md"
|
||||
className="w-full"
|
||||
confirmButton={
|
||||
<Button type="submit" variant="primary/medium">
|
||||
Send
|
||||
@@ -101,3 +104,28 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
||||
function DiscordBanner() {
|
||||
return (
|
||||
<a
|
||||
href="https://discord.gg/nkqV9xBYWy"
|
||||
target="_blank"
|
||||
className="group mb-4 flex w-full items-center justify-between rounded-md border border-slate-600 bg-gradient-to-br from-blue-400/30 to-indigo-400/50 p-4 transition hover:border-indigo-400"
|
||||
>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<DiscordIcon className="h-8 w-8" />
|
||||
<h2 className="font-title text-2xl text-bright transition group-hover:text-white">
|
||||
Join the Trigger.dev
|
||||
<br />
|
||||
Discord community
|
||||
</h2>
|
||||
<Paragraph variant="small">
|
||||
Get help or answer questions from the Trigger.dev community.
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className="h-full">
|
||||
<ChevronRightIcon className="h-5 w-5 text-slate-400 transition group-hover:translate-x-1 group-hover:text-indigo-400" />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Link } from "@remix-run/react";
|
||||
import { ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
|
||||
import { Link, useRevalidator } from "@remix-run/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEventSource } from "remix-utils";
|
||||
import invariant from "tiny-invariant";
|
||||
import gradientBackground from "~/assets/images/gradient-background.png";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { StepNumber } from "~/components/primitives/StepNumber";
|
||||
import { useAppOrigin } from "~/hooks/useAppOrigin";
|
||||
@@ -7,7 +12,8 @@ import { useJob } from "~/hooks/useJob";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { IntegrationIcon } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route";
|
||||
import { jobTestPath } from "~/utils/pathBuilder";
|
||||
import { jobTestPath, projectStreamingPath } from "~/utils/pathBuilder";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { CodeBlock } from "../code/CodeBlock";
|
||||
import { InlineCode } from "../code/InlineCode";
|
||||
import { EnvironmentLabel } from "../environments/EnvironmentLabel";
|
||||
@@ -23,132 +29,217 @@ import {
|
||||
ClientTabsTrigger,
|
||||
} from "../primitives/ClientTabs";
|
||||
import { ClipboardField } from "../primitives/ClipboardField";
|
||||
import { Header2 } from "../primitives/Headers";
|
||||
import { Header1, Header2 } from "../primitives/Headers";
|
||||
import { NamedIcon } from "../primitives/NamedIcon";
|
||||
import { RadioGroup, RadioGroupItem } from "../primitives/RadioButton";
|
||||
import { TextLink } from "../primitives/TextLink";
|
||||
import integrationButton from "./integration-button.png";
|
||||
import selectEnvironment from "./select-environment.png";
|
||||
import selectExample from "./select-example.png";
|
||||
|
||||
type SelectionChoices = "use-existing-project" | "create-new-next-app";
|
||||
|
||||
export function HowToSetupYourProject() {
|
||||
const project = useProject();
|
||||
const devEnvironment = useDevEnvironment();
|
||||
const appOrigin = useAppOrigin();
|
||||
return (
|
||||
<>
|
||||
<StepNumber stepNumber="1" title="Run the CLI 'init' command in a Next.js project" />
|
||||
<StepContentContainer>
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`npx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`npx @trigger.dev/cli@latest init -k ${devEnvironment?.apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`pnpm dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`pnpm dlx @trigger.dev/cli@latest init -k ${devEnvironment?.apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`yarn dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`yarn dlx @trigger.dev/cli@latest init -k ${devEnvironment?.apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
|
||||
<Paragraph spacing>
|
||||
You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple
|
||||
example Job in <InlineCode>examples.ts</InlineCode> to help you get started.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Run your Next.js app" />
|
||||
<StepContentContainer>
|
||||
<Paragraph>Ensure your app is running locally.</Paragraph>
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`npm run dev`} />
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`pnpm run dev`} />
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`yarn run dev`} />
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="3" title="Run the CLI 'dev' command" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
In a <strong className="text-bright">separate terminal window or tab</strong> run:
|
||||
</Paragraph>
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm dlx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn dlx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
<Paragraph spacing variant="small">
|
||||
If you’re not running on port 3000 you can specify the port by adding{" "}
|
||||
<InlineCode>--port 3001</InlineCode> to the end.
|
||||
</Paragraph>
|
||||
<Paragraph spacing variant="small">
|
||||
You should leave the <InlineCode>dev</InlineCode> command running when you're developing.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="4" title="Check for Jobs" />
|
||||
<StepContentContainer>
|
||||
<Paragraph>
|
||||
Once you've run the CLI command, click Refresh to view your example Job in the list.
|
||||
</Paragraph>
|
||||
<Button
|
||||
variant="primary/medium"
|
||||
className="mt-4"
|
||||
LeadingIcon="refresh"
|
||||
onClick={() => window.location.reload()}
|
||||
const [selectedValue, setSelectedValue] = useState<SelectionChoices | null>(null);
|
||||
|
||||
invariant(devEnvironment, "devEnvironment is required");
|
||||
|
||||
const revalidator = useRevalidator();
|
||||
const events = useEventSource(projectStreamingPath(project.id), {
|
||||
event: "message",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (events !== null) {
|
||||
// This uses https://www.npmjs.com/package/canvas-confetti
|
||||
if ("confetti" in window && typeof window.confetti !== "undefined") {
|
||||
var duration = 2.5 * 1000;
|
||||
var end = Date.now() + duration;
|
||||
|
||||
(function frame() {
|
||||
// launch a few confetti from the left edge
|
||||
// @ts-ignore
|
||||
window.confetti({
|
||||
particleCount: 7,
|
||||
angle: 60,
|
||||
spread: 55,
|
||||
origin: { x: 0 },
|
||||
});
|
||||
// and launch a few from the right edge
|
||||
// @ts-ignore
|
||||
window.confetti({
|
||||
particleCount: 7,
|
||||
angle: 120,
|
||||
spread: 55,
|
||||
origin: { x: 1 },
|
||||
});
|
||||
|
||||
// keep going until we are out of time
|
||||
if (Date.now() < end) {
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
revalidator.revalidate();
|
||||
}
|
||||
// WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
|
||||
}, [events]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div
|
||||
className="-ml-4 -mt-4 h-full w-[calc(100%+32px)] bg-cover bg-no-repeat pt-20"
|
||||
style={{ backgroundImage: `url("${gradientBackground}")` }}
|
||||
>
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<div className="flex items-center justify-between">
|
||||
<Header1 spacing className="text-bright">
|
||||
Get setup in {selectedValue === "create-new-next-app" ? "5" : "2"} minutes
|
||||
</Header1>
|
||||
<Feedback
|
||||
button={
|
||||
<Button variant="secondary/small" LeadingIcon={ChatBubbleLeftRightIcon}>
|
||||
I'm stuck!
|
||||
</Button>
|
||||
}
|
||||
defaultValue="help"
|
||||
/>
|
||||
</div>
|
||||
<RadioGroup
|
||||
className="mb-4 flex gap-x-2"
|
||||
onValueChange={(value) => setSelectedValue(value as SelectionChoices)}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</StepContentContainer>
|
||||
</>
|
||||
<RadioGroupItem
|
||||
label="Use an existing Next.js project"
|
||||
description="Use Trigger.dev in an existing Next.js project in less than 2 mins."
|
||||
value="use-existing-project"
|
||||
checked={selectedValue === "use-existing-project"}
|
||||
variant="icon"
|
||||
data-action="use-existing-project"
|
||||
icon={<NamedIcon className="h-12 w-12 text-green-600" name={"tree"} />}
|
||||
/>
|
||||
<RadioGroupItem
|
||||
label="Create a new Next.js project"
|
||||
description="This is the quickest way to try out Trigger.dev in a new Next.js project and takes 5 mins."
|
||||
value="create-new-next-app"
|
||||
checked={selectedValue === "create-new-next-app"}
|
||||
variant="icon"
|
||||
data-action="create-new-next-app"
|
||||
icon={<NamedIcon className="h-8 w-8 text-green-600" name={"sapling"} />}
|
||||
/>
|
||||
</RadioGroup>
|
||||
{selectedValue && (
|
||||
<>
|
||||
{selectedValue === "create-new-next-app" ? (
|
||||
<>
|
||||
<StepNumber stepNumber="1" title="Create a new Next.js project" />
|
||||
<StepContentContainer>
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npx create-next-app@latest`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm create next-app`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn create next-app`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
|
||||
<Paragraph spacing variant="small">
|
||||
Trigger.dev works with either the Pages or App Router configuration.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Navigate to your new Next.js project" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
You have now created a new Next.js project. Let’s <InlineCode>cd</InlineCode>{" "}
|
||||
into it using the project name you just provided:
|
||||
</Paragraph>
|
||||
<ClipboardField
|
||||
value={"cd [replace with your project name]"}
|
||||
variant={"primary/medium"}
|
||||
></ClipboardField>
|
||||
</StepContentContainer>
|
||||
<StepNumber
|
||||
stepNumber="3"
|
||||
title="Run the CLI 'init' command in your new Next.js project"
|
||||
/>
|
||||
<StepContentContainer>
|
||||
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
|
||||
<Paragraph spacing variant="small">
|
||||
You’ll notice a new folder in your project called 'jobs'. We’ve added a very
|
||||
simple example Job in <InlineCode variant="extra-small">examples.ts</InlineCode>{" "}
|
||||
to help you get started.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="4" title="Run your Next.js app" />
|
||||
<StepContentContainer>
|
||||
<NextDevCommand />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="5" title="Run the CLI 'dev' command" />
|
||||
<StepContentContainer>
|
||||
<TriggerDevStep />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="6" title="Wait for Jobs" displaySpinner />
|
||||
<StepContentContainer>
|
||||
<Paragraph>This page will automatically refresh.</Paragraph>
|
||||
</StepContentContainer>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StepNumber
|
||||
stepNumber="1"
|
||||
title="Run the CLI 'init' command in an existing Next.js project"
|
||||
/>
|
||||
<StepContentContainer>
|
||||
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
|
||||
|
||||
<Paragraph spacing variant="small">
|
||||
You’ll notice a new folder in your project called 'jobs'. We’ve added a very
|
||||
simple example Job in <InlineCode variant="extra-small">examples.ts</InlineCode>{" "}
|
||||
to help you get started.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Run your Next.js app" />
|
||||
<StepContentContainer>
|
||||
<NextDevCommand />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="3" title="Run the CLI 'dev' command" />
|
||||
<StepContentContainer>
|
||||
<TriggerDevStep />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="4" title="Wait for Jobs" displaySpinner />
|
||||
<StepContentContainer>
|
||||
<Paragraph>This page will automatically refresh.</Paragraph>
|
||||
</StepContentContainer>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -391,3 +482,112 @@ export function HowToUseApiKeysAndEndpoints() {
|
||||
function StepContentContainer({ children }: { children: React.ReactNode }) {
|
||||
return <div className="mb-6 ml-9 mt-1">{children}</div>;
|
||||
}
|
||||
|
||||
function InitCommand({ appOrigin, apiKey }: { appOrigin: string; apiKey: string }) {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`npx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`npx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`pnpm dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`pnpm dlx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`yarn dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`yarn dlx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
function NextDevCommand() {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`npm run dev`} />
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`pnpm run dev`} />
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`yarn run dev`} />
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
function TriggerDevCommand() {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm dlx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn dlx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
function TriggerDevStep() {
|
||||
return (
|
||||
<>
|
||||
<Paragraph spacing>
|
||||
In a <span className="text-amber-400">separate terminal window or tab</span> run:
|
||||
</Paragraph>
|
||||
<TriggerDevCommand />
|
||||
<Paragraph spacing variant="small">
|
||||
If you’re not running on port 3000 you can specify the port by adding{" "}
|
||||
<InlineCode variant="extra-small">--port 3001</InlineCode> to the end.
|
||||
</Paragraph>
|
||||
<Paragraph spacing variant="small">
|
||||
You should leave the <InlineCode variant="extra-small">dev</InlineCode> command running when
|
||||
you're developing.
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useProject } from "~/hooks/useProject";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { JobRunStatus } from "~/models/job.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Badge } from "../primitives/Badge";
|
||||
|
||||
export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResultsText: string }) {
|
||||
const organization = useOrganization();
|
||||
@@ -44,7 +45,10 @@ export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResul
|
||||
return (
|
||||
<TableRow
|
||||
key={job.id}
|
||||
className={cn(job.hasIntegrationsRequiringAction && "bg-rose-500/30")}
|
||||
className={cn(
|
||||
(job.hasIntegrationsRequiringAction && "bg-rose-500/20") ||
|
||||
(job.lastRun === undefined && "bg-green-500/20")
|
||||
)}
|
||||
>
|
||||
<TableCell to={path}>
|
||||
<span className="flex items-center gap-2">
|
||||
@@ -141,7 +145,13 @@ export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResul
|
||||
<LabelValueStack label={"Never run"} value={"–"} />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCellChevron to={path} />
|
||||
<TableCellChevron to={path}>
|
||||
{job.lastRun === undefined && (
|
||||
<Badge className="mr-4" variant="green">
|
||||
New Job!
|
||||
</Badge>
|
||||
)}
|
||||
</TableCellChevron>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -6,12 +6,18 @@ const variants = {
|
||||
"grid place-items-center rounded-full px-2 h-5 tracking-wider text-xxs bg-slate-700 text-bright uppercase whitespace-nowrap",
|
||||
outline:
|
||||
"grid place-items-center rounded-sm px-1 h-5 tracking-wider text-xxs border border-dimmed text-dimmed uppercase whitespace-nowrap",
|
||||
green:
|
||||
"grid place-items-center rounded-sm px-1.5 h-5 tracking-wider outline-offset-1 outline outline-1 outline-green-600 text-xxs bg-green-500 text-slate-900 uppercase whitespace-nowrap",
|
||||
};
|
||||
|
||||
type BadgeProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||
variant?: keyof typeof variants;
|
||||
};
|
||||
|
||||
export function Badge({ className, variant = "default", ...props }: BadgeProps) {
|
||||
return <div className={cn(variants[variant], className)} {...props} />;
|
||||
export function Badge({ className, variant = "default", children, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(variants[variant], className)} {...props}>
|
||||
<span className="-mb-0.5">{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Paragraph, ParagraphVariant } from "./Paragraph";
|
||||
import { Paragraph } from "./Paragraph";
|
||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
|
||||
import { SimpleTooltip } from "./Tooltip";
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ import { WebhookIcon } from "~/assets/icons/WebhookIcon";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { LogoIcon } from "../LogoIcon";
|
||||
import { Spinner } from "./Spinner";
|
||||
import { SaplingIcon } from "~/assets/icons/SaplingIcon";
|
||||
import { TwoTreesIcon } from "~/assets/icons/TwoTreesIcon";
|
||||
import { OneTreeIcon } from "~/assets/icons/OneTreeIcon";
|
||||
|
||||
const icons = {
|
||||
account: (className: string) => <UserCircleIcon className={cn("text-slate-400", className)} />,
|
||||
@@ -128,6 +131,7 @@ const icons = {
|
||||
property: (className: string) => <Cog8ToothIcon className={cn("text-slate-600", className)} />,
|
||||
"qr-code": (className: string) => <QrCodeIcon className={cn("text-amber-400", className)} />,
|
||||
refresh: (className: string) => <ArrowPathIcon className={cn("text-bright", className)} />,
|
||||
sapling: (className: string) => <SaplingIcon className={cn("text-green-500", className)} />,
|
||||
search: (className: string) => <MagnifyingGlassIcon className={cn("text-dimmed", className)} />,
|
||||
settings: (className: string) => <Cog8ToothIcon className={cn("text-slate-600", className)} />,
|
||||
spinner: (className: string) => <Spinner className={className} color="blue" />,
|
||||
@@ -135,6 +139,8 @@ const icons = {
|
||||
star: (className: string) => <StarIcon className={cn("text-yellow-500", className)} />,
|
||||
stop: (className: string) => <StopIcon className={cn("text-rose-500", className)} />,
|
||||
team: (className: string) => <UserGroupIcon className={cn("text-blue-500", className)} />,
|
||||
tree: (className: string) => <OneTreeIcon className={cn("text-green-500", className)} />,
|
||||
trees: (className: string) => <TwoTreesIcon className={cn("text-green-500", className)} />,
|
||||
trigger: (className: string) => <BoltIcon className={cn("text-orange-500", className)} />,
|
||||
user: (className: string) => <UserIcon className={cn("text-blue-600", className)} />,
|
||||
warning: (className: string) => (
|
||||
|
||||
@@ -11,12 +11,14 @@ const variants = {
|
||||
label: "text-sm text-bright mt-0.5 select-none",
|
||||
description: "text-dimmed",
|
||||
inputPosition: "mt-1",
|
||||
icon: "w-8 h-8 mb-2",
|
||||
},
|
||||
simple: {
|
||||
button: "w-fit pr-4 data-[disabled]:opacity-70",
|
||||
label: "text-bright select-none",
|
||||
description: "text-dimmed",
|
||||
inputPosition: "mt-1",
|
||||
icon: "w-8 h-8 mb-2",
|
||||
},
|
||||
"button/small": {
|
||||
button:
|
||||
@@ -24,6 +26,7 @@ const variants = {
|
||||
label: "text-sm text-bright select-none",
|
||||
description: "text-dimmed",
|
||||
inputPosition: "mt-0",
|
||||
icon: "w-8 h-8 mb-2",
|
||||
},
|
||||
button: {
|
||||
button:
|
||||
@@ -31,6 +34,7 @@ const variants = {
|
||||
label: "text-bright select-none",
|
||||
description: "text-dimmed",
|
||||
inputPosition: "mt-1",
|
||||
icon: "w-8 h-8 mb-2",
|
||||
},
|
||||
description: {
|
||||
button:
|
||||
@@ -38,6 +42,15 @@ const variants = {
|
||||
label: "text-bright font-semibold -mt-1 text-left",
|
||||
description: "text-dimmed -mt-0 text-left",
|
||||
inputPosition: "mt-0",
|
||||
icon: "w-8 h-8 mb-2",
|
||||
},
|
||||
icon: {
|
||||
button:
|
||||
"w-full p-2.5 pb-4 hover:bg-slate-850 transition data-[disabled]:opacity-70 data-[state=checked]:bg-slate-850 border-slate-800 border rounded-sm",
|
||||
label: "text-bright font-semibold -mt-1 text-left",
|
||||
description: "text-dimmed -mt-0 text-left",
|
||||
inputPosition: "mt-0",
|
||||
icon: "mb-3",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -57,53 +70,60 @@ type RadioGroupItemProps = Omit<
|
||||
description?: string;
|
||||
badges?: string[];
|
||||
className?: string;
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const RadioGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
||||
RadioGroupItemProps
|
||||
>(({ className, children, variant = "simple", label, description, badges, ...props }, ref) => {
|
||||
const variation = variants[variant];
|
||||
>(
|
||||
(
|
||||
{ className, children, variant = "simple", label, description, badges, icon, ...props },
|
||||
ref
|
||||
) => {
|
||||
const variation = variants[variant];
|
||||
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex cursor-pointer items-start gap-x-2 transition",
|
||||
variation.button,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-sm border border-slate-700 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
variation.inputPosition
|
||||
"group flex cursor-pointer items-start gap-x-2 transition",
|
||||
variation.button,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex h-full w-full items-center justify-center bg-indigo-700">
|
||||
<Circle className="h-1.5 w-1.5 fill-white text-white" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<label htmlFor={props.id} className={cn("cursor-pointer", variation.label)}>
|
||||
{label}
|
||||
</label>
|
||||
{badges && (
|
||||
<span className="-mr-2 flex gap-x-1.5">
|
||||
{badges.map((badge) => (
|
||||
<Badge key={badge}>{badge}</Badge>
|
||||
))}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
"aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-sm border border-slate-700 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
variation.inputPosition
|
||||
)}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex h-full w-full items-center justify-center bg-indigo-700">
|
||||
<Circle className="h-1.5 w-1.5 fill-white text-white" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</div>
|
||||
<div className={cn(icon ? "flex h-full flex-col justify-end" : "")}>
|
||||
{variant === "icon" && <div className={variation.icon}>{icon}</div>}
|
||||
<div className="flex items-center gap-x-2">
|
||||
<label htmlFor={props.id} className={cn("cursor-pointer", variation.label)}>
|
||||
{label}
|
||||
</label>
|
||||
{badges && (
|
||||
<span className="-mr-2 flex gap-x-1.5">
|
||||
{badges.map((badge) => (
|
||||
<Badge key={badge}>{badge}</Badge>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(variant === "description" || variant === "icon") && (
|
||||
<Paragraph variant="small" className={cn("mt-0.5", variation.description)}>
|
||||
{description}
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
{variant === "description" && (
|
||||
<Paragraph variant="small" className={cn("mt-0.5", variation.description)}>
|
||||
{description}
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -51,7 +51,7 @@ const SheetOverlay = React.forwardRef<
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 scale-100 gap-4 bg-midnight-900 shadow-lg shadow-indigo-900 opacity-100 border-l border-y border-slate-800",
|
||||
"fixed z-50 scale-100 gap-4 bg-midnight-900 shadow-lg shadow-white/10 opacity-100 border-l border-y border-slate-800",
|
||||
{
|
||||
variants: {
|
||||
position: {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Header2 } from "./Headers";
|
||||
import { Spinner } from "./Spinner";
|
||||
|
||||
export function StepNumber({
|
||||
stepNumber,
|
||||
active = false,
|
||||
complete = false,
|
||||
displaySpinner = false,
|
||||
title,
|
||||
className,
|
||||
}: {
|
||||
@@ -13,6 +15,7 @@ export function StepNumber({
|
||||
complete?: boolean;
|
||||
title?: React.ReactNode;
|
||||
className?: string;
|
||||
displaySpinner?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("mr-3", className)}>
|
||||
@@ -28,7 +31,15 @@ export function StepNumber({
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded border border-slate-700 bg-slate-800 py-1 text-xs font-semibold text-dimmed shadow">
|
||||
{complete ? "✓" : stepNumber}
|
||||
</span>
|
||||
<Header2>{title}</Header2>
|
||||
|
||||
{displaySpinner ? (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Header2>{title}</Header2>
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<Header2>{title}</Header2>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||
import { Link } from "@remix-run/react";
|
||||
import { ReactNode, forwardRef } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Badge } from "./Badge";
|
||||
|
||||
type TableProps = {
|
||||
containerClassName?: string;
|
||||
@@ -172,12 +173,14 @@ export const TableCellChevron = forwardRef<
|
||||
{
|
||||
className?: string;
|
||||
to?: string;
|
||||
children?: ReactNode;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
}
|
||||
>(({ className, to, onClick }, ref) => {
|
||||
>(({ className, to, children, onClick }, ref) => {
|
||||
return (
|
||||
<TableCell className={className} to={to} onClick={onClick} ref={ref} alignment="right">
|
||||
<ChevronRightIcon className="h-4 w-4 text-slate-700 transition group-hover:text-bright" />
|
||||
{children}
|
||||
<ChevronRightIcon className="h-4 w-4 text-dimmed transition group-hover:text-bright" />
|
||||
</TableCell>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -325,7 +325,7 @@ function RerunPopover({
|
||||
<Form method="post" action={`/resources/runs/${runId}/rerun`} {...form.props}>
|
||||
<input {...conform.input(successRedirect, { type: "hidden" })} defaultValue={runsPath} />
|
||||
{environmentType === "PRODUCTION" && (
|
||||
<Callout variant="warning">
|
||||
<Callout variant="warning" className="mb-2">
|
||||
This will rerun this Job in your Production environment.
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { withDesign } from "storybook-addon-designs";
|
||||
import { Badge } from "../primitives/Badge";
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Primitives/Badges",
|
||||
decorators: [withDesign],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof BadgesExample>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: () => <BadgesExample />,
|
||||
};
|
||||
|
||||
function BadgesExample() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-y-8 p-8">
|
||||
<Badge>Default</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
<Badge variant="green">Green</Badge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { withDesign } from "storybook-addon-designs";
|
||||
import { useState } from "react";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { RadioGroup, RadioGroupItem } from "../primitives/RadioButton";
|
||||
import { NamedIcon } from "../primitives/NamedIcon";
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Primitives/RadioGroup",
|
||||
@@ -42,6 +43,14 @@ function RadioGroupExample() {
|
||||
value={"5"}
|
||||
variant="description"
|
||||
/>
|
||||
<RadioGroupItem
|
||||
id="r7"
|
||||
label="This is an icon label"
|
||||
description="This is a description"
|
||||
value={"6"}
|
||||
variant="icon"
|
||||
icon={<NamedIcon name="tree" className="h-8 w-8" />}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const LIVE_ENVIRONMENT = "live";
|
||||
export const DEV_ENVIRONMENT = "development";
|
||||
export const MAX_LIVE_PROJECTS = 1;
|
||||
export const DEFAULT_MAX_CONCURRENT_RUNS = 100;
|
||||
export const DEFAULT_MAX_CONCURRENT_RUNS = 10000;
|
||||
export const PREPROCESS_RETRY_LIMIT = 2;
|
||||
export const EXECUTE_JOB_RETRY_LIMIT = 10;
|
||||
|
||||
@@ -94,6 +94,7 @@ function getClient() {
|
||||
datasources: {
|
||||
db: {
|
||||
url: DATABASE_URL,
|
||||
// We can't set directUrl here, and we don't have to
|
||||
},
|
||||
},
|
||||
log: [
|
||||
|
||||
@@ -4,6 +4,7 @@ import { SecretStoreOptionsSchema } from "./services/secrets/secretStore.server"
|
||||
const EnvironmentSchema = z.object({
|
||||
NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]),
|
||||
DATABASE_URL: z.string(),
|
||||
DIRECT_URL: z.string(),
|
||||
SESSION_SECRET: z.string(),
|
||||
MAGIC_LINK_SECRET: z.string(),
|
||||
ENCRYPTION_KEY: z.string(),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { getUser } from "./services/session.server";
|
||||
import { appEnvTitleTag } from "./utils";
|
||||
import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react";
|
||||
import { useHighlight } from "./hooks/useHighlight";
|
||||
import { ExternalScripts } from "remix-utils";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: tailwindStylesheetUrl }];
|
||||
@@ -104,6 +105,7 @@ function App() {
|
||||
</HighlightErrorBoundary>
|
||||
<Toast />
|
||||
<ScrollRestoration />
|
||||
<ExternalScripts />
|
||||
<Scripts />
|
||||
<LiveReload />
|
||||
</body>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { ArrowUpIcon } from "@heroicons/react/24/solid";
|
||||
import { LoaderArgs, SerializeFrom } from "@remix-run/server-runtime";
|
||||
import useWindowSize from "react-use/lib/useWindowSize";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import Confetti from "react-confetti";
|
||||
import { ExternalScriptsFunction } from "remix-utils";
|
||||
import { HowToSetupYourProject } from "~/components/helpContent/HelpContentText";
|
||||
import { JobsTable } from "~/components/jobs/JobsTable";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { BreadcrumbLink } from "~/components/navigation/NavBar";
|
||||
import { Callout } from "~/components/primitives/Callout";
|
||||
import { Header2 } from "~/components/primitives/Headers";
|
||||
import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
|
||||
import { Input } from "~/components/primitives/Input";
|
||||
import { NamedIcon } from "~/components/primitives/NamedIcon";
|
||||
import {
|
||||
PageHeader,
|
||||
PageInfoGroup,
|
||||
@@ -17,24 +21,19 @@ import {
|
||||
PageTitleRow,
|
||||
} from "~/components/primitives/PageHeader";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { TextLink } from "~/components/primitives/TextLink";
|
||||
import { useFilterJobs } from "~/hooks/useFilterJobs";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { JobListPresenter } from "~/presenters/JobListPresenter.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import useWindowSize from "react-use/lib/useWindowSize";
|
||||
import {
|
||||
docsPath,
|
||||
ProjectParamSchema,
|
||||
projectIntegrationsPath,
|
||||
trimTrailingSlash,
|
||||
ProjectParamSchema,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { BreadcrumbLink } from "~/components/navigation/NavBar";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { JobListPresenter } from "~/presenters/JobListPresenter.server";
|
||||
import { TextLink } from "~/components/primitives/TextLink";
|
||||
import { GitHubLightIcon, OpenAILightIcon, ResendIcon } from "@trigger.dev/companyicons";
|
||||
import { ClockIcon, CalendarDaysIcon, SlackIcon } from "lucide-react";
|
||||
|
||||
export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
const userId = await requireUserId(request);
|
||||
@@ -59,6 +58,12 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
export const handle: Handle = {
|
||||
breadcrumb: (match) => <BreadcrumbLink to={trimTrailingSlash(match.pathname)} title="Jobs" />,
|
||||
expandSidebar: true,
|
||||
scripts: (match) => [
|
||||
{
|
||||
src: "https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js",
|
||||
crossOrigin: "anonymous",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
@@ -83,94 +88,50 @@ export default function Page() {
|
||||
</PageInfoRow>
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
{/* Todo: this confetti component needs to trigger when the example project is created, then never again. */}
|
||||
{/* <Confetti
|
||||
width={width}
|
||||
height={height}
|
||||
recycle={false}
|
||||
numberOfPieces={1000}
|
||||
colors={[
|
||||
"#E7FF52",
|
||||
"#41FF54",
|
||||
"rgb(245 158 11)",
|
||||
"rgb(22 163 74)",
|
||||
"rgb(37 99 235)",
|
||||
"rgb(67 56 202)",
|
||||
"rgb(219 39 119)",
|
||||
"rgb(225 29 72)",
|
||||
"rgb(217 70 239)",
|
||||
]}
|
||||
/> */}
|
||||
<Help defaultOpen={jobs.length === 0}>
|
||||
<Help>
|
||||
{(open) => (
|
||||
<div className={cn("grid h-fit gap-4", open ? "grid-cols-2" : "grid-cols-1")}>
|
||||
<div>
|
||||
{jobs.length > 0 && jobs.some((j) => j.hasIntegrationsRequiringAction) && (
|
||||
<Callout
|
||||
variant="error"
|
||||
to={projectIntegrationsPath(organization, project)}
|
||||
className="mb-2"
|
||||
>
|
||||
Some of your Jobs have Integrations that have not been configured.
|
||||
</Callout>
|
||||
)}
|
||||
<div className="mb-2 flex items-center justify-between gap-x-2">
|
||||
{jobs.length === 0 ? (
|
||||
<Header2>Jobs</Header2>
|
||||
) : (
|
||||
<Input
|
||||
placeholder="Search Jobs"
|
||||
variant="tertiary"
|
||||
icon="search"
|
||||
fullWidth={true}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
<HelpTrigger title="How do I add Trigger.dev to my Next.js app?" />
|
||||
</div>
|
||||
{jobs.length === 0 ? (
|
||||
<div>
|
||||
<div
|
||||
className={
|
||||
"flex w-full flex-col justify-center gap-x-4 rounded-md border border-dashed border-indigo-800 px-5 py-8"
|
||||
}
|
||||
>
|
||||
<Paragraph variant="small">Your Jobs will appear here.</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={cn("grid gap-4", open ? "h-full grid-cols-2" : " h-full grid-cols-1")}>
|
||||
<div className="h-full">
|
||||
{jobs.length > 0 ? (
|
||||
<>
|
||||
{jobs.some((j) => j.hasIntegrationsRequiringAction) && (
|
||||
<Callout
|
||||
variant="error"
|
||||
to={projectIntegrationsPath(organization, project)}
|
||||
className="mb-2"
|
||||
>
|
||||
Some of your Jobs have Integrations that have not been configured.
|
||||
</Callout>
|
||||
)}
|
||||
<div className="mb-2 flex flex-col">
|
||||
<Header2 spacing>Jobs</Header2>
|
||||
<div className="flex w-full">
|
||||
<Input
|
||||
placeholder="Search Jobs"
|
||||
variant="tertiary"
|
||||
icon="search"
|
||||
fullWidth={true}
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
/>
|
||||
<HelpTrigger title="Example Jobs and inspiration" />
|
||||
</div>
|
||||
</div>
|
||||
<JobsTable
|
||||
jobs={filteredItems}
|
||||
noResultsText={`No Jobs match ${filterText}. Try a different search
|
||||
query.`}
|
||||
query.`}
|
||||
/>
|
||||
{jobs.length === 1 ? (
|
||||
<>
|
||||
<Callout
|
||||
variant="docs"
|
||||
to={docsPath("documentation/guides/create-a-job")}
|
||||
className="my-3"
|
||||
>
|
||||
Create your first Job in code
|
||||
</Callout>
|
||||
<ExampleJobs />
|
||||
</>
|
||||
) : (
|
||||
<Callout
|
||||
variant="docs"
|
||||
to={docsPath("documentation/guides/create-a-job")}
|
||||
className="my-3"
|
||||
>
|
||||
Create another Job
|
||||
</Callout>
|
||||
{jobs.length === 1 && jobs.every((r) => r.lastRun === undefined) && (
|
||||
<RunYourJobPrompt />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<HowToSetupYourProject />
|
||||
)}
|
||||
</div>
|
||||
<HelpContent title="How to add Trigger.dev to your Next.js app (3 mins)">
|
||||
<HowToSetupYourProject />
|
||||
<HelpContent title="Example Jobs and inspiration">
|
||||
<ExampleJobs />
|
||||
</HelpContent>
|
||||
</div>
|
||||
)}
|
||||
@@ -180,69 +141,155 @@ export default function Page() {
|
||||
);
|
||||
}
|
||||
|
||||
function ExampleJobs() {
|
||||
function RunYourJobPrompt() {
|
||||
return (
|
||||
<div className="mt-6 flex w-full flex-col gap-y-2 rounded bg-slate-900 p-4">
|
||||
<Header2 className="text-slate-300">Example Jobs</Header2>
|
||||
<Paragraph variant="small">
|
||||
If you want more inspiration or just want to dig into the code of a Job, check out our{" "}
|
||||
<TextLink href="https://github.com/triggerdotdev/examples">examples repo</TextLink>.
|
||||
<div className="mt-2 flex w-full gap-x-2 rounded border border-slate-800 bg-slate-900 p-4 pl-6">
|
||||
<ArrowUpIcon className="h-5 w-5 animate-bounce text-green-500" />
|
||||
<Paragraph variant="small" className="text-green-500">
|
||||
Your Job is ready to run! Click it to run it now.
|
||||
</Paragraph>
|
||||
<div className="h-[1px] w-full bg-slate-800" />
|
||||
<div className="flex gap-1.5">
|
||||
<ClockIcon className="h-4 w-4 pt-0.5 text-slate-100" />
|
||||
<Paragraph variant="small">
|
||||
<TextLink href="https://github.com/triggerdotdev/examples/tree/main/delays">
|
||||
Delays
|
||||
</TextLink>{" "}
|
||||
- Using delays inside Jobs
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
<CalendarDaysIcon className="h-4 w-4 pt-0.5 text-slate-100" />
|
||||
<Paragraph variant="small">
|
||||
<TextLink href="https://github.com/triggerdotdev/examples/tree/main/scheduled">
|
||||
Scheduled
|
||||
</TextLink>{" "}
|
||||
- Interval and cron scheduled Jobs
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
<GitHubLightIcon className="ml-0.5 h-4 w-4 pt-0.5" />
|
||||
<Paragraph variant="small">
|
||||
<TextLink href="https://github.com/triggerdotdev/examples/tree/main/github">
|
||||
GitHub
|
||||
</TextLink>{" "}
|
||||
- When a new GitHub issue is opened it adds a “Bug” label to it.
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
<OpenAILightIcon className="ml-0.5 h-4 w-4 pt-0.5" />
|
||||
<Paragraph variant="small">
|
||||
<TextLink href="https://github.com/triggerdotdev/examples/tree/main/openai">
|
||||
OpenAI
|
||||
</TextLink>{" "}
|
||||
- Generate images and jokes from a prompt
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
<ResendIcon className="ml-0.5 h-4 w-4 pt-0.5" />
|
||||
<Paragraph variant="small">
|
||||
<TextLink href="https://github.com/triggerdotdev/examples/tree/main/resend">
|
||||
Resend
|
||||
</TextLink>{" "}
|
||||
- Sends an email by submitting a form in the Next.js app
|
||||
</Paragraph>
|
||||
</div>{" "}
|
||||
<div className="flex gap-1.5">
|
||||
<SlackIcon className="ml-0.5 h-4 w-4 pt-0.5" />
|
||||
<Paragraph variant="small">
|
||||
<TextLink href="https://github.com/triggerdotdev/examples/tree/main/slack">
|
||||
Slack
|
||||
</TextLink>{" "}
|
||||
- Sends a Slack message when an event is received
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ExampleJobs() {
|
||||
return (
|
||||
<>
|
||||
<Header2 spacing>Video walk-through</Header2>
|
||||
<Paragraph spacing variant="small">
|
||||
Watch Matt, CEO of Trigger.dev create a GitHub issue reminder in Slack using Trigger.dev.
|
||||
(10 mins)
|
||||
</Paragraph>
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/uocBQt2HeQo?&showinfo=0&rel=0&modestbranding=1"
|
||||
title="Trigger.dev explainer video"
|
||||
width="400"
|
||||
height="250"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
className="mb-4 w-full border-b border-slate-800"
|
||||
/>
|
||||
<Header2 spacing>How to create a Job</Header2>
|
||||
<Paragraph variant="small" spacing>
|
||||
Our docs are a great way to learn how to create Jobs with each type of Trigger, from
|
||||
webhooks, to delays, to triggering Jobs on a schedule.{" "}
|
||||
</Paragraph>
|
||||
<a
|
||||
href="https://trigger.dev/docs/documentation/guides/create-a-job"
|
||||
className="mb-4 flex w-full items-center rounded border-b border-slate-800 py-2 transition hover:border-transparent hover:bg-slate-800"
|
||||
>
|
||||
<NamedIcon name={"external-link"} className={iconStyles} />
|
||||
<Paragraph variant="small" className="font-semibold text-bright">
|
||||
How to create a Job
|
||||
</Paragraph>
|
||||
</a>
|
||||
<Header2 spacing>Check out some example Jobs in code</Header2>
|
||||
<Paragraph spacing variant="small">
|
||||
If you're looking for inspiration for your next Job, check out our{" "}
|
||||
<TextLink href="https://github.com/triggerdotdev/examples">examples repo</TextLink>. Or jump
|
||||
straight into an example repo from the list below:
|
||||
</Paragraph>
|
||||
<div className="flex w-full flex-col">
|
||||
{examples.map((example) => (
|
||||
<a
|
||||
href={example.codeLink}
|
||||
key={example.title}
|
||||
className="flex w-full items-center rounded border-b border-slate-800 py-2 transition hover:border-transparent hover:bg-slate-800"
|
||||
>
|
||||
{example.icon}
|
||||
<Paragraph variant="small">
|
||||
<span className="font-semibold text-bright">{example.title}</span> -{" "}
|
||||
{example.description}
|
||||
</Paragraph>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const iconStyles = "h-7 w-7 mr-2 pl-2 min-w-[28px]";
|
||||
|
||||
const examples = [
|
||||
{
|
||||
icon: <NamedIcon name={"clock"} className={iconStyles} />,
|
||||
title: "Basic delay",
|
||||
description: "Logs a message to the console, waits 5 minutes, and then logs another message.",
|
||||
codeLink: "https://github.com/triggerdotdev/examples/blob/main/delays/src/jobs/delayJob.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="calendar" className={iconStyles} />,
|
||||
title: "Basic interval",
|
||||
description: "This Job runs every 60 seconds, starting 60 seconds after it is first indexed.",
|
||||
codeLink: "https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/interval.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="calendar" className={iconStyles} />,
|
||||
title: "Cron scheduled interval",
|
||||
description: "A scheduled Job which runs at 2:30pm every Monday.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/cronScheduled.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="openai" className={iconStyles} />,
|
||||
title: "OpenAI text summarizer",
|
||||
description:
|
||||
"Summarizes a block of text, pulling out the most unique / helpful points using OpenAI.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/openai-text-summarizer/src/jobs/textSummarizer.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="openai" className={iconStyles} />,
|
||||
title: "Tell me a joke using OpenAI",
|
||||
description: "Generates a random joke using OpenAI GPT 3.5.",
|
||||
codeLink: "https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/tellMeAJoke.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="openai" className={iconStyles} />,
|
||||
title: "Generate a random image using OpenAI",
|
||||
description: "Generates a random image of a hedgehog using OpenAI DALL-E.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/generateHedgehogImages.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="resend" className={iconStyles} />,
|
||||
title: "Send an email using Resend",
|
||||
description: "Send a basic email using Resend.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/resend/src/jobs/resendBasicEmail.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="github" className={iconStyles} />,
|
||||
title: "GitHub issue reminder",
|
||||
description: "Sends a Slack message if a GitHub issue is left for 24h.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/github-issue-reminder/jobs/githubIssue.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="github" className={iconStyles} />,
|
||||
title: "Github new star alert in Slack",
|
||||
description: "When a repo is starred, a message is sent to a Slack.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarToSlack.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="github" className={iconStyles} />,
|
||||
title: "Add a custom label to a GitHub issue",
|
||||
description: "When a new GitHub issue is opened it adds a “Bug” label to it.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/onIssueOpened.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="github" className={iconStyles} />,
|
||||
title: "GitHub new star alert",
|
||||
description: "When a repo is starred a message is logged with the new Stargazers count.",
|
||||
codeLink: "https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarAlert.ts",
|
||||
},
|
||||
{
|
||||
icon: <NamedIcon name="slack" className={iconStyles} />,
|
||||
title: "Send a Slack message",
|
||||
description: "Sends a Slack message to a specific channel when an event is received.",
|
||||
codeLink:
|
||||
"https://github.com/triggerdotdev/examples/blob/main/slack/src/jobs/sendSlackMessage.ts",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -6,26 +6,17 @@ import { ImpersonationBanner } from "~/components/ImpersonationBanner";
|
||||
import { NoMobileOverlay } from "~/components/NoMobileOverlay";
|
||||
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
|
||||
import { NavBar } from "~/components/navigation/NavBar";
|
||||
import { featuresForRequest } from "~/features.server";
|
||||
import { useIsProjectChildPage } from "~/hooks/useIsProjectChildPage";
|
||||
import { getOrganizations } from "~/models/organization.server";
|
||||
|
||||
import { getImpersonationId } from "~/services/impersonation.server";
|
||||
import { clearRedirectTo, commitSession } from "~/services/redirectTo.server";
|
||||
import { requireUser } from "~/services/session.server";
|
||||
import { confirmBasicDetailsPath, invitationCodePath } from "~/utils/pathBuilder";
|
||||
import { confirmBasicDetailsPath } from "~/utils/pathBuilder";
|
||||
|
||||
export const loader = async ({ request }: LoaderArgs) => {
|
||||
const user = await requireUser(request);
|
||||
const organizations = await getOrganizations({ userId: user.id });
|
||||
const impersonationId = await getImpersonationId(request);
|
||||
const features = featuresForRequest(request);
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (features.isManagedCloud && !user.invitationCodeId && url.pathname !== invitationCodePath()) {
|
||||
throw redirect(invitationCodePath());
|
||||
}
|
||||
|
||||
//you have to confirm basic details before you can do anything
|
||||
if (!user.confirmedBasicDetails) {
|
||||
|
||||
@@ -83,7 +83,7 @@ export async function action({ request }: ActionArgs) {
|
||||
return redirectWithSuccessMessage(
|
||||
organizationsPath(),
|
||||
request,
|
||||
"🚀 Welcome to the Trigger.dev Cloud private beta"
|
||||
"🚀 Welcome to the Trigger.dev Cloud"
|
||||
);
|
||||
} catch (error: any) {
|
||||
return json({ errors: { body: error.message } }, { status: 400 });
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { sse } from "~/utils/sse";
|
||||
|
||||
export async function loader({ request, params }: LoaderArgs) {
|
||||
await requireUserId(request);
|
||||
|
||||
const { projectId } = z.object({ projectId: z.string() }).parse(params);
|
||||
|
||||
const project = await projectForUpdates(projectId);
|
||||
|
||||
if (!project) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
let lastSignals = calculateChangeSignals(project);
|
||||
|
||||
return sse({
|
||||
request,
|
||||
run: async (send, stop) => {
|
||||
const result = await projectForUpdates(projectId);
|
||||
if (!result) {
|
||||
return stop();
|
||||
}
|
||||
|
||||
const newSignals = calculateChangeSignals(result);
|
||||
|
||||
if (lastSignals.jobCount !== newSignals.jobCount) {
|
||||
send({ data: JSON.stringify(newSignals) });
|
||||
}
|
||||
|
||||
lastSignals = newSignals;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function projectForUpdates(id: string) {
|
||||
return prisma.project.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: { jobs: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function calculateChangeSignals(
|
||||
project: NonNullable<Awaited<ReturnType<typeof projectForUpdates>>>
|
||||
) {
|
||||
const jobCount = project._count?.jobs ?? 0;
|
||||
|
||||
return {
|
||||
jobCount,
|
||||
};
|
||||
}
|
||||
@@ -94,6 +94,7 @@ function getWorkerQueue() {
|
||||
connectionString: env.DATABASE_URL,
|
||||
concurrency: 5,
|
||||
pollInterval: 1000,
|
||||
noPreparedStatements: env.DATABASE_URL !== env.DIRECT_URL,
|
||||
},
|
||||
schema: workerCatalog,
|
||||
recurringTasks: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Roboto+Mono:wght@300;400;500;600;700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Roboto+Mono:wght@300;400;500;600;700&display=swap&family=Poppins:wght@600&display=swap");
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ExternalScriptsFunction } from "remix-utils";
|
||||
import { BreadcrumbItem } from "~/components/navigation/Breadcrumb";
|
||||
|
||||
export type Handle = {
|
||||
breadcrumb?: BreadcrumbItem;
|
||||
expandSidebar?: boolean;
|
||||
scripts?: ExternalScriptsFunction;
|
||||
};
|
||||
|
||||
@@ -131,6 +131,10 @@ export function projectEnvironmentsPath(organization: OrgForPath, project: Proje
|
||||
return `${projectPath(organization, project)}/environments`;
|
||||
}
|
||||
|
||||
export function projectStreamingPath(id: string) {
|
||||
return `/resources/projects/${id}/jobs/stream`;
|
||||
}
|
||||
|
||||
export function projectEnvironmentsStreamingPath(
|
||||
organization: OrgForPath,
|
||||
project: ProjectForPath
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.2.0",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
|
||||
@@ -76,6 +76,7 @@ module.exports = {
|
||||
fontFamily: {
|
||||
sans: ["Inter", "sans-serif"],
|
||||
mono: ["Roboto Mono", "monospace"],
|
||||
title: ["Poppins", "sans-serif"]
|
||||
},
|
||||
fontSize: {
|
||||
xxs: [
|
||||
|
||||
@@ -32,6 +32,7 @@ services:
|
||||
- db
|
||||
environment:
|
||||
DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?schema=public
|
||||
DIRECT_URL: postgres://postgres:postgres@db:5432/postgres?schema=public
|
||||
SESSION_SECRET: secret123
|
||||
MAGIC_LINK_SECRET: secret123
|
||||
REMIX_APP_PORT: 3030
|
||||
|
||||
@@ -107,6 +107,8 @@ client.defineJob({
|
||||
|
||||
They are declarative pattern-matching rules, modeled after [AWS EventBridge patterns](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html).
|
||||
|
||||
Here's a more detailed explanation of [Event filters](/documentation/guides/event-filter)
|
||||
|
||||
Given the following custom event payload:
|
||||
|
||||
```json
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
---
|
||||
title: "Event Filters"
|
||||
description: "They are declarative pattern-matching rules, modeled after [AWS EventBridge patterns](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html)."
|
||||
---
|
||||
|
||||
Given the following custom event payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"uid": "jexAgaeJFJsrGfans1pxqm",
|
||||
"type": "15 Min Meeting",
|
||||
"price": 0,
|
||||
"title": "15 Min Meeting between Eric Allam and John Doe",
|
||||
"length": 15,
|
||||
"status": "ACCEPTED",
|
||||
"endTime": "2023-01-25T16:00:00Z",
|
||||
"bookingId": 198052,
|
||||
"organizer": {
|
||||
"id": 32794,
|
||||
"name": "Eric Allam",
|
||||
"email": "eric@trigger.dev",
|
||||
"language": { "locale": "en" },
|
||||
"timeZone": "Europe/London"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following event filter would match the event:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": ["15 Min Meeting"],
|
||||
"status": ["ACCEPTED", "REJECTED"],
|
||||
"organizer": {
|
||||
"name": ["Eric Allam"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For an event pattern to match an event, the event must contain all the field names listed in the event pattern. The field names must also appear in the event with the same nesting structure.
|
||||
|
||||
The value of each field name in the event pattern must be an array of strings, numbers, or booleans. The event pattern matches the event if the value of the field name in the event is equal to any of the values in the array.
|
||||
|
||||
Effectively, each array is an OR condition, and the entire event pattern is an AND condition.
|
||||
|
||||
So the above event filter will match because `status == "ACCEPTED"`, and it would also match if `status == "REJECTED"`.
|
||||
|
||||
## String Filters
|
||||
|
||||
String filters are used to match string values within the event payload. You can filter events based on exact string matches, case-insensitive matches, starts-with, ends-with, and more.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `name: ["John"]`: Triggers the job if the name field in the event payload is exactly "John".
|
||||
- `name: [{ $startsWith: "Jo" }]`: Triggers the job if the name field starts with "Jo".
|
||||
- `name: [{ $endsWith: "hn" }]`: Triggers the job if the name field ends with "hn".
|
||||
- `name: [{ $ignoreCaseEquals: "john" }]` : Triggers the job if the name field is equal to "john" regardless of case.
|
||||
|
||||
### Examples
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "notify-hr-new-hires-specific-hobbies",
|
||||
name: "Notify HR of New Hires with Specific Hobbies",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "employee.hired",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
hobbies: z.array(z.string()),
|
||||
}),
|
||||
filter: {
|
||||
name: [{ $startsWith: "Jo" }], // Only trigger for employees whose name starts with "Jo"
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "notify-hr-new-hires-specific-hobbies",
|
||||
name: "Notify HR of New Hires with Specific Hobbies",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "employee.hired",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
hobbies: z.array(z.string()),
|
||||
}),
|
||||
filter: {
|
||||
name: ["John"], // Only trigger for employees whose name is "John"
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
## Boolean Filters
|
||||
|
||||
Boolean filters are used to filter events based on boolean values within the event payload.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `paidPlan: [true]`: Triggers the job if the `PaidPlan` field in the event payload is `true`.
|
||||
- `isAdmin: [false]`: Triggers the job if the `isAdmin` field in the event payload is `false`.
|
||||
|
||||
### Examples
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "notify-admins-paid-users",
|
||||
name: "Notify Admins of New Paid Users",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.created",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
paidPlan: z.boolean(),
|
||||
isAdmin: z.boolean(),
|
||||
}),
|
||||
filter: {
|
||||
paidPlan: [true],
|
||||
isAdmin: [true], // Only trigger for paid users who are also admins
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "send-premium-content",
|
||||
name: "Send Premium Content to Subscribed Users",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "content.available",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
subscriptionStatus: z.boolean(),
|
||||
}),
|
||||
filter: {
|
||||
subscriptionStatus: [true], // Only trigger for users with an active subscription
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
## Number Filters
|
||||
|
||||
Number filters are used to filter events based on numeric values within the event payload. It can also be used to perform numeric comparisons on values within the event payload.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `age [18]`: Triggers the job if the `age` field in the event payload is equal to `18`.
|
||||
- `score: [{ $gt: 90 }]` : Triggers the job if the score field is greater than `90`.
|
||||
- `score: [{ $gte: 90 }]` : Triggers the job if the score field is greater than or equal to `90`.
|
||||
- `score: [{ $lt: 90 }]` : Triggers the job if the score field is less than `90`.
|
||||
- `score: [{ $lte: 90 }]` : Triggers the job if the score field is less than or equal to `90`.
|
||||
- `age: [{ $gt: 20 }, { $lt: 40 }]`: Triggers the job if the `age` field is greater than 20 and less than 40.
|
||||
- `score: [{ $between: [90, 110] }]`: Triggers the job if the `score` field is between 90 and 110.
|
||||
|
||||
### Examples
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "send-discount-high-scores",
|
||||
name: "Send Discount to High Scoring Customers",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "customer.score.updated",
|
||||
schema: z.object({
|
||||
customerId: z.string(),
|
||||
score: z.number(),
|
||||
}),
|
||||
filter: {
|
||||
score: [{ $gt: 90 }], // Only trigger for customers with a score greater than 90
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "notify-upcoming-birthdays",
|
||||
name: "Notify Users of Upcoming Birthdays",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.birthday",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
age: z.number(),
|
||||
}),
|
||||
filter: {
|
||||
age: [25], // Only trigger for users who are turning 25
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "send-discount-code",
|
||||
name: "Send Discount Code",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.purchase",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
age: z.number(),
|
||||
}),
|
||||
filter: {
|
||||
age: [{ $gt: 18 }, { $lt: 30 }], // Only trigger for users aged between 18 and 30
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "update-user-level",
|
||||
name: "Update User Level",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.scoreUpdated",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
score: z.number(),
|
||||
}),
|
||||
filter: {
|
||||
score: [{ $between: [50, 100] }], // Only trigger for scores between 50 and 100 (inclusive)
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
## Array Filters
|
||||
|
||||
Array filters are used to filter events based on the content of arrays within the event payload.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `hobbies: [{ $includes: "reading" }]`: Triggers the job if the hobbies array includes the value "reading".
|
||||
|
||||
### Examples
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "recommend-books-avid-readers",
|
||||
name: "Recommend Books to Avid Readers",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.preferences.updated",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
hobbies: z.array(z.string()),
|
||||
}),
|
||||
filter: {
|
||||
hobbies: [{ $includes: "reading" }], // Only trigger for users whose hobbies include "reading"
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
//run function
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Existence Filters
|
||||
|
||||
Existence filters are used to filter events based on the presence or absence of certain keys within the event payload.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `name: [{ $exists: true }]` : Triggers the job if the `name` field exists in the event payload.
|
||||
- `foo: [{ $exists: false }]` : Triggers the job if the foo field does not exist in the event payload.
|
||||
|
||||
### Examples
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "send-welcome-email",
|
||||
name: "Send Welcome Email",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.created",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
}),
|
||||
filter: {
|
||||
name: [{ $exists: true }], // Only trigger for events where the 'name' field exists
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "notify-admin-missing-field",
|
||||
name: "Notify Admins of Missing Field",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.updated",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
isAdmin: z.boolean(),
|
||||
}),
|
||||
filter: {
|
||||
foo: [{ $exists: false }], // Only trigger if the 'foo' field does not exist
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
## Combining Filters
|
||||
|
||||
Filters can be combined to create more complex conditions for triggering jobs.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- `name: ["Alice"], age: [30]` : Triggers the job if the name is "Alice" and the age is 30.
|
||||
- `name: ["Alice"], age: [{ $gt: 20 }, { $lt: 40 }]` : Triggers the job if the name is "Alice" and the age is between 20 and 40.
|
||||
- `name: ["Alice", "Bob"]` : Triggers the job if the name is either "Alice" or "Bob".
|
||||
- `name: ["Alice", "Bob"], age: [30]` : Triggers the job if the name is either "Alice" or "Bob" and the age is 30.
|
||||
|
||||
### Examples
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "notify-vip-event",
|
||||
name: "Notify VIP Customers",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "user.registration",
|
||||
schema: z.object({
|
||||
userId: z.string(),
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
totalPurchases: z.number(),
|
||||
}),
|
||||
filter: {
|
||||
age: [{ $gt: 25 }, { $lt: 60 }], // Trigger for users aged between 25 and 60
|
||||
totalPurchases: [{ $gte: 3 }], // Trigger for users with 3 or more total purchases
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "process-orders",
|
||||
name: "Process Orders",
|
||||
version: "1.0.0",
|
||||
trigger: eventTrigger({
|
||||
name: "order.placed",
|
||||
schema: z.object({
|
||||
orderId: z.string(),
|
||||
customerId: z.string(),
|
||||
region: z.string(),
|
||||
totalAmount: z.number(),
|
||||
}),
|
||||
filter: {
|
||||
region: ["US", "Canada"], // Trigger for orders from US or Canada
|
||||
totalAmount: [{ $gt: 100 }, { $lt: 1000 }], // Trigger for orders with a total amount between 100 and 1000
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
## Using with Stripe triggers
|
||||
|
||||
Here is an example of a Stripe Trigger with an Event Filter:
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "stripe-on-subscription-created",
|
||||
name: "Stripe On Subscription Created",
|
||||
version: "0.1.0",
|
||||
trigger: stripe.onCustomerSubscriptionCreated({
|
||||
filter: {
|
||||
currency: ["usd"],
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("ctx", { ctx });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The job is triggered by the `onCustomerSubscriptionCreated` event from Stripe. It is configured to trigger only when certain conditions are met. In this case, the job is triggered when a subscription is created with the following condition:
|
||||
|
||||
- Currency: Only subscriptions with the currency "USD" will trigger this job.
|
||||
|
||||
## Using with Supabase triggers
|
||||
|
||||
Here is an example of a Supabase Trigger with an Event Filter:
|
||||
|
||||
```ts
|
||||
client.defineJob({
|
||||
id: "supabase-management-example-objects-storage",
|
||||
name: "Supabase Management Example Object Storage",
|
||||
version: "0.1.0",
|
||||
trigger: triggers.onInserted({
|
||||
schema: "storage",
|
||||
table: "objects",
|
||||
filter: {
|
||||
record: {
|
||||
bucket_id: ["example_bucket"], // Only trigger for objects in the "example_bucket" bucket
|
||||
name: [
|
||||
{
|
||||
$endsWith: ".png", // Only trigger for objects with a name ending in ".png"
|
||||
},
|
||||
],
|
||||
path_tokens: [
|
||||
{
|
||||
$includes: "images", // Only trigger for objects with a path that includes the token "images"
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
integrations: {
|
||||
openai,
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
- This Listens for insert events in the "objects" table of the "storage" schema.
|
||||
- Conditions for Triggering:
|
||||
- Object belongs to "example_bucket".
|
||||
- Object's name ends with ".png".
|
||||
- Object's path includes the token "images".
|
||||
@@ -138,7 +138,7 @@ To establish an API route for interacting with Trigger.dev, follow these steps b
|
||||
1. Create a new file named `trigger.(ts/js)` within the `pages/api/` directory.
|
||||
2. Add the following code to `trigger.(ts/js)`:
|
||||
|
||||
```
|
||||
```typescript
|
||||
import { createPagesRoute } from "@trigger.dev/nextjs";
|
||||
import { client } from "@/trigger";
|
||||
import "@/Jobs";
|
||||
@@ -187,7 +187,7 @@ export * from "./examples"
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Additonal Job Definations
|
||||
## Additonal Job Definitions
|
||||
You can define more job definitions by creating additional files in the `Jobs` folder and exporting them in `index` file.
|
||||
|
||||
For example, in `index.(ts/js)`, you can export other job files like this:
|
||||
|
||||
@@ -24,13 +24,20 @@ We provide an official Trigger.dev [docker image](https://github.com/triggerdotd
|
||||
icon="fly"
|
||||
href="/documentation/guides/self-hosting/flyio"
|
||||
>
|
||||
Easily to deploy to Fly.io
|
||||
Easily deploy to Fly.io
|
||||
</Card>
|
||||
<Card
|
||||
title="Render.com"
|
||||
icon="draw-square"
|
||||
href="/documentation/guides/self-hosting/flyio"
|
||||
>
|
||||
Easily to deploy to Render.com
|
||||
Easily deploy to Render.com
|
||||
</Card>
|
||||
<Card
|
||||
title="Supabase.com"
|
||||
icon="bolt"
|
||||
href="/documentation/guides/self-hosting/supabase"
|
||||
>
|
||||
Easily deploy with Supabase
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -19,6 +19,10 @@ You should fork the repository before starting so you can make changes and commi
|
||||
brew install flyctl
|
||||
```
|
||||
|
||||
```sh Linux
|
||||
curl -L https://fly.io/install.sh | sh
|
||||
```
|
||||
|
||||
```sh Windows
|
||||
pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"
|
||||
```
|
||||
@@ -41,6 +45,8 @@ fly launch
|
||||
|
||||
2. Follow the prompts by `fly launch` and make sure to answer them in the following way:
|
||||
|
||||
<Warning>Do NOT get rid of your terminal output, we will need your database connection string shortly.</Warning>
|
||||
|
||||
```sh
|
||||
? Would you like to copy its configuration to the new app? Yes
|
||||
? Choose an app name (leaving blank will default to 'trigger-v2-fly-demo') <enter your preferred app name here or leave blank>
|
||||
@@ -54,7 +60,7 @@ fly launch
|
||||
|
||||
### Required
|
||||
|
||||
`MAGIC_LINK_SECRET`, `SESSION_SECRET` and `ENCRYPTION_KEY`
|
||||
1. `MAGIC_LINK_SECRET`, `SESSION_SECRET` and `ENCRYPTION_KEY`
|
||||
|
||||
All of these secrets should be 16-byte random strings, which you can easily generate (and copy into your pasteboard) with the following command:
|
||||
|
||||
@@ -62,10 +68,18 @@ All of these secrets should be 16-byte random strings, which you can easily gene
|
||||
openssl rand -hex 16 | pbcopy
|
||||
```
|
||||
|
||||
`LOGIN_ORIGIN` and `APP_ORIGIN`
|
||||
2. `LOGIN_ORIGIN` and `APP_ORIGIN`
|
||||
|
||||
Both of these secrets should be set to the base URL of your fly application. For example `https://trigger-v2-fly-demo.fly.dev`
|
||||
|
||||
3. `DIRECT_URL`
|
||||
|
||||
This needs to be set to the database connection string that was printed to your terminal after the creation step above:
|
||||
|
||||
```sh
|
||||
Connection string: postgres://postgres:<PASSWORD>@<fly db name>.flycast:5432
|
||||
```
|
||||
|
||||
### Optional
|
||||
|
||||
`AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET`
|
||||
@@ -97,6 +111,7 @@ fly secrets set \
|
||||
SESSION_SECRET=<random string> \
|
||||
LOGIN_ORIGIN="https://<fly app name>.fly.dev" \
|
||||
APP_ORIGIN="https://<fly app name>.fly.dev" \
|
||||
DIRECT_URL="postgres://postgres:<PASSWORD>@<fly db name>.flycast:5432" \
|
||||
FROM_EMAIL="Acme Inc. <hello@yourdomain.com>" \
|
||||
REPLY_TO_EMAIL="Acme Inc. <reply@yourdomain.com>" \
|
||||
RESEND_API_KEY=<your API Key> \
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
title: "Deploy with Supabase"
|
||||
description: "Self-host [Trigger.dev](https://trigger.dev) with Supabase and Docker"
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- [docker](https://docs.docker.com/engine/install/)
|
||||
- time
|
||||
|
||||
## Set up your project
|
||||
|
||||
1. Create a project dir and grab the latest `.env` template
|
||||
|
||||
```sh
|
||||
mkdir trigger-docker && cd trigger-docker
|
||||
curl -L https://github.com/triggerdotdev/docker/raw/main/.env.example > .env
|
||||
```
|
||||
|
||||
2. Generate secrets with `openssl rand -hex 16` for the following variables:
|
||||
|
||||
<Warning>Do NOT just copy and paste this. Generate your own secrets instead!</Warning>
|
||||
|
||||
```sh .env (excerpt)
|
||||
...
|
||||
MAGIC_LINK_SECRET=15600f1236e568d6c9c400a94e16a4ed
|
||||
SESSION_SECRET=8d92078940c89588fc8b6f5481f2c6e0
|
||||
ENCRYPTION_KEY=1189c93e399856a2a9a1454496171b2e
|
||||
...
|
||||
```
|
||||
|
||||
## Create a Supabase DB
|
||||
|
||||
<Tip>Alternative: [Self-host Supabase](https://supabase.com/docs/guides/self-hosting/docker)</Tip>
|
||||
|
||||
1. No account yet? [Click here](https://supabase.com/dashboard/sign-up) to complete onboarding
|
||||
|
||||
2. [Create a new project](https://supabase.com/dashboard/projects)
|
||||
|
||||
<Frame>
|
||||
<img src="/images/supabase-new-project.png" alt="Supabase - New Project" />
|
||||
</Frame>
|
||||
|
||||
3. Make a note of your password, we'll need it in a moment
|
||||
|
||||
<Frame>
|
||||
<img src="/images/supabase-project-password.png" alt="Supabase - Project Password" />
|
||||
</Frame>
|
||||
|
||||
4. Wait until your project has finished setting up
|
||||
|
||||
<Frame>
|
||||
<img src="/images/supabase-project-setup.png" alt="Supabase - Project Setup" />
|
||||
</Frame>
|
||||
|
||||
5. Navigate to `Project Settings -> Database` and copy your database connection string
|
||||
|
||||
<Frame>
|
||||
<img src="/images/supabase-db-url.png" alt="Supabase - Database URL" />
|
||||
</Frame>
|
||||
|
||||
6. Paste it into your `.env` file, and append `?schema=triggerdotdev`
|
||||
|
||||
<Tip>Don't forget to replace `<PASSWORD>` with the project password you set in Step 3.</Tip>
|
||||
|
||||
```sh .env (excerpt)
|
||||
...
|
||||
DATABASE_URL=postgresql://postgres:<PASSWORD>@db.<ID>.supabase.co:5432/postgres?schema=triggerdotdev
|
||||
...
|
||||
```
|
||||
|
||||
7. The complete file should look something like this now:
|
||||
|
||||
```sh .env
|
||||
LOGIN_ORIGIN=http://localhost:3030
|
||||
APP_ORIGIN=http://localhost:3030
|
||||
PORT=3030
|
||||
REMIX_APP_PORT=3030
|
||||
|
||||
MAGIC_LINK_SECRET=15600f1236e568d6c9c400a94e16a4ed
|
||||
SESSION_SECRET=8d92078940c89588fc8b6f5481f2c6e0
|
||||
ENCRYPTION_KEY=1189c93e399856a2a9a1454496171b2e
|
||||
|
||||
DATABASE_URL=postgresql://postgres:<PASSWORD>@db.<ID>.supabase.co:5432/postgres?schema=triggerdotdev
|
||||
DIRECT_URL=${DATABASE_URL}
|
||||
|
||||
NODE_ENV=development
|
||||
RUNTIME_PLATFORM=docker-compose
|
||||
```
|
||||
|
||||
## Self-host Trigger.dev with docker compose
|
||||
|
||||
1. Create a docker compose file in your project dir
|
||||
|
||||
```yaml docker-compose.yml
|
||||
---
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
triggerdotdev:
|
||||
image: ghcr.io/triggerdotdev/trigger.dev:latest
|
||||
container_name: triggerdotdev
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 3030:3030
|
||||
```
|
||||
|
||||
2. Start your docker container
|
||||
|
||||
```sh
|
||||
docker compose up
|
||||
```
|
||||
|
||||
3. Wait for the database setup to finish - this might take a while
|
||||
|
||||
4. You should now be able to visit http://localhost:3030 and see this screen:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/trigger.dev-login.png" alt="Trigger.dev - Login" />
|
||||
</Frame>
|
||||
|
||||
5. Click "Continue with Email", enter your email address and hit submit
|
||||
|
||||
6. Grab the magic link from your terminal and proceed with account creation
|
||||
|
||||
<Frame>
|
||||
<img src="/images/trigger.dev-magic-link.png" alt="Trigger.dev - CLI Magic Link" />
|
||||
</Frame>
|
||||
|
||||
7. If everything went well, the `triggerdotdev` and `graphile_worker` schemas should now be populated. Check your Supabase DB dashboard to be sure:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/supabase-db-seed.png" alt="Supabase - Database Seed" />
|
||||
</Frame>
|
||||
|
||||
8. Congratulations, you're all set up and ready to go with Supabase and Docker! 🚀
|
||||
|
||||
## Bonus: Connection pooling
|
||||
|
||||
### What is connection pooling?
|
||||
|
||||
<Frame caption="Connection Pool - © 2023 Supabase, Apache License 2.0">
|
||||
<img src="/images/supabase-connection-pool.png" alt="Supabase - Connection Pool" />
|
||||
</Frame>
|
||||
|
||||
When running on a single server, our app can be trusted to manage its own connections - it knows how many are open and can process its internal queue as needed.
|
||||
|
||||
<Tooltip tip="🐝">Serverless</Tooltip> changes things. Here, many concurrent app instances are started, each unaware of the others' connections. This can lead to a huge - potentially fatal - increase in connections straight to your DB.
|
||||
|
||||
<Info>
|
||||
PostgreSQL has a [default connection limit](https://www.postgresql.org/docs/current/runtime-config-connection.html#:~:text=at%20server%20start.-,max_connections,-(integer)) of 100.
|
||||
</Info>
|
||||
|
||||
**External connection pools** solve this by putting an additional layer between client and server, which essentially works like a message queue that gets processed as limits allow. The clients are blind to this and can keep sending their requests as before.
|
||||
|
||||
### Enable connection pooling
|
||||
|
||||
1. Copy and paste the connection string from earlier to `DIRECT_URL`
|
||||
|
||||
```sh .env (excerpt)
|
||||
...
|
||||
DIRECT_URL=postgresql://postgres:<PASSWORD>@db.<ID>.supabase.co:5432/postgres?schema=triggerdotdev
|
||||
...
|
||||
```
|
||||
|
||||
2. Navigate to `Project Settings -> Database` and copy your connection pool URL
|
||||
|
||||
<Frame>
|
||||
<img src="/images/supabase-pool-url.png" alt="Supabase - Connection Pool URL" />
|
||||
</Frame>
|
||||
|
||||
3. Paste it into your `.env` file next to `DATABASE_URL`
|
||||
|
||||
4. Append `?schema=triggerdotdev&pgbouncer=true` and insert your password
|
||||
|
||||
```sh .env (excerpt)
|
||||
...
|
||||
# notice the differing ports and additional query param
|
||||
DATABASE_URL=postgresql://postgres:<PASSWORD>@db.<ID>.supabase.co:6543/postgres?schema=triggerdotdev&pgbouncer=true
|
||||
DIRECT_URL=postgresql://postgres:<PASSWORD>@db.<ID>.supabase.co:5432/postgres?schema=triggerdotdev
|
||||
...
|
||||
```
|
||||
|
||||
5. All done! You can now enjoy the benefits of connection pooling ⚡️
|
||||
|
||||
## Next steps
|
||||
|
||||
<CardGroup>
|
||||
<Card
|
||||
title="Bootstrap your Next.js project"
|
||||
icon="person-running-fast"
|
||||
href="/documentation/quickstart#run-the-cli-init-command"
|
||||
>
|
||||
Get started in 5 minutes.
|
||||
</Card>
|
||||
<Card
|
||||
title="Tunnel local traffic"
|
||||
icon="person-digging"
|
||||
href="/documentation/guides/tunneling-platform"
|
||||
>
|
||||
Start digging with ngrok.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -18,6 +18,14 @@ There are a few ways to do this, but we recommend using [ngrok](https://ngrok.co
|
||||
brew install ngrok/ngrok/ngrok
|
||||
```
|
||||
|
||||
```sh Linux
|
||||
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | \
|
||||
sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && \
|
||||
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | \
|
||||
sudo tee /etc/apt/sources.list.d/ngrok.list && \
|
||||
sudo apt update && sudo apt install ngrok
|
||||
```
|
||||
|
||||
```sh Windows
|
||||
choco install ngrok
|
||||
```
|
||||
|
||||
@@ -8,20 +8,15 @@ Trigger.dev is an open source framework for creating long-running Jobs directly
|
||||
You can use [Trigger.dev Cloud](https://cloud.trigger.dev) or [Self-host Trigger.dev](/documentation/guides/self-hosting) on your own infrastructure.
|
||||
|
||||
<Note>
|
||||
Trigger.dev v2 currently only supports serverless. During our beta we will add
|
||||
[support for long-running
|
||||
servers](https://github.com/triggerdotdev/trigger.dev/issues/244).
|
||||
Trigger.dev v2 currently only supports serverless. We will be adding [support for long-running
|
||||
servers](https://github.com/triggerdotdev/trigger.dev/issues/244) soon.
|
||||
</Note>
|
||||
|
||||
<CardGroup>
|
||||
<Card title="Quick start" icon="person-running-fast" href="quickstart">
|
||||
<Card title="Next.js Quick start" icon="person-running-fast" href="quickstart">
|
||||
Get started in 5 minutes.
|
||||
</Card>
|
||||
<Card
|
||||
title="Video walkthrough"
|
||||
icon="video"
|
||||
href="/documentation/guides/video-walkthrough"
|
||||
>
|
||||
<Card title="Video walkthrough" icon="video" href="/documentation/guides/video-walkthrough">
|
||||
Go from zero to a working Job in your Next.js app in 10 minutes.
|
||||
</Card>
|
||||
<Card
|
||||
@@ -35,8 +30,10 @@ You can use [Trigger.dev Cloud](https://cloud.trigger.dev) or [Self-host Trigger
|
||||
Trigger.dev integrates with a wide range of services.
|
||||
</Card>
|
||||
<Card title="Examples" icon="slot-machine" href="/examples">
|
||||
One of the quickest ways to learn how Trigger.dev works is to view some
|
||||
example Jobs.
|
||||
One of the quickest ways to learn how Trigger.dev works is to view some example Jobs.
|
||||
</Card>
|
||||
<Card title="Manual Setup" icon="book-sparkles" href="guides/manual">
|
||||
For complete control, you can manually setup Trigger.dev using this guide.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -51,8 +48,8 @@ We'd love to hear from you or give you a hand getting started. Here are some way
|
||||
href="https://discord.gg/kA47vcd8P6"
|
||||
color="#5865F2"
|
||||
>
|
||||
The #help-and-questions channel is a great place to get help with any
|
||||
questions about Trigger.dev.
|
||||
The #help-and-questions channel is a great place to get help with any questions about
|
||||
Trigger.dev.
|
||||
</Card>
|
||||
<Card
|
||||
title="Follow us on Twitter"
|
||||
@@ -62,13 +59,9 @@ We'd love to hear from you or give you a hand getting started. Here are some way
|
||||
>
|
||||
Follow us on Twitter to get the latest updates and news.
|
||||
</Card>
|
||||
<Card
|
||||
title="Schedule a call"
|
||||
icon="phone"
|
||||
href="https://cal.com/team/triggerdotdev/support"
|
||||
>
|
||||
Arrange a call with one of the founders. We can help answer questions, build
|
||||
API Integrations for you and give 1-on-1 help building your first Job.
|
||||
<Card title="Schedule a call" icon="phone" href="https://cal.com/team/triggerdotdev/support">
|
||||
Arrange a call with one of the founders. We can help answer questions, build API Integrations
|
||||
for you and give 1-on-1 help building your first Job.
|
||||
</Card>
|
||||
<Card
|
||||
title="Give us a star on GitHub"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
title: "Quick Start"
|
||||
description: "Start creating Jobs in 5 minutes"
|
||||
title: "Next.js Quick Start"
|
||||
sidebarTitle: "Next.js"
|
||||
description: "Start creating Jobs in 5 minutes in your Next.js project."
|
||||
---
|
||||
|
||||
This quick start guide will get you up and running with Trigger.dev.
|
||||
|
||||
<Accordion title="Don't have a Next.js project yet to add Trigger.dev to? No problem, you can complete the Quick Start using a blank Next.js project:">
|
||||
Create a blank project by running the `create-next-app` command in your terminal:
|
||||
<Accordion title="Need to create a new Next.js project to add Trigger.dev to?">
|
||||
No problem, create a blank project by running the `create-next-app` command in your terminal then continue with this quickstart guide as normal:
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest
|
||||
@@ -18,14 +19,14 @@ Trigger.dev works with either the Pages or App Router configuration.
|
||||
|
||||
## Create a Trigger.dev account
|
||||
|
||||
You can either
|
||||
You can either:
|
||||
|
||||
- Use the [Trigger.dev Cloud](https://cloud.trigger.dev) (we're currently in private beta).
|
||||
- Use the [Trigger.dev Cloud](https://cloud.trigger.dev).
|
||||
- Or [self-host](/documentation/guides/self-hosting) the service.
|
||||
|
||||
### Create your first project
|
||||
|
||||
Once you've created an account, follow the steps to:
|
||||
Once you've created an account, follow the steps in the app to:
|
||||
|
||||
1. Complete your account details.
|
||||
2. Create your first Organization and Project.
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
title: "Supabase Quick Start"
|
||||
sidebarTitle: "Supabase.com"
|
||||
description: "Integrate Supabase with Trigger.dev in minutes."
|
||||
---
|
||||
|
||||
This quick start guide will get you setup with Trigger.dev and Supabase inside a new Next.js project.
|
||||
|
||||
## Preparing Supabase & Next.js
|
||||
|
||||
Create an account with Supabase or login if you have one already:
|
||||
|
||||
- [Create a new Supabase account](https://supabase.com/dashboard/sign-up)
|
||||
- [Login to Supabase](https://supabase.com/dashboard/sign-in)
|
||||
|
||||
Once on the dashboard, click the 'New project' button to create a new project in Supabase.
|
||||
|
||||

|
||||
|
||||
From inside your new project, click the 'SQL Editor' button in the side menu and then click the "User Management Starter" from the Quickstarts menu:
|
||||
|
||||
<Note>
|
||||
Feel free to choose another starter or use hand-crafted tables, anything will work for purposes of
|
||||
this quick start
|
||||
</Note>
|
||||
|
||||

|
||||
|
||||
Over in a terminal window, create a new Next.js app with Supabase support:
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest -e with-supabase my-supabase-app
|
||||
cd my-supabase-app
|
||||
```
|
||||
|
||||
Next, rename the `.env.local.example` file to `.env.local` and add your Supabase URL and public key:
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_SUPABASE_URL=your-project-url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
You can find your API keys in Supabase by clicking the cog (⚙️) icon in the left menu and selecting 'API' from the Project Settings menu:
|
||||
|
||||

|
||||
|
||||
Finally, in the terminal, go ahead and [Generate the Typescript types](https://supabase.com/docs/reference/javascript/typescript-support) now as we'll be using them shortly:
|
||||
|
||||
<Tip>
|
||||
You may need to login to the supabase CLI first. For more info see the [supabase login
|
||||
docs](https://supabase.com/docs/reference/cli/supabase-login)
|
||||
</Tip>
|
||||
|
||||
```bash
|
||||
npx supabase gen types typescript --project-id <your project id> --schema public --schema auth > supabase-types.ts
|
||||
```
|
||||
|
||||
## Setup Trigger.dev
|
||||
|
||||
Now it's time to create a Trigger.dev account. You can either:
|
||||
|
||||
- Use the [Trigger.dev Cloud](https://cloud.trigger.dev).
|
||||
- Or [self-host](/documentation/guides/self-hosting) the service.
|
||||
|
||||
Once you've created an account, follow the steps in the app to:
|
||||
|
||||
1. Complete your account details.
|
||||
2. Create your first Organization and Project.
|
||||
|
||||
Next, initialize Trigger.dev in the project by running this command:
|
||||
|
||||
```bash
|
||||
npx @trigger.dev/cli@latest init
|
||||
```
|
||||
|
||||
Edit the `middleware.ts` file and add the following code to exclude the Trigger.dev endpoint from the Supabase auth middleware:
|
||||
|
||||
```ts
|
||||
// Match all routes other than /api/trigger
|
||||
export const config = {
|
||||
matcher: ["/((?!api/trigger).*)"],
|
||||
};
|
||||
```
|
||||
|
||||
## Add the Supabase Integration
|
||||
|
||||
Add the Supabase package to your project by running this command:
|
||||
|
||||
```bash
|
||||
npm add @trigger.dev/supabase
|
||||
```
|
||||
|
||||
Edit the `jobs/examples.ts` file and replace with the following code:
|
||||
|
||||
```ts
|
||||
import { client } from "@/trigger";
|
||||
import { Database } from "@/supabase-types";
|
||||
import { SupabaseManagement } from "@trigger.dev/supabase";
|
||||
|
||||
// Use OAuth to authenticate with Supabase Management API
|
||||
const supabaseManagement = new SupabaseManagement({
|
||||
id: "supabase-management",
|
||||
});
|
||||
|
||||
// Use the types we generated earlier
|
||||
const db = supabaseManagement.db<Database>(process.env.NEXT_PUBLIC_SUPABASE_URL!);
|
||||
|
||||
client.defineJob({
|
||||
id: "supabase-test-job",
|
||||
name: "My Supabase Test Job",
|
||||
version: "1.0.0",
|
||||
trigger: db.onInserted({
|
||||
schema: "auth",
|
||||
table: "users",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
```
|
||||
|
||||
## Authenticate to the Supabase Management API
|
||||
|
||||
The Supabase Triggers use the [Supabase Management API](https://trigger.dev/docs/integrations/apis/supabase/management) to register the triggers in your Supabase projects.
|
||||
|
||||
You can authenticate using a Personal Access Token or via the new Supabase Management API OAuth implementation, which we are using in this example.
|
||||
|
||||
Login to Trigger.dev and navigate to the project "Integrations" page. Select the "Supabase Management" integration and configure it like so:
|
||||
|
||||

|
||||
|
||||
Authorize access to your Supabase project and then you'll be ready to run the Job.
|
||||
|
||||

|
||||
|
||||
## Run and test the Job
|
||||
|
||||
Now you are ready to run the Next.js app and test the Job. Run the following command to start the Next.js app:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
And then in a separate terminal, run the following command to start the Trigger.dev agent:
|
||||
|
||||
```bash
|
||||
npx @trigger.dev/cli dev
|
||||
```
|
||||
|
||||
Head back to your Supabase Dashboard -> Auth, and create a new user (keep "Auto Confirm User?" checked)
|
||||
|
||||

|
||||
|
||||
Then navigate over to your Trigger.dev project dashboard and you should see the job running.
|
||||
|
||||

|
||||
|
||||
## What's next?
|
||||
|
||||
Checkout our fully-functioning [Supabase Onboarding Email example](https://github.com/triggerdotdev/examples/tree/main/supabase-onboarding-emails) to learn how to build an email drip campaign in 62 lines of code.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Write your first Job" icon="hexagon-plus" href="/documentation/guides/create-a-job">
|
||||
A Guide for how to create your first real Job
|
||||
</Card>
|
||||
<Card
|
||||
title="What is Trigger.dev"
|
||||
icon="wand-magic-sparkles"
|
||||
href="/documentation/concepts/what-is-triggerdotdev"
|
||||
>
|
||||
Learn more about how Trigger.dev works and how it can help you.
|
||||
</Card>
|
||||
<Card title="Supabase Integration" icon="grid-2" href="/integrations/apis/supabase/introduction">
|
||||
Read more about the Supabase Integration and how it works.
|
||||
</Card>
|
||||
<Card title="Get help" icon="hire-a-helper" href="/documentation/get-help">
|
||||
Struggling getting setup or have a question? We're here to help.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -10,19 +10,20 @@ description: "An ever-growing list of example Jobs which you can use to get star
|
||||
|
||||
Click the links below to view the Job code. You can also easily test these Jobs by following the instructions in the README of each example.
|
||||
|
||||
| Job (code in link) | Description | Integrations used |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| [Basic delay](https://github.com/triggerdotdev/examples/blob/main/delays/src/jobs/delayJob.ts) | Logs a message to the console, waits for 5 minutes, and then logs another message. | N/A |
|
||||
| [Basic interval](https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/interval.ts) | This Job will run every 60 seconds, starting 60 seconds after this Job is first indexed. | N/A |
|
||||
| [Cron scheduled interval](https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/cronScheduled.ts) | A scheduled Job which runs at 2:30pm every Monday. | N/A |
|
||||
| [OpenAI text summarizer](https://github.com/triggerdotdev/examples/blob/main/openai-text-summarizer/src/jobs/textSummarizer.ts) | Summarizes a block of text, pulling out the most unique and helpful points using OpenAI GPT-3.5 turbo. | [OpenAI](/integrations/apis/openai) |
|
||||
| [Tell me a joke using OpenAI](https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/tellMeAJoke.ts) | Generates a random joke using OpenAI GPT 3.5. | [OpenAI](/integrations/apis/openai) |
|
||||
| [Generate an image using OpenAI](https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/generateHedgehogImages.ts) | Generates a random image of a hedgehog using OpenAI DALL-E. | [OpenAI](/integrations/apis/openai) |
|
||||
| [GitHub issue reminder](https://github.com/triggerdotdev/examples/blob/main/github-issue-reminder/jobs/githubIssue.ts) | Sends a Slack message to a channel if a GitHub issue is left open for 24 hours | [GitHub](/integrations/apis/github), [Slack](/integrations/apis/slack) |
|
||||
| [Github new star alert in Slack](https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarToSlack.ts) | When a repo is starred, a message is sent to a Slack channel with the name and URL of the GitHub user who starred the repo, and the updated Stargazers count. | [GitHub](/integrations/apis/github), [Slack](/integrations/apis/slack) |
|
||||
| [Add a custom label to a GitHub issue when it is created](https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/onIssueOpened.ts) | When a new GitHub issue is opened it adds a "Bug" label to it. | [GitHub](/integrations/apis/github) |
|
||||
| [GitHub new star alert](https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarAlert.ts) | When a repo is starred a message is logged with the new Stargazers count. | [GitHub](/integrations/apis/github) |
|
||||
| [Send a Slack message when an event is received](https://github.com/triggerdotdev/examples/blob/main/slack/src/jobs/sendSlackMessage.ts) | Sends a Slack message to a specific channel when an event is received. | [Slack](/integrations/apis/slack) |
|
||||
| [Send an email using Resend](https://github.com/triggerdotdev/examples/blob/main/resend/src/jobs/resendBasicEmail.ts) | Send a basic email using Resend | [Resend](/integrations/apis/resend) |
|
||||
| Job (code in link) | Description | Integrations used |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| [Basic delay](https://github.com/triggerdotdev/examples/blob/main/delays/src/jobs/delayJob.ts) | Logs a message to the console, waits for 5 minutes, and then logs another message. | N/A |
|
||||
| [Basic interval](https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/interval.ts) | This Job will run every 60 seconds, starting 60 seconds after this Job is first indexed. | N/A |
|
||||
| [Cron scheduled interval](https://github.com/triggerdotdev/examples/blob/main/scheduled/src/jobs/cronScheduled.ts) | A scheduled Job which runs at 2:30pm every Monday. | N/A |
|
||||
| [Supabase user management starter](https://github.com/triggerdotdev/examples/blob/main/supabase-onboarding-emails/jobs/supabase.ts) | When a user signs up and confirms their email address, they will receive 3 "onboarding" emails over 2 days | [Supabase](/integrations/apis/supabase) [Resend](/integrations/apis/resend) |
|
||||
| [OpenAI text summarizer](https://github.com/triggerdotdev/examples/blob/main/openai-text-summarizer/src/jobs/textSummarizer.ts) | Summarizes a block of text, pulling out the most unique and helpful points using OpenAI GPT-3.5 turbo. | [OpenAI](/integrations/apis/openai) |
|
||||
| [Tell me a joke using OpenAI](https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/tellMeAJoke.ts) | Generates a random joke using OpenAI GPT 3.5. | [OpenAI](/integrations/apis/openai) |
|
||||
| [Generate an image using OpenAI](https://github.com/triggerdotdev/examples/blob/main/openai/src/jobs/generateHedgehogImages.ts) | Generates a random image of a hedgehog using OpenAI DALL-E. | [OpenAI](/integrations/apis/openai) |
|
||||
| [GitHub issue reminder](https://github.com/triggerdotdev/examples/blob/main/github-issue-reminder/jobs/githubIssue.ts) | Sends a Slack message to a channel if a GitHub issue is left open for 24 hours | [GitHub](/integrations/apis/github), [Slack](/integrations/apis/slack) |
|
||||
| [Github new star alert in Slack](https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarToSlack.ts) | When a repo is starred, a message is sent to a Slack channel with the name and URL of the GitHub user who starred the repo, and the updated Stargazers count. | [GitHub](/integrations/apis/github), [Slack](/integrations/apis/slack) |
|
||||
| [Add a custom label to a GitHub issue when it is created](https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/onIssueOpened.ts) | When a new GitHub issue is opened it adds a "Bug" label to it. | [GitHub](/integrations/apis/github) |
|
||||
| [GitHub new star alert](https://github.com/triggerdotdev/examples/blob/main/github/src/jobs/newStarAlert.ts) | When a repo is starred a message is logged with the new Stargazers count. | [GitHub](/integrations/apis/github) |
|
||||
| [Send a Slack message when an event is received](https://github.com/triggerdotdev/examples/blob/main/slack/src/jobs/sendSlackMessage.ts) | Sends a Slack message to a specific channel when an event is received. | [Slack](/integrations/apis/slack) |
|
||||
| [Send an email using Resend](https://github.com/triggerdotdev/examples/blob/main/resend/src/jobs/resendBasicEmail.ts) | Send a basic email using Resend | [Resend](/integrations/apis/resend) |
|
||||
|
||||
If you have any ideas for Jobs you would like to build, you can fill in the [Job request form](https://bcymafitv0e.typeform.com/to/YLUKy9my#source=example-jobs-docs).
|
||||
|
||||
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 100 KiB |
@@ -22,31 +22,53 @@ yarn add @trigger.dev/supabase
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Quick links
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Supabase Quick Start"
|
||||
icon="bolt-lightning"
|
||||
href="/documentation/quickstarts/supabase"
|
||||
>
|
||||
Get up and running with Supabase in just a few minutes.
|
||||
</Card>
|
||||
<Card
|
||||
title="Supabase Onboarding Emails"
|
||||
icon="file-code"
|
||||
href="https://github.com/triggerdotdev/examples/tree/main/supabase-onboarding-emails"
|
||||
>
|
||||
Example of how to send a welcome email drip campaign using Supabase and Trigger.dev
|
||||
</Card>
|
||||
<Card
|
||||
title="Introducing Supabase integration"
|
||||
icon="grid-2"
|
||||
href="https://trigger.dev/blog/introducing-supabase-integration"
|
||||
>
|
||||
Our deep-dive blog post announcing our Supabase integration
|
||||
</Card>
|
||||
<Card
|
||||
title="Self-hosting"
|
||||
icon="laptop-binary"
|
||||
href="/documentation/guides/self-hosting/supabase"
|
||||
>
|
||||
Learn how to self-host Trigger.dev on your own Supabase database.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
Our Supabase package supports two different integrations: One for the Supabase Management API and one that wraps the `@supabase/supabase-js` client.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Management API"
|
||||
icon="server"
|
||||
href="/integrations/apis/supabase/management"
|
||||
>
|
||||
The [Management API](https://supabase.com/docs/reference/api/introduction)
|
||||
is used to manage your Supabase organization, projects, and databases. It
|
||||
can also be used to trigger jobs based on changes in your Supabase database.
|
||||
Works with OAuth or Personal Access Tokens.
|
||||
<Card title="Management API" icon="server" href="/integrations/apis/supabase/management">
|
||||
The [Management API](https://supabase.com/docs/reference/api/introduction) is used to manage
|
||||
your Supabase organization, projects, and databases. It can also be used to trigger jobs based
|
||||
on changes in your Supabase database. Works with OAuth or Personal Access Tokens.
|
||||
</Card>
|
||||
<Card
|
||||
title="Supabase Client"
|
||||
icon="database"
|
||||
href="/integrations/apis/supabase/client"
|
||||
>
|
||||
Wraps the [@supabase/supabase-js](https://github.com/supabase/supabase-js)
|
||||
client to make it easy to use Supabase in your jobs to run queries against
|
||||
your database and access other functionality, like auth, storage, and
|
||||
functions. Works with your Supabase
|
||||
[service_role](https://supabase.com/docs/guides/api/api-keys#the-servicerole-key)
|
||||
key.
|
||||
<Card title="Supabase Client" icon="database" href="/integrations/apis/supabase/client">
|
||||
Wraps the [@supabase/supabase-js](https://github.com/supabase/supabase-js) client to make it
|
||||
easy to use Supabase in your jobs to run queries against your database and access other
|
||||
functionality, like auth, storage, and functions. Works with your Supabase
|
||||
[service_role](https://supabase.com/docs/guides/api/api-keys#the-servicerole-key) key.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -18,13 +18,12 @@ description: "Integrations make it easy to authenticate and use APIs."
|
||||
>
|
||||
How to connect Integrations to your account and use them in your code
|
||||
</Card>
|
||||
<Card
|
||||
title="How to create your own Integrations"
|
||||
icon="paintbrush"
|
||||
href="/integrations/create"
|
||||
>
|
||||
<Card title="How to create your own Integrations" icon="paintbrush" href="/integrations/create">
|
||||
You can create your own Integrations (and optionally share them with others)
|
||||
</Card>
|
||||
<Card title="Example repo & Jobs" icon="code" href="/examples/introduction">
|
||||
Check out example Jobs using our integrations.{" "}
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## The Integrations Catalog
|
||||
|
||||
@@ -50,12 +50,24 @@
|
||||
"url": "examples"
|
||||
}
|
||||
],
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/documentation/quickstart",
|
||||
"destination": "/documentation/quickstarts/nextjs"
|
||||
}
|
||||
],
|
||||
"navigation": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"documentation/introduction",
|
||||
"documentation/quickstart",
|
||||
{
|
||||
"group": "Quick Starts",
|
||||
"pages": [
|
||||
"documentation/quickstarts/nextjs",
|
||||
"documentation/quickstarts/supabase"
|
||||
]
|
||||
},
|
||||
"documentation/guides/create-a-job",
|
||||
"documentation/guides/video-walkthrough"
|
||||
]
|
||||
@@ -132,6 +144,7 @@
|
||||
"documentation/guides/deployment-automatic"
|
||||
]
|
||||
},
|
||||
"documentation/guides/event-filter",
|
||||
"documentation/guides/zod",
|
||||
{
|
||||
"group": "Self hosting",
|
||||
@@ -139,6 +152,7 @@
|
||||
"documentation/guides/self-hosting",
|
||||
"documentation/guides/self-hosting/flyio",
|
||||
"documentation/guides/self-hosting/render",
|
||||
"documentation/guides/self-hosting/supabase",
|
||||
"documentation/guides/tunneling-platform"
|
||||
]
|
||||
},
|
||||
@@ -156,7 +170,10 @@
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": ["integrations/introduction", "integrations/create"]
|
||||
"pages": [
|
||||
"integrations/introduction",
|
||||
"integrations/create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
@@ -179,16 +196,22 @@
|
||||
},
|
||||
{
|
||||
"group": "OpenAI",
|
||||
"pages": ["integrations/apis/openai"]
|
||||
"pages": [
|
||||
"integrations/apis/openai"
|
||||
]
|
||||
},
|
||||
"integrations/apis/plain",
|
||||
{
|
||||
"group": "Resend",
|
||||
"pages": ["integrations/apis/resend"]
|
||||
"pages": [
|
||||
"integrations/apis/resend"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Slack",
|
||||
"pages": ["integrations/apis/slack"]
|
||||
"pages": [
|
||||
"integrations/apis/slack"
|
||||
]
|
||||
},
|
||||
"integrations/apis/typeform"
|
||||
]
|
||||
@@ -242,7 +265,10 @@
|
||||
"sdk/dynamictrigger/constructor",
|
||||
{
|
||||
"group": "Instance methods",
|
||||
"pages": ["sdk/dynamictrigger/register", "sdk/dynamictrigger/unregister"]
|
||||
"pages": [
|
||||
"sdk/dynamictrigger/register",
|
||||
"sdk/dynamictrigger/unregister"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -253,7 +279,10 @@
|
||||
"sdk/dynamicschedule/constructor",
|
||||
{
|
||||
"group": "Instance methods",
|
||||
"pages": ["sdk/dynamicschedule/register", "sdk/dynamicschedule/unregister"]
|
||||
"pages": [
|
||||
"sdk/dynamicschedule/register",
|
||||
"sdk/dynamicschedule/unregister"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -274,7 +303,10 @@
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": ["examples/introduction", "examples/examples-repository"]
|
||||
"pages": [
|
||||
"examples/introduction",
|
||||
"examples/examples-repository"
|
||||
]
|
||||
}
|
||||
],
|
||||
"footerSocials": {
|
||||
@@ -287,4 +319,4 @@
|
||||
"apiKey": "phc_hwYmedO564b3Ik8nhA4Csrb5SueY0EwFJWCbseGwWW"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"stripe": "nodemon --watch src/stripe.ts -r tsconfig-paths/register -r dotenv/config src/stripe.ts",
|
||||
"sendgrid": "nodemon --watch src/sendgrid.ts -r tsconfig-paths/register -r dotenv/config src/sendgrid.ts",
|
||||
"supabase": "nodemon --watch src/supabase.ts -r tsconfig-paths/register -r dotenv/config src/supabase.ts",
|
||||
"supabase:types": "npx supabase gen types typescript --project-id $SUPABASE_PROJECT_ID --schema public --schema auth --schema storage > src/supabase-types.ts",
|
||||
"dev:trigger": "trigger-cli dev --port 8080"
|
||||
@@ -14,6 +15,7 @@
|
||||
"@trigger.dev/openai": "workspace:*",
|
||||
"@trigger.dev/plain": "workspace:*",
|
||||
"@trigger.dev/resend": "workspace:*",
|
||||
"@trigger.dev/sendgrid": "workspace:*",
|
||||
"@trigger.dev/sdk": "workspace:*",
|
||||
"@trigger.dev/slack": "workspace:*",
|
||||
"@trigger.dev/stripe": "workspace:*",
|
||||
@@ -35,4 +37,4 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^3.14.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { TriggerClient, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { createExpressServer } from "@trigger.dev/express";
|
||||
import { z } from "zod";
|
||||
import { SendGrid } from "@trigger.dev/sendgrid";
|
||||
|
||||
export const client = new TriggerClient({
|
||||
id: "job-catalog",
|
||||
apiKey: process.env["TRIGGER_API_KEY"],
|
||||
verbose: false,
|
||||
ioLogLocalEnabled: true,
|
||||
});
|
||||
|
||||
const sendgrid = new SendGrid({
|
||||
id: "sendgrid-client",
|
||||
apiKey: process.env.SENDGRID_API_KEY!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "send-Sendgrid-email",
|
||||
name: "Send Sendgrid Email",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "send.email",
|
||||
schema: z.object({
|
||||
to: z.union([z.string(), z.array(z.string())]),
|
||||
subject: z.string(),
|
||||
text: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
sendgrid,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.sendgrid.sendEmail("📧", {
|
||||
to: payload.to,
|
||||
subject: payload.subject,
|
||||
text: payload.text,
|
||||
//this email must be verified in SendGrid, otherwise you'll get a forbidden error
|
||||
from: process.env.SENDGRID_FROM_EMAIL!,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
createExpressServer(client);
|
||||
@@ -88,6 +88,7 @@ client.defineJob({
|
||||
}),
|
||||
integrations: {
|
||||
openai,
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
if (!payload.record.name) {
|
||||
@@ -98,8 +99,17 @@ client.defineJob({
|
||||
data: { publicUrl },
|
||||
} = supabase.native.storage.from("example_bucket").getPublicUrl(payload.record.name);
|
||||
|
||||
// Use the native supabase client to get a signed url
|
||||
const { error, data } = await io.supabase.client.storage
|
||||
.from("example_bucket")
|
||||
.createSignedUrl(payload.record.name, 60);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const imageVariation = await io.openai.createImageVariation("variation-image", {
|
||||
image: publicUrl,
|
||||
image: data.signedUrl,
|
||||
n: 2,
|
||||
response_format: "url",
|
||||
size: "512x512",
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"@trigger.dev/supabase": ["../../integrations/supabase/src/index"],
|
||||
"@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"],
|
||||
"@trigger.dev/stripe": ["../../integrations/stripe/src/index"],
|
||||
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"]
|
||||
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"],
|
||||
"@trigger.dev/sendgrid": ["../../integrations/sendgrid/src/index"],
|
||||
"@trigger.dev/sendgrid/*": ["../../integrations/sendgrid/src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"typescript": "5.0.4",
|
||||
"zod": "3.21.4",
|
||||
"@trigger.dev/supabase": "workspace:*",
|
||||
"@trigger.dev/stripe": "workspace:*"
|
||||
"@trigger.dev/stripe": "workspace:*",
|
||||
"@trigger.dev/sendgrid": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trigger.dev/cli": "workspace:*",
|
||||
@@ -43,4 +44,4 @@
|
||||
"trigger.dev": {
|
||||
"endpointId": "nextjs-example"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"lib": ["dom", "dom.iterable", "ES2015"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"ES2015"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -15,31 +19,87 @@
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
|
||||
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
|
||||
"@trigger.dev/nextjs": ["../../packages/nextjs/src/index"],
|
||||
"@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"],
|
||||
"@trigger.dev/core": ["../../packages/core/src/index"],
|
||||
"@trigger.dev/core/*": ["../../packages/core/src/*"],
|
||||
"@trigger.dev/integration-kit": ["../../packages/integration-kit/src/index"],
|
||||
"@trigger.dev/integration-kit/*": ["../../packages/integration-kit/src/*"],
|
||||
"@trigger.dev/github": ["../../integrations/github/src/index"],
|
||||
"@trigger.dev/github/*": ["../../integrations/github/src/*"],
|
||||
"@trigger.dev/slack": ["../../integrations/slack/src/index"],
|
||||
"@trigger.dev/slack/*": ["../../integrations/slack/src/*"],
|
||||
"@trigger.dev/openai": ["../../integrations/openai/src/index"],
|
||||
"@trigger.dev/openai/*": ["../../integrations/openai/src/*"],
|
||||
"@trigger.dev/resend": ["../../integrations/resend/src/index"],
|
||||
"@trigger.dev/resend/*": ["../../integrations/resend/src/*"],
|
||||
"@trigger.dev/typeform": ["../../integrations/typeform/src/index"],
|
||||
"@trigger.dev/typeform/*": ["../../integrations/typeform/src/*"],
|
||||
"@trigger.dev/plain": ["../../integrations/plain/src/index"],
|
||||
"@trigger.dev/plain/*": ["../../integrations/plain/src/*"],
|
||||
"@trigger.dev/supabase": ["../../integrations/supabase/src/index"],
|
||||
"@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"],
|
||||
"@trigger.dev/stripe": ["../../integrations/stripe/src/index"],
|
||||
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@trigger.dev/sdk": [
|
||||
"../../packages/trigger-sdk/src/index"
|
||||
],
|
||||
"@trigger.dev/sdk/*": [
|
||||
"../../packages/trigger-sdk/src/*"
|
||||
],
|
||||
"@trigger.dev/nextjs": [
|
||||
"../../packages/nextjs/src/index"
|
||||
],
|
||||
"@trigger.dev/nextjs/*": [
|
||||
"../../packages/nextjs/src/*"
|
||||
],
|
||||
"@trigger.dev/core": [
|
||||
"../../packages/core/src/index"
|
||||
],
|
||||
"@trigger.dev/core/*": [
|
||||
"../../packages/core/src/*"
|
||||
],
|
||||
"@trigger.dev/integration-kit": [
|
||||
"../../packages/integration-kit/src/index"
|
||||
],
|
||||
"@trigger.dev/integration-kit/*": [
|
||||
"../../packages/integration-kit/src/*"
|
||||
],
|
||||
"@trigger.dev/github": [
|
||||
"../../integrations/github/src/index"
|
||||
],
|
||||
"@trigger.dev/github/*": [
|
||||
"../../integrations/github/src/*"
|
||||
],
|
||||
"@trigger.dev/slack": [
|
||||
"../../integrations/slack/src/index"
|
||||
],
|
||||
"@trigger.dev/slack/*": [
|
||||
"../../integrations/slack/src/*"
|
||||
],
|
||||
"@trigger.dev/openai": [
|
||||
"../../integrations/openai/src/index"
|
||||
],
|
||||
"@trigger.dev/openai/*": [
|
||||
"../../integrations/openai/src/*"
|
||||
],
|
||||
"@trigger.dev/resend": [
|
||||
"../../integrations/resend/src/index"
|
||||
],
|
||||
"@trigger.dev/resend/*": [
|
||||
"../../integrations/resend/src/*"
|
||||
],
|
||||
"@trigger.dev/typeform": [
|
||||
"../../integrations/typeform/src/index"
|
||||
],
|
||||
"@trigger.dev/typeform/*": [
|
||||
"../../integrations/typeform/src/*"
|
||||
],
|
||||
"@trigger.dev/plain": [
|
||||
"../../integrations/plain/src/index"
|
||||
],
|
||||
"@trigger.dev/plain/*": [
|
||||
"../../integrations/plain/src/*"
|
||||
],
|
||||
"@trigger.dev/supabase": [
|
||||
"../../integrations/supabase/src/index"
|
||||
],
|
||||
"@trigger.dev/supabase/*": [
|
||||
"../../integrations/supabase/src/*"
|
||||
],
|
||||
"@trigger.dev/stripe": [
|
||||
"../../integrations/stripe/src/index"
|
||||
],
|
||||
"@trigger.dev/stripe/*": [
|
||||
"../../integrations/stripe/src/*"
|
||||
],
|
||||
"@trigger.dev/sendgrid": [
|
||||
"../../integrations/sendgrid/src/index"
|
||||
],
|
||||
"@trigger.dev/sendgrid/*": [
|
||||
"../../integrations/sendgrid/src/*"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
@@ -47,6 +107,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/github
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/github",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "The official GitHub integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -29,8 +29,8 @@
|
||||
"@octokit/request": "^6.2.5",
|
||||
"@octokit/request-error": "^4.0.1",
|
||||
"@octokit/webhooks": "^10.4.0",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8",
|
||||
"octokit": "^2.0.14",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/slack
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/openai",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "The official OpenAI integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,8 +25,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"openai": "^3.3.0",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/plain
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/plain",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "The official Plain.com integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -24,8 +24,8 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"@team-plain/typescript-sdk": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/resend
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/resend",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "The official Resend.com integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -24,8 +24,8 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"resend": "^0.9.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# @trigger.dev/sendgrid
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2bedc779: feature: initial work for sendgrid integration
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
# @trigger.dev/sendgrid
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@trigger.dev/sendgrid",
|
||||
"version": "2.0.8",
|
||||
"description": "Trigger.dev integration for @sendgrid/mail",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist/index.js",
|
||||
"dist/index.d.ts",
|
||||
"dist/index.js.map"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "16.x",
|
||||
"rimraf": "^3.0.2",
|
||||
"tsup": "7.1.x",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"build": "npm run clean && npm run build:tsup",
|
||||
"build:tsup": "tsup",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { IntegrationClient, TriggerIntegration } from "@trigger.dev/sdk";
|
||||
import sgMail from "@sendgrid/mail";
|
||||
|
||||
import type { AuthenticatedTask } from "@trigger.dev/sdk";
|
||||
|
||||
type SendEmailData = Parameters<(typeof sgMail)["send"]>[0];
|
||||
export const sendEmail: AuthenticatedTask<typeof sgMail, SendEmailData, void> = {
|
||||
run: async (params, client) => {
|
||||
await client.send(params);
|
||||
},
|
||||
init: (params) => {
|
||||
const subjectProperty = Array.isArray(params)
|
||||
? []
|
||||
: params.subject
|
||||
? [{ label: "Subject", text: params.subject }]
|
||||
: [];
|
||||
|
||||
return {
|
||||
name: "Send Email",
|
||||
params,
|
||||
icon: "sendgrid",
|
||||
properties: [
|
||||
{
|
||||
label: "From",
|
||||
text: Array.isArray(params)
|
||||
? getEmailFromEmailData(params[0].from)
|
||||
: getEmailFromEmailData(params.from),
|
||||
},
|
||||
{
|
||||
label: "To",
|
||||
text: Array.isArray(params)
|
||||
? getEmailFromEmailData(params[0]?.to)
|
||||
: getEmailFromEmailData(params?.to),
|
||||
},
|
||||
...subjectProperty,
|
||||
],
|
||||
retry: {
|
||||
limit: 8,
|
||||
factor: 1.8,
|
||||
minTimeoutInMs: 500,
|
||||
maxTimeoutInMs: 30000,
|
||||
randomize: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const tasks = {
|
||||
sendEmail,
|
||||
};
|
||||
|
||||
export type SendGridIntegrationOptions = {
|
||||
id: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
export class SendGrid
|
||||
implements TriggerIntegration<IntegrationClient<typeof sgMail, typeof tasks>>
|
||||
{
|
||||
client: IntegrationClient<typeof sgMail, typeof tasks>;
|
||||
|
||||
constructor(private options: SendGridIntegrationOptions) {
|
||||
if (!options.apiKey) {
|
||||
throw new Error(`Can't create SendGrid integration (${options.id}) as apiKey was undefined`);
|
||||
}
|
||||
|
||||
sgMail.setApiKey(options.apiKey);
|
||||
|
||||
this.client = {
|
||||
tasks,
|
||||
usesLocalAuth: true,
|
||||
client: sgMail,
|
||||
auth: {
|
||||
apiKey: options.apiKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.options.id;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
return { id: "sendgrid", name: "SendGrid" };
|
||||
}
|
||||
}
|
||||
|
||||
type EmailData = string | { name?: string; email: string };
|
||||
type EmailDataArray = EmailData | EmailData[];
|
||||
|
||||
function getEmailFromEmailData(emailData: EmailDataArray | undefined): string {
|
||||
if (emailData === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (Array.isArray(emailData)) {
|
||||
return emailData.map(getEmailFromEmailData).join(", ");
|
||||
}
|
||||
|
||||
if (typeof emailData === "string") {
|
||||
return emailData;
|
||||
}
|
||||
return emailData.email;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSources": false,
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node16",
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"preserveWatchOutput": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["es2019"],
|
||||
"module": "commonjs",
|
||||
"target": "es2021"
|
||||
},
|
||||
"include": ["./src/**/*.ts", "tsup.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: "main",
|
||||
entry: ["./src/index.ts"],
|
||||
outDir: "./dist",
|
||||
platform: "node",
|
||||
format: ["cjs"],
|
||||
legacyOutput: true,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
bundle: true,
|
||||
splitting: false,
|
||||
dts: true,
|
||||
treeshake: {
|
||||
preset: "smallest",
|
||||
},
|
||||
esbuildPlugins: [],
|
||||
external: ["http", "https", "util", "events", "tty", "os", "timers"],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @trigger.dev/slack
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/slack",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "The official Slack integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@slack/web-api": "^6.8.1",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/stripe
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/stripe",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "Trigger.dev integration for stripe",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"stripe": "^12.14.0",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/supabase
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/supabase",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "Trigger.dev integration for @supabase/supabase-js",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.26.0",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"supabase-management-js": "^0.1.4",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/typeform
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@2.0.8
|
||||
- @trigger.dev/sdk@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/typeform",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "The official Typeform integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@typeform/api-client": "^1.8.0",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.7",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.7",
|
||||
"@trigger.dev/sdk": "workspace:^2.0.8",
|
||||
"@trigger.dev/integration-kit": "workspace:^2.0.8",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# create-trigger
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c246578a: Made the CLI error more helpful if you’re not running your Next.js app
|
||||
- d395b957: fix: Handle ngrok config upgrade error in createTunnel function
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/cli",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "The Trigger.dev CLI",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import childProcess from "child_process";
|
||||
import chokidar from "chokidar";
|
||||
import dotenv from "dotenv";
|
||||
import fs from "fs/promises";
|
||||
import ngrok from "ngrok";
|
||||
import fetch from "node-fetch";
|
||||
import ora from "ora";
|
||||
import ora, { Ora } from "ora";
|
||||
import pathModule from "path";
|
||||
import util from "util";
|
||||
import { z } from "zod";
|
||||
import { CLOUD_API_URL } from "../consts.js";
|
||||
import { telemetryClient } from "../telemetry/telemetry.js";
|
||||
import { pathExists, readFile } from "../utils/fileSystem.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import { resolvePath } from "../utils/parseNameAndPath.js";
|
||||
import { TriggerApi } from "../utils/triggerApi.js";
|
||||
import { CLOUD_API_URL } from "../consts.js";
|
||||
|
||||
const asyncExecFile = util.promisify(childProcess.execFile);
|
||||
|
||||
export const DevCommandOptionsSchema = z.object({
|
||||
port: z.coerce.number(),
|
||||
@@ -80,7 +84,9 @@ export async function devCommand(path: string, anyOptions: any) {
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`❌ [trigger.dev] No server found on port ${options.port}.`);
|
||||
logger.error(
|
||||
`❌ [trigger.dev] No server found on port ${options.port}. Make sure your Next.js app is running and try again.`
|
||||
);
|
||||
telemetryClient.dev.failed("no_server_found", options);
|
||||
return;
|
||||
}
|
||||
@@ -282,7 +288,9 @@ async function resolveEndpointUrl(apiUrl: string, port: number) {
|
||||
|
||||
// Setup tunnel
|
||||
const tunnelSpinner = ora(`🚇 Creating tunnel`).start();
|
||||
const tunnelUrl = await createTunnel(port);
|
||||
|
||||
const tunnelUrl = await createTunnel(port, tunnelSpinner);
|
||||
|
||||
if (tunnelUrl) {
|
||||
tunnelSpinner.succeed(`🚇 Created tunnel: ${tunnelUrl}`);
|
||||
}
|
||||
@@ -290,15 +298,38 @@ async function resolveEndpointUrl(apiUrl: string, port: number) {
|
||||
return tunnelUrl;
|
||||
}
|
||||
|
||||
async function createTunnel(port: number) {
|
||||
async function createTunnel(port: number, spinner: Ora) {
|
||||
try {
|
||||
return await ngrok.connect(port);
|
||||
} catch (e) {
|
||||
logger.error(`Ngrok failed to create a tunnel for port ${port}.\n${e}`);
|
||||
} catch (error: any) {
|
||||
if (
|
||||
typeof error.message === "string" &&
|
||||
error.message.includes("`version` property is required")
|
||||
) {
|
||||
await upgradeNgrokConfig(spinner);
|
||||
|
||||
try {
|
||||
return await ngrok.connect(port);
|
||||
} catch (retryError) {
|
||||
spinner.fail(
|
||||
`Ngrok failed to create a tunnel for port ${port} after configuration upgrade.\n${retryError}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function upgradeNgrokConfig(spinner: Ora) {
|
||||
try {
|
||||
await asyncExecFile("ngrok", ["config", "upgrade"]);
|
||||
spinner.info("Ngrok configuration upgraded successfully.");
|
||||
} catch (error) {
|
||||
spinner.fail(`Failed to upgrade ngrok configuration.\n${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshEndpoint(apiClient: TriggerApi, endpointId: string, endpointUrl: string) {
|
||||
try {
|
||||
const response = await apiClient.registerEndpoint({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# internal-platform
|
||||
|
||||
## 2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/core",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.8",
|
||||
"description": "Core code used across the Trigger.dev SDK and platform",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../.env
|
||||