Compare commits

...

28 Commits

Author SHA1 Message Date
Eric Allam 67b5365d50 Fix pnpm lock 2023-11-01 10:16:52 +00:00
github-actions[bot] ea25c37a10 chore: Update version for release (#709)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-11-01 09:19:10 +00:00
Eric Allam c1710ae72d fix: Buffer import breaks @trigger.dev/react (#714) 2023-10-31 15:28:55 +00:00
nicktrn 9c5a7cf073 Fix: Allow errors and special values on Airtable field set (#697)
* Allow errors and special values on field set

* Add changeset
2023-10-31 10:17:05 +00:00
Eric Allam 8eedf994af Fixed call -> on 2023-10-30 21:58:04 +00:00
nicktrn 2c91c492df Feature: Cross-instance notification system (#689)
* Add notification system

* Graceful worker shutdown on migration notification

* Add notification catalog

* Rename pgListen call to on
2023-10-30 17:08:16 +00:00
Hemachandar b859abdb66 fix: remove unstable createRemixStub in storybook (#707) 2023-10-30 17:04:29 +00:00
James Ritchie 0876b87526 No Tasks in a Run warning message (#688)
* Card hover state is now grey

* Added a callout warning if you don’t have any tasks

* Conditionally show the no-task warning at the top of the run

* New job to test the no-task warning message

* Created a new example Job for the CLI that uses Tasks

* Updated the Next.js cli joke job

* Updated the Remix app cli job example

* Updated the Astro app cli job example

* Made sure the example job works in remix

* Added changeset

---------

Co-authored-by: Eric Allam <eric@trigger.dev>
2023-10-30 16:24:46 +00:00
Hemachandar 9c4be40adc fix: append nanoid for dynamic trigger reg event (#628)
* fix: append nanoid for dynamic trigger reg event

* use idempotency-key as event-id

* add changeset
2023-10-30 16:24:03 +00:00
D-K-P 2635f4bd10 typo fix 2023-10-30 14:36:17 +00:00
nicktrn 401b7ee03d Set NODE_ENV to production in supabase docs 2023-10-28 13:09:01 +00:00
Eric Allam 6881de84ab Fix pnpm lock file 2023-10-26 20:04:03 +01:00
github-actions[bot] 0ca8296b9a chore: Update version for release (#686)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-26 20:03:37 +01:00
Eric Allam 55946fcdb7 Update @trigger.dev/nextjs to support Next.js 14 (#690) 2023-10-26 19:45:19 +01:00
Eric Allam c0f9f10b07 Upgrade to Remix v2.1.0 2023-10-26 15:54:21 +01:00
Eric Allam e80aa2826d @trigger.dev/express: Remove web-fetch dependency and use native node Request/Response 2023-10-26 13:42:20 +01:00
NIXEY 1be25d7639 BlankRow centered (#678) 2023-10-26 13:35:15 +01:00
ROMIT PAL 3c3a5e1f28 added a missing CODE OF CONDUCT file (#677)
Incorporating a Code of Conduct in your GitHub repository is crucial for promoting an inclusive, respectful community. It establishes guidelines, minimizes conflicts, safeguards participants, and adheres to legal standards. Additionally, it demonstrates professionalism and aligns with GitHub's best practices, contributing to a welcoming and harmonious environment, which, in turn, enhances collaboration and the project's overall success.
2023-10-26 13:30:14 +01:00
Vineet J Karni 8e741c7218 Added warning ⚠️ - No support for long-running servers (#669) 2023-10-26 13:18:28 +01:00
Hemachandar 83455e3ad4 feat: implement API key regeneration (#649) 2023-10-26 13:15:50 +01:00
Ronit Panda f0f0420280 feat: adds helm charts (#636)
* feat: adds helm charts template and adds postgres deps

* feat: adds all depl (barebones)

* feat: adds all depl (barebones)

* fix: removes print

* fix: image pulling issue

* fix: templatisation complete

* style: cleanup

* feat: works

* feat: adds ingress and some docs

* fix: ingress bug --> routing to cluster works

* feat: completes docs v1 todo --> needs hosting of the helm chart on chosen platform
2023-10-26 11:50:59 +01:00
Hemachandar c4533c36cb fix: set error message in runTask & executeJob (#634)
* fix: set error message in runTask & executeJob

* add changeset
2023-10-26 11:46:09 +01:00
Eric Allam f8e034c190 Fixed import that was causing typecheck to fail 2023-10-26 11:43:23 +01:00
Eric Allam 21f62e780c Added tests and refactored response to use camelCase 2023-10-26 11:30:49 +01:00
hmacr c3564ca0d4 fix doc 2023-10-26 11:18:11 +01:00
hmacr 6e1b8a11d4 feat: allow cancelling job runs for an event-id 2023-10-26 11:18:11 +01:00
Matt Aitken 9900f19b70 Migration: Updates any Endpoint runChunkExecutionLimits that were 60ms to be 60000ms 2023-10-25 15:50:11 +01:00
Matt Aitken 237545754f Indexing errors weren’t being serialized to JSON properly in some cases, so the errors weren’t getting inserted
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 1s
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 21s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
2023-10-24 18:08:46 +01:00
231 changed files with 3717 additions and 1103 deletions
+43
View File
@@ -0,0 +1,43 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct that could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
+1 -1
View File
@@ -20,7 +20,7 @@ All of October we're participating in Hacktoberfest and invite you to join us! W
- Check out our [Hacktoberfest landing page](https://trigger.dev/hacktoberfest) for how to participate and win swag.
- Contribute to either our [/trigger.dev](https://github.com/triggerdotdev/trigger.dev/labels/%F0%9F%8E%83%20hacktoberfest), [/api-reference](https://github.com/triggerdotdev/api-reference/issues?q=is%3Aopen+is%3Aissue+label%3A%F0%9F%8E%83hacktoberfest) or [/jobs-showcase](https://github.com/triggerdotdev/jobs-showcase/labels/%F0%9F%8E%83%20hacktoberfest) repositories and complete issues marked `🎃 Hacktoberfest` to be eligible for swag.
- Join our [Discord](https://discord.gg/JtBAxBr2m3) and get involved in with the community.
- Join our [Discord](https://discord.gg/JtBAxBr2m3) and get involved with the community.
_New to Hacktober? Check out the [Hacktoberfest website](https://hacktoberfest.digitalocean.com/) for more information._
+3 -3
View File
@@ -1,6 +1,6 @@
import type { Preview } from "@storybook/react";
import "../app/tailwind.css";
import { unstable_createRemixStub } from "@remix-run/testing";
import { createRemixStub } from "@remix-run/testing";
import React from "react";
import { LocaleContextProvider } from "../app/components/primitives/LocaleProvider";
import { OperatingSystemContextProvider } from "../app/components/primitives/OperatingSystemProvider";
@@ -26,10 +26,10 @@ const preview: Preview = {
},
decorators: [
(Story) => {
const RemixStub = unstable_createRemixStub([
const RemixStub = createRemixStub([
{
path: "/*",
element: <Story />,
Component: Story,
},
]);
+2 -1
View File
@@ -36,7 +36,8 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
const [form, { path, feedbackType, message }] = useForm({
id: "accept-invite",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -0,0 +1,107 @@
import { Button } from "../primitives/Buttons";
import { Dialog, DialogContent, DialogTrigger } from "~/components/primitives/Dialog";
import { Header1, Header2 } from "../primitives/Headers";
import { Input } from "../primitives/Input";
import { NamedIcon } from "../primitives/NamedIcon";
import { Paragraph } from "../primitives/Paragraph";
import { Spinner } from "../primitives/Spinner";
import { useState } from "react";
import { useFetcher } from "@remix-run/react";
import { generateTwoRandomWords } from "~/utils/randomWords";
type ModalProps = {
id: string;
title: string;
};
type ModalContentProps = ModalProps & {
randomWord: string;
closeModal: () => void;
};
export function RegenerateApiKeyModal({ id, title }: ModalProps) {
const randomWord = generateTwoRandomWords();
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="tertiary/small" leadingIconClassName="text-dimmed" LeadingIcon="key">
Regenerate
</Button>
</DialogTrigger>
<DialogContent>
<RegenerateApiKeyModalContent
id={id}
title={title}
randomWord={randomWord}
closeModal={() => setOpen(false)}
/>
</DialogContent>
</Dialog>
);
}
const RegenerateApiKeyModalContent = ({ id, title, randomWord, closeModal }: ModalContentProps) => {
const [confirmationText, setConfirmationText] = useState("");
const fetcher = useFetcher();
const isSubmitting = fetcher.state === "submitting";
// form submission completed
if (fetcher.state === "loading") {
closeModal();
}
return (
<div className="flex w-full flex-col items-center gap-y-6">
<div className="flex justify-center">
<Header1>{title} Environment</Header1>
</div>
<Header2 className="rounded border border-rose-500 bg-rose-500/10 px-3.5 py-2 text-center text-bright">
Are you sure you want to regenerate the keys <br />
for this environment?
</Header2>
<Paragraph variant="small" className="px-6 text-center">
<>
This will temporarily break any live jobs in the
<span className="strong text-bright"> {title} Environment</span> until the new API keys
are set in the relevant environment variables.
</>
</Paragraph>
<fetcher.Form
method="post"
action={`/resources/environments/${id}/regenerate-api-key`}
className="mt-2 flex w-full flex-col items-center gap-2"
>
<Paragraph variant="small" className="font-medium">
To confirm, please enter the text:{" "}
<span className="font-bold text-bright">{randomWord}</span>
</Paragraph>
<Input
type="text"
placeholder="Confirmation Text"
fullWidth={false}
value={confirmationText}
onChange={(e) => setConfirmationText(e.target.value)}
/>
<Button
variant="primary/large"
fullWidth
className="mt-4"
disabled={confirmationText !== randomWord}
>
{isSubmitting ? (
<Spinner color="white" />
) : (
<>
<NamedIcon
name="key"
className="mr-1.5 h-4 w-4 text-bright transition group-hover:text-bright"
/>
Regenerate API Keys
</>
)}
</Button>
</fetcher.Form>
</div>
);
};
@@ -46,7 +46,8 @@ export function ConnectToOAuthForm({
const [form, { title, slug, scopes, hasCustomClient, customClientId, customClientSecret }] =
useForm({
lastSubmission: fetcher.data,
// TODO: type this
lastSubmission: fetcher.data as any,
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
@@ -45,7 +45,8 @@ export function UpdateOAuthForm({
const { isManagedCloud } = useFeatures();
const [form, { title, scopes, hasCustomClient, customClientId, customClientSecret }] = useForm({
lastSubmission: fetcher.data,
// TODO: type this
lastSubmission: fetcher.data as any,
onValidate({ formData }) {
return parse(formData, {
schema,
@@ -1,11 +1,12 @@
import { RouteMatch, useMatches } from "@remix-run/react";
import { UIMatch, useMatches } from "@remix-run/react";
import { Fragment, ReactNode } from "react";
import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon";
import { Handle } from "~/utils/handle";
export type BreadcrumbItem = (match: RouteMatch, allMatches: RouteMatch[]) => ReactNode;
export type BreadcrumbItem = (match: UIMatch, allMatches: UIMatch[]) => ReactNode;
export function Breadcrumb() {
const matches = useMatches();
const matches = useMatches() as UIMatch<unknown, Handle>[];
return (
<div className="hidden items-center md:flex">
@@ -1,4 +1,4 @@
import { Link, RouteMatch } from "@remix-run/react";
import { Link, UIMatch } from "@remix-run/react";
import { useState } from "react";
import { useJob } from "~/hooks/useJob";
import { useOrganization } from "~/hooks/useOrganizations";
@@ -15,7 +15,7 @@ import {
PopoverSectionHeader,
} from "../primitives/Popover";
export function JobsMenu({ matches }: { matches: RouteMatch[] }) {
export function JobsMenu({ matches }: { matches: UIMatch[] }) {
const [isOpen, setIsOpen] = useState(false);
const organization = useOrganization(matches);
const project = useProject(matches);
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { Fragment, useState } from "react";
import simplur from "simplur";
import { Badge } from "~/components/primitives/Badge";
@@ -13,7 +13,7 @@ import {
PopoverSectionHeader,
} from "../primitives/Popover";
export function ProjectsMenu({ matches }: { matches: RouteMatch[] }) {
export function ProjectsMenu({ matches }: { matches: UIMatch[] }) {
const [isOpen, setIsOpen] = useState(false);
const organizations = useOrganizations(matches);
const isNewOrgPage = useIsNewOrganizationPage(matches);
+1 -1
View File
@@ -36,7 +36,7 @@ export function RunPanel({
? "border-slate-850"
: "border-slate-900",
onClick && "cursor-pointer",
onClick && !selected && "hover:border-green-500/30",
onClick && !selected && "hover:border-slate-500/30",
className
)}
onClick={() => onClick && onClick()}
+14 -2
View File
@@ -159,6 +159,16 @@ export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps
<div className="grid h-full grid-cols-2 gap-2">
<div className="flex flex-col gap-6 overflow-y-auto py-4 pl-4 pr-2 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
<div>
{(run.tasks.length === 0 || run.tasks.every((t) => t.noop)) && (
<Callout
variant={"warning"}
to="https://trigger.dev/docs/documentation/concepts/tasks"
className="mb-4"
>
This Run completed but it did not use any Tasks this can cause unpredictable
results. Read the docs to view the solution.
</Callout>
)}
<Header2 className="mb-2">Trigger</Header2>
<RunPanel
selected={selectedId === "trigger"}
@@ -313,7 +323,8 @@ function RerunPopover({
const [form, { successRedirect }] = useForm({
id: "rerun",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -386,7 +397,8 @@ export function CancelRun({ runId }: { runId: string }) {
const [form, { redirectUrl }] = useForm({
id: "cancel-run",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: cancelSchema });
},
@@ -28,7 +28,7 @@ import {
} from "../primitives/Table";
import { TaskAttemptStatusLabel } from "./TaskAttemptStatus";
import { TaskStatusIcon } from "./TaskStatus";
import { ClientOnly } from "remix-utils";
import { ClientOnly } from "remix-utils/client-only";
import { Spinner } from "../primitives/Spinner";
import type { DetailedTask } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route";
+80 -56
View File
@@ -1,6 +1,9 @@
import { H } from "@highlight-run/node";
import type { DataFunctionArgs, EntryContext, Headers } from "@remix-run/node"; // or cloudflare/deno
import { Response } from "@remix-run/node"; // or cloudflare/deno
import {
createReadableStreamFromReadable,
type DataFunctionArgs,
type EntryContext,
} from "@remix-run/node"; // or cloudflare/deno
import { RemixServer } from "@remix-run/react";
import { parseAcceptLanguage } from "intl-parse-accept-language";
import isbot from "isbot";
@@ -36,7 +39,7 @@ export default function handleRequest(
// response to render before sending it to the client. This
// ensures that bots can see the full page content.
if (isbot(request.headers.get("user-agent"))) {
return serveTheBots(
return handleBotRequest(
request,
responseStatusCode,
responseHeaders,
@@ -46,7 +49,7 @@ export default function handleRequest(
);
}
return serveBrowsers(
return handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
@@ -56,7 +59,7 @@ export default function handleRequest(
);
}
function serveTheBots(
function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
@@ -65,76 +68,97 @@ function serveTheBots(
platform: OperatingSystemPlatform
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
// Use onAllReady to wait for the entire document to be ready
onAllReady() {
responseHeaders.set("Content-Type", "text/html; charset=utf-8");
let body = new PassThrough();
pipe(body);
resolve(
new Response(body, {
status: responseStatusCode,
headers: responseHeaders,
})
);
},
onShellError(err: unknown) {
reject(err);
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
function serveBrowsers(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
locales: string[],
platform: OperatingSystemPlatform
) {
return new Promise((resolve, reject) => {
let didError = false;
let shellReady = false;
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
// use onShellReady to wait until a suspense boundary is triggered
onShellReady() {
shellReady = true;
responseHeaders.set("Content-Type", "text/html; charset=utf-8");
let body = new PassThrough();
pipe(body);
resolve(
new Response(body, {
status: didError ? 500 : responseStatusCode,
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(err: unknown) {
reject(err);
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
didError = true;
if (shellReady) {
logError(error, request);
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
locales: string[],
platform: OperatingSystemPlatform
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
onShellReady() {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
+4 -4
View File
@@ -1,17 +1,17 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { MatchedProject, useOptionalProject } from "./useProject";
import { useUser } from "./useUser";
export type ProjectJobEnvironment = MatchedProject["environments"][number];
export function useEnvironments(matches?: RouteMatch[]) {
export function useEnvironments(matches?: UIMatch[]) {
const project = useOptionalProject(matches);
if (!project) return;
return project.environments;
}
export function useDevEnvironment(matches?: RouteMatch[]) {
export function useDevEnvironment(matches?: UIMatch[]) {
const user = useUser();
const environments = useEnvironments(matches);
if (!environments) return;
@@ -21,7 +21,7 @@ export function useDevEnvironment(matches?: RouteMatch[]) {
);
}
export function useProdEnvironment(matches?: RouteMatch[]) {
export function useProdEnvironment(matches?: UIMatch[]) {
const environments = useEnvironments(matches);
if (!environments) return;
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route";
@@ -6,7 +6,7 @@ import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedClient = UseDataFunctionReturn<typeof loader>["client"];
export function useOptionalIntegrationClient(matches?: RouteMatch[]) {
export function useOptionalIntegrationClient(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam",
matches,
@@ -15,7 +15,7 @@ export function useOptionalIntegrationClient(matches?: RouteMatch[]) {
return routeMatch?.client;
}
export function useIntegrationClient(matches?: RouteMatch[]) {
export function useIntegrationClient(matches?: UIMatch[]) {
const integration = useOptionalIntegrationClient(matches);
invariant(integration, "Integration must be defined");
return integration;
@@ -1,6 +1,6 @@
import { RouteMatch, useMatches } from "@remix-run/react";
import { UIMatch, useMatches } from "@remix-run/react";
export function useIsProjectChildPage(matches?: RouteMatch[]) {
export function useIsProjectChildPage(matches?: UIMatch[]) {
if (!matches) {
matches = useMatches();
}
+3 -3
View File
@@ -2,14 +2,14 @@ import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route";
import { useChanged } from "./useChanged";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedJob = UseDataFunctionReturn<typeof loader>["job"];
export const jobMatchId =
"routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam";
export function useOptionalJob(matches?: RouteMatch[]) {
export function useOptionalJob(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: jobMatchId,
matches,
@@ -22,7 +22,7 @@ export function useOptionalJob(matches?: RouteMatch[]) {
return routeMatch.projectJobs.find((j) => j.id === routeMatch.job.id);
}
export function useJob(matches?: RouteMatch[]) {
export function useJob(matches?: UIMatch[]) {
const job = useOptionalJob(matches);
invariant(job, "Job must be defined");
return job;
+3 -3
View File
@@ -1,7 +1,7 @@
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
export type ProjectJob = UseDataFunctionReturn<typeof loader>["projectJobs"][number];
@@ -12,7 +12,7 @@ export const jobsMatchId =
// This is only used in the JobsMenu component, which is the breadcrumb job list dropdown.
// This dropdown is only shown once you have selected a job, so we can assume that
// the route above has loaded and we can use the data from it.
export function useOptionalJobs(matches?: RouteMatch[]) {
export function useOptionalJobs(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: jobsMatchId,
matches,
@@ -21,7 +21,7 @@ export function useOptionalJobs(matches?: RouteMatch[]) {
return routeMatch?.projectJobs;
}
export function useJobs(matches?: RouteMatch[]) {
export function useJobs(matches?: UIMatch[]) {
const jobs = useOptionalJobs(matches);
invariant(jobs, "Jobs must be defined");
return jobs;
+6 -6
View File
@@ -4,12 +4,12 @@ import type { loader as orgLoader } from "~/routes/_app.orgs.$organizationSlug/r
import type { loader as appLoader } from "~/routes/_app/route";
import { hydrateObject, useMatchesData } from "~/utils";
import { useChanged } from "./useChanged";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedOrganization = UseDataFunctionReturn<typeof appLoader>["organizations"][number];
export function useOptionalOrganizations(matches?: RouteMatch[]) {
export function useOptionalOrganizations(matches?: UIMatch[]) {
const data = useTypedMatchesData<typeof appLoader>({
id: "routes/_app",
matches,
@@ -17,13 +17,13 @@ export function useOptionalOrganizations(matches?: RouteMatch[]) {
return data?.organizations;
}
export function useOrganizations(matches?: RouteMatch[]) {
export function useOrganizations(matches?: UIMatch[]) {
const orgs = useOptionalOrganizations(matches);
invariant(orgs, "No organizations found in loader.");
return orgs;
}
export function useOptionalOrganization(matches?: RouteMatch[]) {
export function useOptionalOrganization(matches?: UIMatch[]) {
const orgs = useOptionalOrganizations(matches);
const org = useTypedMatchesData<typeof orgLoader>({
id: "routes/_app.orgs.$organizationSlug",
@@ -37,13 +37,13 @@ export function useOptionalOrganization(matches?: RouteMatch[]) {
return orgs.find((o) => o.id === org.organization.id);
}
export function useOrganization(matches?: RouteMatch[]) {
export function useOrganization(matches?: UIMatch[]) {
const org = useOptionalOrganization(matches);
invariant(org, "No organization found in loader.");
return org;
}
export function useIsNewOrganizationPage(matches?: RouteMatch[]): boolean {
export function useIsNewOrganizationPage(matches?: UIMatch[]): boolean {
const data = useTypedMatchesData<any>({
id: "routes/_app.orgs.new",
matches,
+3 -3
View File
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam/route";
@@ -9,7 +9,7 @@ export type MatchedProject = UseDataFunctionReturn<typeof loader>["project"];
export const projectMatchId = "routes/_app.orgs.$organizationSlug.projects.$projectParam";
export function useOptionalProject(matches?: RouteMatch[]) {
export function useOptionalProject(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: projectMatchId,
matches,
@@ -18,7 +18,7 @@ export function useOptionalProject(matches?: RouteMatch[]) {
return routeMatch?.project;
}
export function useProject(matches?: RouteMatch[]) {
export function useProject(matches?: UIMatch[]) {
const project = useOptionalProject(matches);
invariant(project, "Project must be defined");
return project;
@@ -1,5 +1,5 @@
import { useEffect } from "react";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { projectPath, projectStreamingPath } from "~/utils/pathBuilder";
import { useProject } from "./useProject";
import { useOrganization } from "./useOrganizations";
+3 -3
View File
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route";
@@ -7,7 +7,7 @@ import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedRun = UseDataFunctionReturn<typeof runLoader>["run"];
export function useOptionalRun(matches?: RouteMatch[]) {
export function useOptionalRun(matches?: UIMatch[]) {
const project = useOptionalProject(matches);
const routeMatch = useTypedMatchesData<typeof runLoader>({
id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam",
@@ -21,7 +21,7 @@ export function useOptionalRun(matches?: RouteMatch[]) {
return routeMatch.run;
}
export function useRun(matches?: RouteMatch[]) {
export function useRun(matches?: UIMatch[]) {
const run = useOptionalRun(matches);
invariant(run, "Run must be present");
return run;
+4 -4
View File
@@ -1,4 +1,4 @@
import { RouteMatch, useMatches } from "@remix-run/react";
import { UIMatch, useMatches } from "@remix-run/react";
import { RemixSerializedType, UseDataFunctionReturn, deserializeRemix } from "remix-typedjson";
type AppData = any;
@@ -8,7 +8,7 @@ function useTypedDataFromMatches<T = AppData>({
matches,
}: {
id: string;
matches: RouteMatch[];
matches: UIMatch[];
}): UseDataFunctionReturn<T> | undefined {
const match = matches.find((m) => m.id === id);
return useTypedMatchData<T>(match);
@@ -19,7 +19,7 @@ export function useTypedMatchesData<T = AppData>({
matches,
}: {
id: string;
matches?: RouteMatch[];
matches?: UIMatch[];
}): UseDataFunctionReturn<T> | undefined {
if (!matches) {
matches = useMatches();
@@ -29,7 +29,7 @@ export function useTypedMatchesData<T = AppData>({
}
export function useTypedMatchData<T = AppData>(
match: RouteMatch | undefined
match: UIMatch | undefined
): UseDataFunctionReturn<T> | undefined {
if (!match) {
return undefined;
+3 -3
View File
@@ -1,11 +1,11 @@
import type { User } from "~/models/user.server";
import { useMatchesData } from "~/utils";
import { useChanged } from "./useChanged";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
import { loader } from "~/root";
export function useOptionalUser(matches?: RouteMatch[]): User | undefined {
export function useOptionalUser(matches?: UIMatch[]): User | undefined {
const routeMatch = useTypedMatchesData<typeof loader>({
id: "root",
matches,
@@ -14,7 +14,7 @@ export function useOptionalUser(matches?: RouteMatch[]): User | undefined {
return routeMatch?.user ?? undefined;
}
export function useUser(matches?: RouteMatch[]): User {
export function useUser(matches?: UIMatch[]): User {
const maybeUser = useOptionalUser(matches);
if (!maybeUser) {
throw new Error(
+101
View File
@@ -0,0 +1,101 @@
import { RuntimeEnvironmentType, type RuntimeEnvironment } from "@trigger.dev/database";
import { prisma } from "~/db.server";
import { customAlphabet } from "nanoid";
const apiKeyId = customAlphabet(
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
12
);
type RegenerateAPIKeyInput = {
userId: string;
environmentId: string;
};
export async function regenerateApiKey({ userId, environmentId }: RegenerateAPIKeyInput) {
const environment = await prisma.runtimeEnvironment.findUnique({
where: {
id: environmentId,
},
include: {
organization: true,
project: true,
},
});
if (!environment) {
throw new Error("Environment does not exist");
}
// check if the user is part of the org
const organization = await prisma.organization.findFirst({
where: {
id: environment.organization.id,
members: { some: { userId } },
},
});
if (!organization) {
throw new Error("User does not have permission to regenerate API key");
}
// check if it is the user's dev environment
if (environment.type === RuntimeEnvironmentType.DEVELOPMENT) {
if (!environment.orgMemberId) {
throw new Error("User does not have permission to regenerate API key");
}
const orgMember = await prisma.orgMember.findFirst({
where: {
organizationId: organization.id,
userId: userId,
id: environment.orgMemberId,
},
});
if (!orgMember) {
throw new Error("User does not have permission to regenerate API key");
}
}
// generate and store new keys
const newApiKey = createApiKeyForEnv(environment.type);
const newPkApiKey = createPkApiKeyForEnv(environment.type);
const updatedEnviroment = await prisma.runtimeEnvironment.update({
data: {
apiKey: newApiKey,
pkApiKey: newPkApiKey,
},
where: {
id: environmentId,
},
});
return updatedEnviroment;
}
export function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `tr_${envSlug(envType)}_${apiKeyId(20)}`;
}
export function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
}
export function envSlug(environmentType: RuntimeEnvironment["type"]) {
switch (environmentType) {
case "DEVELOPMENT": {
return "dev";
}
case "PRODUCTION": {
return "prod";
}
case "STAGING": {
return "stg";
}
case "PREVIEW": {
return "prev";
}
}
}
+1 -25
View File
@@ -9,6 +9,7 @@ import { customAlphabet } from "nanoid";
import slug from "slug";
import { prisma, PrismaClientOrTransaction } from "~/db.server";
import { createProject } from "./project.server";
import { createApiKeyForEnv, createPkApiKeyForEnv, envSlug } from "./api-key.server";
export type { Organization };
@@ -157,28 +158,3 @@ export async function createEnvironment(
},
});
}
function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `tr_${envSlug(envType)}_${apiKeyId(20)}`;
}
function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
}
function envSlug(environmentType: RuntimeEnvironment["type"]) {
switch (environmentType) {
case "DEVELOPMENT": {
return "dev";
}
case "PRODUCTION": {
return "prod";
}
case "STAGING": {
return "stg";
}
case "PREVIEW": {
return "prev";
}
}
}
+18 -1
View File
@@ -15,6 +15,8 @@ import omit from "lodash.omit";
import { z } from "zod";
import { PrismaClient, PrismaClientOrTransaction } from "~/db.server";
import { workerLogger as logger } from "~/services/logger.server";
import { PgListenService } from "~/services/db/pgListen.server";
import { safeJsonParse } from "~/utils/json";
export interface MessageCatalogSchema {
[key: string]: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>;
@@ -164,8 +166,23 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
this.#logDebug("pool:create", { attempts });
});
this.#runner?.events.on("pool:listen:success", ({ workerPool, client }) => {
this.#runner?.events.on("pool:listen:success", async ({ workerPool, client }) => {
this.#logDebug("pool:listen:success");
// hijack client instance to listen and react to incoming NOTIFY events
const pgListen = new PgListenService(client, this.#name, logger);
await pgListen.on("trigger:graphile:migrate", async ({ latestMigration }) => {
this.#logDebug("Detected incoming migration", { latestMigration });
if (latestMigration > 10) {
// already migrated past v0.14 - nothing to do
return;
}
// simulate SIGTERM to trigger graceful shutdown
this._handleSignal("SIGTERM");
});
});
this.#runner?.events.on("pool:listen:error", ({ error }) => {
@@ -156,6 +156,7 @@ export class RunPresenter {
completedAt: true,
style: true,
parentId: true,
noop: true,
runConnection: {
select: {
integration: {
+13 -10
View File
@@ -1,7 +1,10 @@
import type { LinksFunction, LoaderArgs } from "@remix-run/node";
import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react";
import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node";
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
import { metaV1 } from "@remix-run/v1-meta";
import { TypedMetaFunction, typedjson, useTypedLoaderData } from "remix-typedjson";
import { ExternalScripts } from "remix-utils/external-scripts";
import type { ToastMessage } from "~/models/message.server";
import { commitSession, getSession } from "~/models/message.server";
import tailwindStylesheetUrl from "~/tailwind.css";
@@ -11,24 +14,24 @@ import { AppContainer, MainCenteredContainer } from "./components/layout/AppLayo
import { Toast } from "./components/primitives/Toast";
import { env } from "./env.server";
import { featuresForRequest } from "./features.server";
import { useHighlight } from "./hooks/useHighlight";
import { usePostHog } from "./hooks/usePostHog";
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 }];
};
export const meta: TypedMetaFunction<typeof loader> = ({ data }) => ({
title: `Trigger.dev${appEnvTitleTag(data?.appEnv)}`,
charset: "utf-8",
viewport: "width=1024, initial-scale=1",
});
export const meta: TypedMetaFunction<typeof loader> = (args) => {
return metaV1(args, {
title: `Trigger.dev${appEnvTitleTag(args.data.appEnv)}`,
charset: "utf-8",
viewport: "width=1024, initial-scale=1",
});
};
export const loader = async ({ request }: LoaderArgs) => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const session = await getSession(request.headers.get("cookie"));
const toastMessage = session.get("toastMessage") as ToastMessage;
const posthogProjectKey = env.POSTHOG_PROJECT_KEY;
@@ -1,4 +1,4 @@
import { LoaderArgs, json } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { redirect } from "remix-typedjson";
import { LinkButton } from "~/components/primitives/Buttons";
import { useOptionalOrganizations } from "~/hooks/useOrganizations";
@@ -11,7 +11,7 @@ import { Link } from "@remix-run/react";
import { NamedIcon } from "~/components/primitives/NamedIcon";
import { Paragraph } from "~/components/primitives/Paragraph";
export const loader = async ({ request }: LoaderArgs) => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
//if there are invites then we should redirect to the invites page
@@ -113,7 +113,8 @@ export default function Page() {
const [form, { name, email, marketingEmails }] = useForm({
id: "account",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: createSchema() });
},
@@ -14,12 +14,12 @@ import { useOrganization } from "~/hooks/useOrganizations";
import { OrganizationParamsSchema, jobPath, organizationTeamPath } from "~/utils/pathBuilder";
import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader";
import { Link } from "@remix-run/react/dist/components";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { OrgUsagePresenter } from "~/presenters/OrgUsagePresenter.server";
import { requireUserId } from "~/services/session.server";
export async function loader({ params, request }: LoaderArgs) {
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const { organizationSlug } = OrganizationParamsSchema.parse(params);
@@ -91,7 +91,8 @@ export default function Page() {
const [form, { emails }] = useForm({
id: "invite-members",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,5 +1,5 @@
import { ArrowUpIcon } from "@heroicons/react/24/solid";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { FrameworkSelector } from "~/components/frameworks/FrameworkSelector";
import { JobsTable } from "~/components/jobs/JobsTable";
@@ -34,7 +34,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = ProjectParamSchema.parse(params);
@@ -1,9 +1,9 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { EnvironmentsStreamPresenter } from "~/presenters/EnvironmentsStreamPresenter.server";
import { requireUserId } from "~/services/session.server";
import { ProjectParamSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = ProjectParamSchema.parse(params);
@@ -2,8 +2,12 @@ import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { useFetcher, useRevalidator } from "@remix-run/react";
import { useEffect } from "react";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { InlineCode } from "~/components/code/InlineCode";
import {
EndpointIndexStatusIcon,
EndpointIndexStatusLabel,
} from "~/components/environments/EndpointIndexStatus";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { Button } from "~/components/primitives/Buttons";
import { Callout, CalloutVariant } from "~/components/primitives/Callout";
@@ -20,12 +24,6 @@ import { ClientEndpoint } from "~/presenters/EnvironmentsPresenter.server";
import { endpointStreamingPath } from "~/utils/pathBuilder";
import { EndpointIndexStatus, RuntimeEnvironmentType } from "../../../../../packages/database/src";
import { bodySchema } from "../resources.environments.$environmentParam.endpoint";
import {
EndpointIndexStatusIcon,
EndpointIndexStatusLabel,
endpointIndexStatusTitle,
} from "~/components/environments/EndpointIndexStatus";
import { CodeBlock } from "~/components/code/CodeBlock";
type ConfigureEndpointSheetProps = {
slug: string;
@@ -39,7 +37,7 @@ export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEnd
const [form, { url, clientSlug }] = useForm({
id: "endpoint-url",
lastSubmission: setEndpointUrlFetcher.data,
lastSubmission: setEndpointUrlFetcher.data as any,
onValidate({ formData }) {
return parse(formData, { schema: bodySchema });
},
@@ -40,7 +40,8 @@ export function FirstEndpointSheet({ projectId, environments }: FirstEndpointShe
const setEndpointUrlFetcher = useFetcher();
const [form, { url, environmentId }] = useForm({
id: "new-endpoint-url",
lastSubmission: setEndpointUrlFetcher.data,
// TODO: type this
lastSubmission: setEndpointUrlFetcher.data as any,
onValidate({ formData }) {
return parse(formData, { schema: bodySchema });
},
@@ -1,8 +1,8 @@
import { useRevalidator } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useEffect, useMemo, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import {
EndpointIndexStatusIcon,
EndpointIndexStatusLabel,
@@ -44,8 +44,9 @@ import { requestUrl } from "~/utils/requestUrl.server";
import { RuntimeEnvironmentType } from "../../../../../packages/database/src";
import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet";
import { FirstEndpointSheet } from "./FirstEndpointSheet";
import { RegenerateApiKeyModal } from "~/components/environments/RegenerateApiKeyModal";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = ProjectParamSchema.parse(params);
@@ -148,11 +149,17 @@ export default function Page() {
</Paragraph>
<div className="mt-4 flex flex-col gap-6">
{environments.map((environment) => (
<div key={environment.id}>
<Header3 className="flex items-center gap-1">
<EnvironmentLabel environment={environment} /> Environment
</Header3>
<div className="mt-2 inline-flex flex-col gap-3">
<div key={environment.id} className="w-[400px]">
<div className="flex justify-between">
<Header3 className="flex items-center gap-1">
<EnvironmentLabel environment={environment} /> Environment
</Header3>
<RegenerateApiKeyModal
id={environment.id}
title={environmentTitle(environment)}
/>
</div>
<div className="mt-3 inline-flex w-full flex-col gap-3">
<ClipboardField
className="w-full max-w-none"
secure
@@ -1,5 +1,5 @@
import { ChevronRightIcon } from "@heroicons/react/24/solid";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useCallback, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { Feedback } from "~/components/Feedback";
@@ -56,7 +56,7 @@ import {
integrationClientPath,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { HowToUseThisIntegration } from "~/components/helpContent/HelpContentText";
import { JobSkeleton } from "~/components/jobs/JobSkeleton";
@@ -21,7 +21,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, clientParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { connectionType } from "~/components/integrations/connectionType";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
@@ -18,7 +18,7 @@ import { requireUserId } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, clientParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
import { Paragraph } from "~/components/primitives/Paragraph";
@@ -7,7 +7,7 @@ import { requireUserId } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, clientParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,5 +1,5 @@
import { Outlet } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { connectionType } from "~/components/integrations/connectionType";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
@@ -29,7 +29,7 @@ import {
projectIntegrationsPath,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, clientParam, projectParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,5 +1,5 @@
import { useNavigation } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { HowToRunYourJob } from "~/components/helpContent/HelpContentText";
@@ -30,7 +30,7 @@ export const RunListSearchSchema = z.object({
direction: DirectionSchema.optional(),
});
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params);
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
import { requireUserId } from "~/services/session.server";
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
await requireUserId(request);
const { runParam } = z.object({ runParam: z.string() }).parse(params);
@@ -1,5 +1,5 @@
import { Await, useLoaderData } from "@remix-run/react";
import { LoaderArgs, SerializeFrom, defer } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, SerializeFrom, defer } from "@remix-run/server-runtime";
import { Suspense } from "react";
import { Spinner } from "~/components/primitives/Spinner";
import { TaskDetail } from "~/components/run/TaskDetail";
@@ -7,7 +7,7 @@ import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
import { requireUserId } from "~/services/session.server";
import { TaskParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { taskParam } = TaskParamsSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { TriggerDetail } from "~/components/run/TriggerDetail";
import { useJob } from "~/hooks/useJob";
@@ -6,7 +6,7 @@ import { useRun } from "~/hooks/useRun";
import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
import { RunParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { runParam } = RunParamsSchema.parse(params);
const presenter = new TriggerDetailsPresenter();
@@ -1,8 +1,8 @@
import { useRevalidator } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment, useEffect } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon";
import { RunOverview } from "~/components/run/RunOverview";
@@ -22,7 +22,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { runParam } = RunParamsSchema.parse(params);
@@ -3,7 +3,7 @@ import { parse } from "@conform-to/zod";
import { ClipboardIcon } from "@heroicons/react/20/solid";
import { ClockIcon, CodeBracketIcon } from "@heroicons/react/24/outline";
import { Form, useActionData, useSubmit } from "@remix-run/react";
import { ActionFunction, LoaderArgs, json } from "@remix-run/server-runtime";
import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { useCallback, useRef, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
@@ -38,7 +38,7 @@ import { Handle } from "~/utils/handle";
import { isValidIcon } from "~/utils/icon";
import { JobParamsSchema, jobRunDashboardPath, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, jobParam } = JobParamsSchema.parse(params);
@@ -166,7 +166,8 @@ export default function Page() {
const [form, { environmentId, payload, accountId }] = useForm({
id: "test-job",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,5 +1,5 @@
import { Outlet, useLocation } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment } from "react";
import { typedjson } from "remix-typedjson";
import { JobStatusBadge } from "~/components/jobs/JobStatusBadge";
@@ -37,7 +37,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params);
@@ -1,5 +1,5 @@
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
@@ -25,7 +25,7 @@ import { cn } from "~/utils/cn";
import { Handle } from "~/utils/handle";
import { ProjectParamSchema, externalTriggerPath, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
@@ -138,7 +138,7 @@ export default function Integrations() {
);
})
) : (
<TableBlankRow colSpan={5}>
<TableBlankRow colSpan={100}>
<Paragraph>No External triggers</Paragraph>
</TableBlankRow>
)}
@@ -1,6 +1,6 @@
import { NoSymbolIcon } from "@heroicons/react/20/solid";
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
@@ -25,7 +25,7 @@ import { requireUser } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { ProjectParamSchema, docsPath, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
@@ -143,7 +143,7 @@ export default function Integrations() {
);
})
) : (
<TableBlankRow colSpan={5}>
<TableBlankRow colSpan={100}>
<Paragraph>No Scheduled triggers</Paragraph>
</TableBlankRow>
)}
@@ -1,5 +1,5 @@
import { Response, json } from "@remix-run/node";
import type { ActionFunction, LoaderArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/node";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
@@ -45,7 +45,7 @@ import { ActivateSourceService } from "~/services/sources/activateSource.server"
import { redirectWithSuccessMessage } from "~/models/message.server";
import { nanoid } from "nanoid";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params);
@@ -135,7 +135,8 @@ export default function Page() {
const [form, { jobId }] = useForm({
id: "trigger-registration-retry",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
import { requireUserId } from "~/services/session.server";
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
await requireUserId(request);
const { runParam } = z.object({ runParam: z.string() }).parse(params);
@@ -1,5 +1,5 @@
import { Await, useLoaderData } from "@remix-run/react";
import { LoaderArgs, defer } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, defer } from "@remix-run/server-runtime";
import { Suspense } from "react";
import { Spinner } from "~/components/primitives/Spinner";
import { TaskDetail } from "~/components/run/TaskDetail";
@@ -7,7 +7,7 @@ import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
import { requireUserId } from "~/services/session.server";
import { TriggerSourceRunTaskParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params);
@@ -1,10 +1,10 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { TriggerDetail } from "~/components/run/TriggerDetail";
import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
import { TriggerSourceRunParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { runParam } = TriggerSourceRunParamsSchema.parse(params);
const presenter = new TriggerDetailsPresenter();
@@ -1,8 +1,8 @@
import { useRevalidator } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment, useEffect } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon";
import { RunOverview } from "~/components/run/RunOverview";
@@ -23,7 +23,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { runParam, triggerParam } = TriggerSourceRunParamsSchema.parse(params);
@@ -1,5 +1,5 @@
import { Outlet } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson } from "remix-typedjson";
import invariant from "tiny-invariant";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
@@ -13,7 +13,7 @@ import { requireUserId } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { projectPath } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = params;
invariant(projectParam, "projectParam not found");
@@ -1,7 +1,7 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import type { ActionFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import invariant from "tiny-invariant";
import { z } from "zod";
@@ -11,7 +11,6 @@ import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { FormTitle } from "~/components/primitives/FormTitle";
import { Hint } from "~/components/primitives/Hint";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
@@ -60,7 +59,8 @@ export default function NewOrganizationPage() {
const [form, { projectName }] = useForm({
id: "create-project",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -2,7 +2,7 @@ import { useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { UserPlusIcon } from "@heroicons/react/20/solid";
import { Form, useActionData } from "@remix-run/react";
import { ActionFunction, LoaderArgs, json } from "@remix-run/server-runtime";
import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { useState } from "react";
import { UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
@@ -33,7 +33,7 @@ import { inviteTeamMemberPath, organizationTeamPath, resendInvitePath } from "~/
import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader";
import { DateTime } from "~/components/primitives/DateTime";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug } = params;
invariant(organizationSlug, "organizationSlug not found");
@@ -235,7 +235,8 @@ function LeaveTeamModal({
const [form, { memberId }] = useForm({
id: "remove-member",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,6 +1,6 @@
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { Outlet } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson } from "remix-typedjson";
import invariant from "tiny-invariant";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
@@ -11,7 +11,7 @@ import { commitCurrentOrgSession, setCurrentOrg } from "~/services/currentOrgani
import { requireUserId } from "~/services/session.server";
import { organizationPath } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug } = params;
invariant(organizationSlug, "organizationSlug not found");
@@ -56,7 +56,8 @@ export default function NewOrganizationPage() {
const [form, { orgName, projectName }] = useForm({
id: "create-organization",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
+2 -2
View File
@@ -1,5 +1,5 @@
import { Outlet, ShouldRevalidateFunction } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
import { ImpersonationBanner } from "~/components/ImpersonationBanner";
@@ -13,7 +13,7 @@ import { clearRedirectTo, commitSession } from "~/services/redirectTo.server";
import { requireUser } from "~/services/session.server";
import { confirmBasicDetailsPath } from "~/utils/pathBuilder";
export const loader = async ({ request }: LoaderArgs) => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const organizations = await getOrganizations({ userId: user.id });
const impersonationId = await getImpersonationId(request);
+2 -2
View File
@@ -1,5 +1,5 @@
import { Form } from "@remix-run/react";
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { redirect } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
@@ -15,7 +15,7 @@ export async function loader() {
const FormSchema = z.object({ id: z.string() });
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
if (request.method.toLowerCase() !== "post") {
return new Response("Method not allowed", { status: 405 });
}
@@ -1,9 +1,9 @@
import { ActionArgs, json, redirect } from "@remix-run/server-runtime";
import { ActionFunctionArgs, json, redirect } from "@remix-run/server-runtime";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { workerQueue } from "~/services/worker.server";
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
// Next authenticate the request
const authenticationResult = await authenticateApiRequest(request);
+2 -2
View File
@@ -1,10 +1,10 @@
import { HomeIcon } from "@heroicons/react/24/outline";
import { Outlet } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import { getUser, requireUserId } from "~/services/session.server";
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
await requireUserId(request);
const user = await getUser(request);
if (user == null) {
@@ -1,4 +1,4 @@
import { ActionArgs } from "@remix-run/server-runtime";
import { ActionFunctionArgs } from "@remix-run/server-runtime";
/*
To use this route, use the stripe CLI to forward events to this route:
@@ -9,7 +9,7 @@ import { ActionArgs } from "@remix-run/server-runtime";
stripe trigger price.created
*/
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
const body: any = await request.json();
const response = await fetch("https://jsonhero.io/api/create.json", {
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
logger.info("Fetching auth", { url: request.url });
const parsedParams = ParamsSchema.safeParse(params);
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
key: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Unregistering schedule", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { RegisterScheduleBodySchema, RegisterScheduleResponseBodySchema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Initializing schedule", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { UpdateTriggerSourceBodyV1Schema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Updating source", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { RegisterTriggerBodySchemaV1 } from "@trigger.dev/core";
import { z } from "zod";
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
key: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Registering trigger", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { InitializeTriggerBodySchema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Initializing trigger", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import {
CreateExternalConnectionBody,
@@ -16,7 +16,7 @@ const ParamsSchema = z.object({
clientSlug: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import { ActionArgs, json } from "@remix-run/server-runtime";
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
import {
EndpointIndexErrorSchema,
GetEndpointIndexResponse,
@@ -14,7 +14,7 @@ const ParamsSchema = z.object({
indexId: z.string(),
});
export async function loader({ request, params }: ActionArgs) {
export async function loader({ request, params }: ActionFunctionArgs) {
if (request.method.toUpperCase() !== "GET") {
return { status: 405, body: "Method Not Allowed" };
}
@@ -1,4 +1,4 @@
import { ActionArgs, json } from "@remix-run/server-runtime";
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
@@ -14,7 +14,7 @@ const BodySchema = z.object({
data: z.any().optional(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import { ActionArgs, LoaderArgs, json } from "@remix-run/server-runtime";
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { RuntimeEnvironmentType } from "@trigger.dev/database";
import { z } from "zod";
import { $transaction, PrismaClient, prisma } from "~/db.server";
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
indexHookIdentifier: z.string(),
});
export async function loader({ params }: LoaderArgs) {
export async function loader({ params }: LoaderFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
@@ -39,7 +39,7 @@ export async function loader({ params }: LoaderArgs) {
});
}
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { generateErrorMessage } from "zod-error";
@@ -11,7 +11,7 @@ const BodySchema = z.object({
id: z.string(),
});
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
logger.info("action", { url: request.url });
// Ensure this is a POST request
@@ -0,0 +1,49 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { CancelEventService } from "~/services/events/cancelEvent.server";
import { logger } from "~/services/logger.server";
import { CancelRunsForEventService } from "~/services/events/cancelRunsForEvent.server";
const ParamsSchema = z.object({
eventId: z.string(),
});
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
}
// Next authenticate the request
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const authenticatedEnv = authenticationResult.environment;
const parsed = ParamsSchema.safeParse(params);
if (!parsed.success) {
return json({ error: "Invalid or Missing eventId" }, { status: 400 });
}
const { eventId } = parsed.data;
const service = new CancelRunsForEventService();
try {
const res = await service.call(authenticatedEnv, eventId);
if (!res) {
return json({ error: "Event not found" }, { status: 404 });
}
return json(res);
} catch (err) {
logger.error("CancelRunsForEventService.call() error", { error: err });
return json({ error: "Internal Server Error" }, { status: 500 });
}
}
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { eventRecordToApiJson } from "~/api.server";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
eventId: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { GetEvent } from "@trigger.dev/core";
import { z } from "zod";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
eventId: z.string(),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { SendEventBodySchema } from "@trigger.dev/core";
import { generateErrorMessage } from "zod-error";
@@ -6,7 +6,7 @@ import { authenticateApiRequest } from "~/services/apiAuth.server";
import { IngestSendEvent } from "~/services/events/ingestSendEvent.server";
import { eventRecordToApiJson } from "~/api.server";
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import { LoaderArgs, json } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { resolveApiConnection } from "~/models/runConnection.server";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
connectionId: z.string(),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
@@ -14,7 +14,7 @@ const SearchQuerySchema = z.object({
take: z.coerce.number().default(20),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { PrismaErrorSchema } from "~/db.server";
import { z } from "zod";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
runId: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,5 +1,5 @@
import type { Organization, RuntimeEnvironment } from "@trigger.dev/database";
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import type { LogMessage } from "@trigger.dev/core";
import { LogMessageSchema } from "@trigger.dev/core";
@@ -13,7 +13,7 @@ const ParamsSchema = z.object({
runId: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import {
JobRunStatusRecordSchema,
@@ -19,7 +19,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "PUT") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { JobRunStatusRecordSchema } from "@trigger.dev/core";
import { z } from "zod";
@@ -13,7 +13,7 @@ const ParamsSchema = z.object({
const RecordsSchema = z.array(JobRunStatusRecordSchema);
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { RuntimeEnvironmentType } from "@trigger.dev/database";
import { z } from "zod";
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
secret: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import type { CompleteTaskBodyOutput, ServerTask } from "@trigger.dev/core";
import { CompleteTaskBodyInputSchema } from "@trigger.dev/core";
@@ -14,7 +14,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { FailTaskBodyInput, FailTaskBodyInputSchema, ServerTask } from "@trigger.dev/core";
import { z } from "zod";
@@ -14,7 +14,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { TaskStatus } from "@trigger.dev/database";
import {
@@ -28,7 +28,7 @@ const HeadersSchema = z.object({
"x-cached-tasks-cursor": z.string().optional().nullable(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
+2 -2
View File
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server";
@@ -17,7 +17,7 @@ const SearchQuerySchema = z.object({
taskdetails: z.coerce.boolean().default(false),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
@@ -1,9 +1,9 @@
import type { ActionArgs, LoaderArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { logger } from "~/services/logger.server";
import { HandleHttpSourceService } from "~/services/sources/handleHttpSource.server";
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Handling http source", { url: request.url });
try {
@@ -26,7 +26,7 @@ export async function action({ request, params }: ActionArgs) {
}
}
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
logger.info("Handling http source", { url: request.url });
const { id } = z.object({ id: z.string() }).parse(params);
+2 -2
View File
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
// Next authenticate the request
const authenticationResult = await authenticateApiRequest(request);
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { UpdateTriggerSourceBodyV2Schema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Updating source", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import {
REGISTER_SOURCE_EVENT_V2,
@@ -10,6 +10,7 @@ import { authenticateApiRequest } from "~/services/apiAuth.server";
import { IngestSendEvent } from "~/services/events/ingestSendEvent.server";
import { logger } from "~/services/logger.server";
import { RegisterTriggerSourceServiceV2 } from "~/services/triggers/registerTriggerSourceV2.server";
import { nanoid } from "nanoid";
const ParamsSchema = z.object({
endpointSlug: z.string(),
@@ -17,7 +18,11 @@ const ParamsSchema = z.object({
key: z.string(),
});
export async function action({ request, params }: ActionArgs) {
const HeadersSchema = z.object({
"idempotency-key": z.string().optional(),
});
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Registering trigger", { url: request.url });
// Ensure this is a POST request
@@ -77,11 +82,18 @@ export async function action({ request, params }: ActionArgs) {
dynamicTriggerId: parsedParams.data.id,
};
const headers = HeadersSchema.safeParse(Object.fromEntries(request.headers));
const eventId =
headers.success && headers.data["idempotency-key"]
? headers.data["idempotency-key"]
: `${registration.id}:${nanoid()}`;
const ingestEventService = new IngestSendEvent();
await ingestEventService.call(
authenticatedEnv,
{
id: registration.id,
id: eventId,
name: REGISTER_SOURCE_EVENT_V2,
source: "trigger.dev",
payload,
@@ -127,7 +127,8 @@ export default function Page() {
const [form, { name, email, confirmEmail }] = useForm({
id: "confirm-basic-details",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: createSchema() });
},
+2 -2
View File
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { getInviteFromToken } from "~/models/member.server";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { getUser } from "~/services/session.server";
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
const user = await getUser(request);
const url = new URL(request.url);

Some files were not shown because too many files have changed in this diff Show More