Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d6fd62fb8 | |||
| e8993ee882 | |||
| e0cdebbe09 | |||
| d6c6dc993d | |||
| 27de884f93 | |||
| c2707800aa | |||
| dbda820a71 | |||
| e417aca879 | |||
| 5a3b4a450b | |||
| f91a118958 | |||
| 9971de6a1e | |||
| 0847bee5b8 | |||
| 6152b2f3dc | |||
| ec8381e2d0 | |||
| 76a15fd278 | |||
| 824a6205f7 | |||
| a0984d5292 | |||
| c61d8ed890 | |||
| 76a0f61dee | |||
| d934feb02a | |||
| 7fdedf3137 | |||
| f6e23e94d1 | |||
| 3d6cfe5ead | |||
| 946efd1fb4 | |||
| 3d645e2b2c | |||
| 45cb75b8a3 | |||
| 91d45e5ba6 | |||
| fa5b3fe11e | |||
| 90506322b5 | |||
| e59dd465d8 | |||
| 98d5f005e4 | |||
| a6b5d2edc5 | |||
| f4a1d5f36d | |||
| 6fb6cb6aaf | |||
| 0b6417f1d8 | |||
| a685f5a4ec | |||
| 62532d5ce1 | |||
| 786d5508f0 | |||
| 187f02780b | |||
| b8b3ea07eb | |||
| e341df806c | |||
| ecf1110ab7 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Increase span attribute value length limit to 2048
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
- Prevent uncaught exceptions when handling WebSocket messages
|
||||
- Improve CLI dev command WebSocket debug and error logging
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Improve prisma errors for missing postinstall
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
try/catch opening the login URL
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Added config option extraCACerts to ProjectConfig type. This copies the ca file along with additionalFiles and sets NODE_EXTRA_CA_CERTS environment variable in built image as well as running the task.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
v3: Export AbortTaskRunError from @trigger.dev/sdk/v3
|
||||
@@ -46,6 +46,7 @@
|
||||
"changesets": [
|
||||
"afraid-sheep-joke",
|
||||
"angry-eagles-trade",
|
||||
"angry-trees-drop",
|
||||
"beige-pears-explode",
|
||||
"beige-pens-dance",
|
||||
"big-tomatoes-deliver",
|
||||
@@ -60,6 +61,7 @@
|
||||
"cool-comics-burn",
|
||||
"cool-glasses-bake",
|
||||
"cuddly-feet-approve",
|
||||
"curly-monkeys-tell",
|
||||
"dry-walls-check",
|
||||
"dull-mangos-press",
|
||||
"eight-pumas-float",
|
||||
@@ -67,9 +69,11 @@
|
||||
"famous-boats-tease",
|
||||
"fast-colts-relax",
|
||||
"few-students-share",
|
||||
"fifty-lions-think",
|
||||
"five-toes-destroy",
|
||||
"friendly-walls-repair",
|
||||
"funny-swans-destroy",
|
||||
"gorgeous-cycles-guess",
|
||||
"gorgeous-gorillas-compete",
|
||||
"green-bags-wink",
|
||||
"hot-buckets-behave",
|
||||
@@ -97,7 +101,9 @@
|
||||
"mighty-parrots-sin",
|
||||
"modern-stingrays-end",
|
||||
"nasty-jars-pump",
|
||||
"nervous-baboons-sin",
|
||||
"nervous-planets-sparkle",
|
||||
"nervous-seas-shave",
|
||||
"new-pants-beg",
|
||||
"new-rivers-tell",
|
||||
"nice-bulldogs-turn",
|
||||
@@ -175,6 +181,7 @@
|
||||
"violet-clocks-notice",
|
||||
"warm-olives-provide",
|
||||
"warm-planes-taste",
|
||||
"yellow-roses-arrive",
|
||||
"young-snails-sell"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Add more package exports that can be used from the web app
|
||||
+33
-10
@@ -1,13 +1,36 @@
|
||||
{
|
||||
"extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node", "prettier"],
|
||||
"rules": {
|
||||
"@typescript-eslint/strict-boolean-expressions": [
|
||||
"error",
|
||||
{
|
||||
"allowNullableBoolean": true,
|
||||
"allowNullableString": true,
|
||||
"allowNullableNumber": true
|
||||
"plugins": [
|
||||
"@trigger.dev/eslint-plugin",
|
||||
"react-hooks",
|
||||
"@typescript-eslint/eslint-plugin",
|
||||
"import"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {
|
||||
// Autofixes imports from "@trigger.dev/core" to fine grained modules
|
||||
"@trigger.dev/no-trigger-core-import": "error",
|
||||
// Normalize `import type {}` and `import { type }`
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"warn",
|
||||
{
|
||||
// the "type" annotation can get tangled and cause syntax errors
|
||||
// during some autofixes, so easier to just turn it off
|
||||
"prefer": "type-imports",
|
||||
"disallowTypeAnnotations": true,
|
||||
"fixStyle": "inline-type-imports"
|
||||
}
|
||||
],
|
||||
// no-trigger-core-import splits imports into multiple lines
|
||||
// this one merges them back into a single line
|
||||
// if they still import from the same module
|
||||
"import/no-duplicates": ["warn", { "prefer-inline": true }],
|
||||
// lots of undeclared vars, enable this rule if you want to clean them up
|
||||
"turbo/no-undeclared-env-vars": "off"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignorePatterns": ["seed.js", "seedCloud.ts", "populate.js"]
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export function UsageBar({
|
||||
return (
|
||||
<div className="h-fit w-full py-12">
|
||||
<div className="relative h-3 w-full rounded-sm bg-background-bright">
|
||||
{billingLimit && (
|
||||
{billingLimit !== undefined && (
|
||||
<motion.div
|
||||
initial={{ width: billingLimitPercentage / startFactor + "%" }}
|
||||
animate={{ width: billingLimitPercentage + "%" }}
|
||||
@@ -57,11 +57,10 @@ export function UsageBar({
|
||||
value={formatCurrency(billingLimit, false)}
|
||||
position="bottomRow2"
|
||||
percentage={billingLimitPercentage}
|
||||
tooltipContent={`Billing limit: ${formatCurrency(billingLimit, false)}`}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{tierLimit && (
|
||||
{tierLimit !== undefined && (
|
||||
<motion.div
|
||||
initial={{ width: tierRunLimitPercentage / startFactor + "%" }}
|
||||
animate={{ width: tierRunLimitPercentage + "%" }}
|
||||
@@ -74,14 +73,10 @@ export function UsageBar({
|
||||
value={formatCurrency(tierLimit, false)}
|
||||
position="bottomRow1"
|
||||
percentage={tierRunLimitPercentage}
|
||||
tooltipContent={`${isPaying ? "Included usage" : "Tier limit"}: ${formatCurrency(
|
||||
tierLimit,
|
||||
false
|
||||
)}`}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{projectedUsage && projectedUsage !== 0 && (
|
||||
{projectedUsage !== undefined && projectedUsage !== 0 && (
|
||||
<motion.div
|
||||
initial={{ width: projectedRunsPercentage / startFactor + "%" }}
|
||||
animate={{ width: projectedRunsPercentage + "%" }}
|
||||
@@ -94,7 +89,6 @@ export function UsageBar({
|
||||
value={formatCurrency(projectedUsage, false)}
|
||||
position="topRow2"
|
||||
percentage={projectedRunsPercentage}
|
||||
tooltipContent={`Projected runs: ${formatCurrency(projectedUsage, false)}`}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -113,7 +107,6 @@ export function UsageBar({
|
||||
value={formatCurrency(current, false)}
|
||||
position="topRow1"
|
||||
percentage={usagePercentage}
|
||||
tooltipContent={`Used: ${formatCurrency(current, false)}`}
|
||||
/>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -140,7 +133,7 @@ type LegendProps = {
|
||||
value: number | string;
|
||||
percentage: number;
|
||||
position: keyof typeof positions;
|
||||
tooltipContent: string;
|
||||
tooltipContent?: string;
|
||||
};
|
||||
|
||||
function Legend({ text, value, position, percentage, tooltipContent }: LegendProps) {
|
||||
@@ -154,17 +147,24 @@ function Legend({ text, value, position, percentage, tooltipContent }: LegendPro
|
||||
flipLegendPosition === true ? "-translate-x-full border-r" : "border-l"
|
||||
)}
|
||||
>
|
||||
<SimpleTooltip
|
||||
button={
|
||||
<Paragraph className="mr-px h-fit whitespace-nowrap bg-background-bright px-1.5 text-xs text-text-bright">
|
||||
{text}
|
||||
<span className="ml-1 text-text-dimmed">{value}</span>
|
||||
</Paragraph>
|
||||
}
|
||||
side="top"
|
||||
content={tooltipContent}
|
||||
className="z-50 h-fit"
|
||||
/>
|
||||
{tooltipContent ? (
|
||||
<SimpleTooltip
|
||||
button={
|
||||
<Paragraph className="mr-px h-fit whitespace-nowrap bg-background-bright px-1.5 text-xs text-text-bright">
|
||||
{text}
|
||||
<span className="ml-1 text-text-dimmed">{value}</span>
|
||||
</Paragraph>
|
||||
}
|
||||
side="top"
|
||||
content={tooltipContent}
|
||||
className="z-50 h-fit"
|
||||
/>
|
||||
) : (
|
||||
<Paragraph className="mr-px h-fit whitespace-nowrap bg-background-bright px-1.5 text-xs text-text-bright">
|
||||
{text}
|
||||
<span className="ml-1 text-text-dimmed">{value}</span>
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -552,7 +552,6 @@ function V3ProjectSideMenu({
|
||||
name="Tasks"
|
||||
icon={TaskIcon}
|
||||
iconColor="text-blue-500"
|
||||
count={project.jobCount}
|
||||
to={v3ProjectPath(organization, project)}
|
||||
data-action="tasks"
|
||||
/>
|
||||
|
||||
@@ -8,12 +8,8 @@ const Alert = AlertDialogPrimitive.Root;
|
||||
|
||||
const AlertTrigger = AlertDialogPrimitive.Trigger;
|
||||
|
||||
const AlertPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
|
||||
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
const AlertPortal = ({ children, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => (
|
||||
<AlertDialogPrimitive.Portal {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,8 @@ const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = ({ className, children, ...props }: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
const DialogPortal = ({ children, ...props }: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -28,8 +28,8 @@ interface SheetPortalProps
|
||||
extends SheetPrimitive.DialogPortalProps,
|
||||
VariantProps<typeof portalVariants> {}
|
||||
|
||||
const SheetPortal = ({ position, className, children, ...props }: SheetPortalProps) => (
|
||||
<SheetPrimitive.Portal className={cn(className)} {...props}>
|
||||
const SheetPortal = ({ position, children, ...props }: SheetPortalProps) => (
|
||||
<SheetPrimitive.Portal {...props}>
|
||||
<div className={portalVariants({ position })}>{children}</div>
|
||||
</SheetPrimitive.Portal>
|
||||
);
|
||||
|
||||
@@ -11,22 +11,32 @@ import { cn } from "~/utils/cn";
|
||||
|
||||
export function DeploymentStatus({
|
||||
status,
|
||||
isBuilt,
|
||||
className,
|
||||
}: {
|
||||
status: WorkerDeploymentStatus;
|
||||
isBuilt: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<span className={cn("flex items-center gap-1", className)}>
|
||||
<DeploymentStatusIcon status={status} className="h-4 w-4" />
|
||||
<DeploymentStatusLabel status={status} />
|
||||
<DeploymentStatusLabel status={status} isBuilt={isBuilt} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function DeploymentStatusLabel({ status }: { status: WorkerDeploymentStatus }) {
|
||||
export function DeploymentStatusLabel({
|
||||
status,
|
||||
isBuilt,
|
||||
}: {
|
||||
status: WorkerDeploymentStatus;
|
||||
isBuilt: boolean;
|
||||
}) {
|
||||
return (
|
||||
<span className={deploymentStatusClassNameColor(status)}>{deploymentStatusTitle(status)}</span>
|
||||
<span className={deploymentStatusClassNameColor(status)}>
|
||||
{deploymentStatusTitle(status, isBuilt)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +89,7 @@ export function deploymentStatusClassNameColor(status: WorkerDeploymentStatus):
|
||||
}
|
||||
}
|
||||
|
||||
export function deploymentStatusTitle(status: WorkerDeploymentStatus): string {
|
||||
export function deploymentStatusTitle(status: WorkerDeploymentStatus, isBuilt: boolean): string {
|
||||
switch (status) {
|
||||
case "PENDING":
|
||||
return "Pending…";
|
||||
@@ -92,9 +102,17 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus): string {
|
||||
case "CANCELED":
|
||||
return "Canceled";
|
||||
case "TIMED_OUT":
|
||||
return "Timed out";
|
||||
if (!isBuilt) {
|
||||
return "Build timed out";
|
||||
}
|
||||
|
||||
return "Indexing timed out";
|
||||
case "FAILED":
|
||||
return "Failed";
|
||||
if (!isBuilt) {
|
||||
return "Build failed";
|
||||
}
|
||||
|
||||
return "Indexing failed";
|
||||
default: {
|
||||
assertNever(status);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ArrowPathIcon } from "@heroicons/react/20/solid";
|
||||
import { Form, useNavigation } from "@remix-run/react";
|
||||
import { Button } from "~/components/primitives/Buttons";
|
||||
import {
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
} from "~/components/primitives/Dialog";
|
||||
|
||||
type RetryDeploymentIndexingDialogProps = {
|
||||
projectId: string;
|
||||
deploymentShortCode: string;
|
||||
redirectPath: string;
|
||||
};
|
||||
|
||||
export function RetryDeploymentIndexingDialog({
|
||||
projectId,
|
||||
deploymentShortCode,
|
||||
redirectPath,
|
||||
}: RetryDeploymentIndexingDialogProps) {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const formAction = `/resources/${projectId}/deployments/${deploymentShortCode}/retry-indexing`;
|
||||
const isLoading = navigation.formAction === formAction;
|
||||
|
||||
return (
|
||||
<DialogContent key="retry-indexing">
|
||||
<DialogHeader>Retry indexing this deployment?</DialogHeader>
|
||||
<DialogDescription>
|
||||
Retrying can be useful if indexing failed due to missing environment variables. Make sure
|
||||
you set them before retrying. In most other cases, indexing will keep failing until you fix
|
||||
any errors and re-deploy.
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Form
|
||||
action={`/resources/${projectId}/deployments/${deploymentShortCode}/retry-indexing`}
|
||||
method="post"
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
name="redirectUrl"
|
||||
value={redirectPath}
|
||||
variant="primary/small"
|
||||
LeadingIcon={isLoading ? "spinner-white" : ArrowPathIcon}
|
||||
disabled={isLoading}
|
||||
shortcut={{ modifiers: ["meta"], key: "enter" }}
|
||||
>
|
||||
{isLoading ? "Retrying..." : "Retry indexing"}
|
||||
</Button>
|
||||
</Form>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
@@ -118,6 +118,7 @@ export async function adminGetOrganizations(userId: string, { page, search }: Se
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
v2Enabled: true,
|
||||
v3Enabled: true,
|
||||
members: {
|
||||
select: {
|
||||
|
||||
@@ -88,6 +88,7 @@ export class DeploymentListPresenter {
|
||||
version: string;
|
||||
status: WorkerDeploymentStatus;
|
||||
environmentId: string;
|
||||
builtAt: Date | null;
|
||||
deployedAt: Date | null;
|
||||
tasksCount: BigInt | null;
|
||||
userId: string | null;
|
||||
@@ -107,6 +108,7 @@ export class DeploymentListPresenter {
|
||||
u."name" AS "userName",
|
||||
u."displayName" AS "userDisplayName",
|
||||
u."avatarUrl" AS "userAvatarUrl",
|
||||
wd."builtAt",
|
||||
wd."deployedAt"
|
||||
FROM
|
||||
${sqlDatabaseSchema}."WorkerDeployment" as wd
|
||||
@@ -121,7 +123,7 @@ LIMIT ${pageSize} OFFSET ${pageSize * (page - 1)};`;
|
||||
return {
|
||||
currentPage: page,
|
||||
totalPages: Math.ceil(totalCount / pageSize),
|
||||
deployments: deployments.map((deployment) => {
|
||||
deployments: deployments.map((deployment, index) => {
|
||||
const environment = project.environments.find((env) => env.id === deployment.environmentId);
|
||||
if (!environment) {
|
||||
throw new Error(`Environment not found for deployment ${deployment.id}`);
|
||||
@@ -136,11 +138,14 @@ LIMIT ${pageSize} OFFSET ${pageSize * (page - 1)};`;
|
||||
shortCode: deployment.shortCode,
|
||||
version: deployment.version,
|
||||
status: deployment.status,
|
||||
builtAt: deployment.builtAt,
|
||||
deployedAt: deployment.deployedAt,
|
||||
tasksCount: deployment.tasksCount ? Number(deployment.tasksCount) : null,
|
||||
label: label?.label,
|
||||
isBuilt: !!deployment.builtAt,
|
||||
isCurrent: label?.label === "current",
|
||||
isDeployed: deployment.status === "DEPLOYED",
|
||||
isLatest: page === 1 && index === 0,
|
||||
environment: {
|
||||
id: environment.id,
|
||||
type: environment.type,
|
||||
|
||||
@@ -90,6 +90,7 @@ export class DeploymentPresenter {
|
||||
},
|
||||
},
|
||||
status: true,
|
||||
builtAt: true,
|
||||
deployedAt: true,
|
||||
createdAt: true,
|
||||
promotions: {
|
||||
@@ -134,6 +135,7 @@ export class DeploymentPresenter {
|
||||
version: deployment.version,
|
||||
status: deployment.status,
|
||||
createdAt: deployment.createdAt,
|
||||
builtAt: deployment.builtAt,
|
||||
deployedAt: deployment.deployedAt,
|
||||
tasks: deployment.worker?.tasks,
|
||||
label: deployment.promotions?.[0]?.label,
|
||||
@@ -152,6 +154,7 @@ export class DeploymentPresenter {
|
||||
projectId: deployment.projectId,
|
||||
organizationId: project.organizationId,
|
||||
errorData: DeploymentPresenter.prepareErrorData(deployment.errorData),
|
||||
isBuilt: !!deployment.builtAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,26 +78,11 @@ export class EnvironmentVariablesPresenter {
|
||||
where: {
|
||||
project: {
|
||||
slug: projectSlug,
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
type: {
|
||||
in: ["PREVIEW", "STAGING", "PRODUCTION"],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "DEVELOPMENT",
|
||||
orgMember: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const sortedEnvironments = sortEnvironments(environments).filter(
|
||||
(e) => e.orgMember?.userId === userId || e.orgMember === null
|
||||
);
|
||||
const sortedEnvironments = sortEnvironments(environments);
|
||||
|
||||
const repository = new EnvironmentVariablesRepository(this.#prismaClient);
|
||||
const variables = await repository.getProject(project.id);
|
||||
@@ -119,7 +104,9 @@ export class EnvironmentVariablesPresenter {
|
||||
}, {} as Record<string, { value: string | undefined; environment: { type: string; id: string } }>),
|
||||
};
|
||||
}),
|
||||
environments: sortedEnvironments.map((environment) => ({
|
||||
environments: sortedEnvironments.filter(
|
||||
(e) => e.orgMember?.userId === userId || e.orgMember === null
|
||||
).map((environment) => ({
|
||||
id: environment.id,
|
||||
type: environment.type,
|
||||
})),
|
||||
|
||||
@@ -69,8 +69,6 @@ async function getSlackChannelsForToken(integration: AuthenticatableIntegration)
|
||||
return (channels ?? [])
|
||||
.filter((channel) => !channel.is_archived)
|
||||
.filter((channel) => channel.is_channel)
|
||||
.filter((channel) => !channel.is_ext_shared)
|
||||
.filter((channel) => channel.unlinked === 0)
|
||||
.filter((channel) => channel.num_members)
|
||||
.sort((a, b) => a.name!.localeCompare(b.name!));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Prisma, TaskRunStatus } from "@trigger.dev/database";
|
||||
import { Prisma, TaskRunStatus, TaskTriggerSource } from "@trigger.dev/database";
|
||||
import parse from "parse-duration";
|
||||
import { Direction } from "~/components/runs/RunStatuses";
|
||||
import { FINISHED_STATUSES } from "~/components/runs/v3/TaskRunStatus";
|
||||
@@ -94,12 +94,16 @@ export class RunListPresenter extends BasePresenter {
|
||||
});
|
||||
|
||||
//get all possible tasks
|
||||
const possibleTasksAsync = this._replica.backgroundWorkerTask.findMany({
|
||||
distinct: ["slug"],
|
||||
where: {
|
||||
projectId: project.id,
|
||||
},
|
||||
});
|
||||
const possibleTasksAsync = this._replica.$queryRaw<
|
||||
{
|
||||
slug: string;
|
||||
triggerSource: TaskTriggerSource
|
||||
}[]
|
||||
>`
|
||||
SELECT DISTINCT(slug), "triggerSource"
|
||||
FROM ${sqlDatabaseSchema}."BackgroundWorkerTask"
|
||||
WHERE "projectId" = ${project.id}
|
||||
ORDER BY slug ASC;`;
|
||||
|
||||
//get possible bulk actions
|
||||
const bulkActionsAsync = this._replica.bulkActionGroup.findMany({
|
||||
|
||||
@@ -78,8 +78,10 @@ export class UsagePresenter extends BasePresenter {
|
||||
${sqlDatabaseSchema}."TaskRun" tr
|
||||
JOIN ${sqlDatabaseSchema}."Project" pr ON pr.id = tr."projectId"
|
||||
JOIN ${sqlDatabaseSchema}."Organization" org ON org.id = pr."organizationId"
|
||||
JOIN ${sqlDatabaseSchema}."RuntimeEnvironment" env ON env."projectId" = pr.id
|
||||
WHERE
|
||||
tr."createdAt" > ${startOfMonth}
|
||||
env.type <> 'DEVELOPMENT'
|
||||
AND tr."createdAt" > ${startOfMonth}
|
||||
AND tr."createdAt" < ${endOfMonth}
|
||||
AND org.id = ${organizationId}
|
||||
GROUP BY
|
||||
|
||||
+16
-1
@@ -132,7 +132,11 @@ export default function Page() {
|
||||
</Property>
|
||||
<Property label="Version">{deployment.version}</Property>
|
||||
<Property label="Status">
|
||||
<DeploymentStatus status={deployment.status} className="text-sm" />
|
||||
<DeploymentStatus
|
||||
status={deployment.status}
|
||||
isBuilt={deployment.isBuilt}
|
||||
className="text-sm"
|
||||
/>
|
||||
</Property>
|
||||
<Property label="Tasks">{deployment.tasks ? deployment.tasks.length : "–"}</Property>
|
||||
<Property label="SDK Version">
|
||||
@@ -143,6 +147,17 @@ export default function Page() {
|
||||
<DateTimeAccurate date={deployment.createdAt} /> UTC
|
||||
</Paragraph>
|
||||
</Property>
|
||||
<Property label="Built at">
|
||||
<Paragraph variant="small/bright">
|
||||
{deployment.builtAt ? (
|
||||
<>
|
||||
<DateTimeAccurate date={deployment.builtAt} /> UTC
|
||||
</>
|
||||
) : (
|
||||
"–"
|
||||
)}
|
||||
</Paragraph>
|
||||
</Property>
|
||||
<Property label="Deployed at">
|
||||
<Paragraph variant="small/bright">
|
||||
{deployment.deployedAt ? (
|
||||
|
||||
+36
-5
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
ArrowUturnLeftIcon,
|
||||
BookOpenIcon,
|
||||
CommandLineIcon,
|
||||
ServerIcon,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
} from "@heroicons/react/20/solid";
|
||||
import { Outlet, useLocation, useParams } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { Fragment } from "react/jsx-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { z } from "zod";
|
||||
import { BlankstateInstructions } from "~/components/BlankstateInstructions";
|
||||
@@ -38,6 +40,7 @@ import {
|
||||
} from "~/components/primitives/Table";
|
||||
import { TextLink } from "~/components/primitives/TextLink";
|
||||
import { DeploymentStatus } from "~/components/runs/v3/DeploymentStatus";
|
||||
import { RetryDeploymentIndexingDialog } from "~/components/runs/v3/RetryDeploymentIndexingDialog";
|
||||
import { RollbackDeploymentDialog } from "~/components/runs/v3/RollbackDeploymentDialog";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
@@ -54,6 +57,7 @@ import {
|
||||
v3EnvironmentVariablesPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { createSearchParams } from "~/utils/searchParams";
|
||||
import { deploymentIndexingIsRetryable } from "~/v3/deploymentStatus";
|
||||
|
||||
const SearchParams = z.object({
|
||||
page: z.coerce.number().optional(),
|
||||
@@ -124,7 +128,12 @@ export default function Page() {
|
||||
user.id !== deployment.environment.userId
|
||||
? deployment.environment.userName
|
||||
: undefined;
|
||||
const path = v3DeploymentPath(organization, project, deployment);
|
||||
const path = v3DeploymentPath(
|
||||
organization,
|
||||
project,
|
||||
deployment,
|
||||
currentPage
|
||||
);
|
||||
return (
|
||||
<TableRow key={deployment.id} className="group">
|
||||
<TableCell to={path}>
|
||||
@@ -143,7 +152,10 @@ export default function Page() {
|
||||
</TableCell>
|
||||
<TableCell to={path}>{deployment.version}</TableCell>
|
||||
<TableCell to={path}>
|
||||
<DeploymentStatus status={deployment.status} />
|
||||
<DeploymentStatus
|
||||
status={deployment.status}
|
||||
isBuilt={deployment.isBuilt}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
{deployment.tasksCount !== null ? deployment.tasksCount : "–"}
|
||||
@@ -265,14 +277,19 @@ function DeploymentActionsCell({
|
||||
const location = useLocation();
|
||||
const project = useProject();
|
||||
|
||||
if (deployment.isCurrent || !deployment.isDeployed) return <TableCell to={path}>{""}</TableCell>;
|
||||
const canRollback = !deployment.isCurrent && deployment.isDeployed;
|
||||
const canRetryIndexing = deployment.isLatest && deploymentIndexingIsRetryable(deployment);
|
||||
|
||||
if (!canRollback && !canRetryIndexing) {
|
||||
return <TableCell to={path}>{""}</TableCell>;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCellMenu isSticky>
|
||||
{!deployment.isCurrent && deployment.isDeployed && (
|
||||
{canRollback && (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="small-menu-item" LeadingIcon={ArrowPathIcon}>
|
||||
<Button variant="small-menu-item" LeadingIcon={ArrowUturnLeftIcon}>
|
||||
Rollback
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
@@ -283,6 +300,20 @@ function DeploymentActionsCell({
|
||||
/>
|
||||
</Dialog>
|
||||
)}
|
||||
{canRetryIndexing && (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="small-menu-item" LeadingIcon={ArrowPathIcon}>
|
||||
Retry indexing
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<RetryDeploymentIndexingDialog
|
||||
projectId={project.id}
|
||||
deploymentShortCode={deployment.shortCode}
|
||||
redirectPath={`${location.pathname}${location.search}`}
|
||||
/>
|
||||
</Dialog>
|
||||
)}
|
||||
</TableCellMenu>
|
||||
);
|
||||
}
|
||||
|
||||
+9
@@ -301,6 +301,8 @@ function EditEnvironmentVariablePanel({
|
||||
const lastSubmission = useActionData();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const hiddenValues = Object.values(variable.values).filter((value) => !environments.map(e => e.id).includes(value.environment.id));
|
||||
|
||||
const isLoading =
|
||||
navigation.state !== "idle" &&
|
||||
navigation.formMethod === "post" &&
|
||||
@@ -336,6 +338,12 @@ function EditEnvironmentVariablePanel({
|
||||
<input type="hidden" name="action" value="edit" />
|
||||
<input type="hidden" name="id" value={variable.id} />
|
||||
<input type="hidden" name="key" value={variable.key} />
|
||||
{hiddenValues.map((value, index) => (
|
||||
<Fragment key={index}>
|
||||
<input type="hidden" name={`values[${index}].environmentId`} value={value.environment.id} />
|
||||
<input type="hidden" name={`values[${index}].value`} value={value.value} />
|
||||
</Fragment>
|
||||
))}
|
||||
<FormError id={id.errorId}>{id.error}</FormError>
|
||||
<Fieldset>
|
||||
<InputGroup fullWidth className="mb-5 mt-2">
|
||||
@@ -351,6 +359,7 @@ function EditEnvironmentVariablePanel({
|
||||
<div className="grid grid-cols-[auto_1fr] gap-x-2 gap-y-2">
|
||||
{environments.map((environment, index) => {
|
||||
const value = variable.values[environment.id]?.value;
|
||||
index += hiddenValues.length;
|
||||
return (
|
||||
<Fragment key={environment.id}>
|
||||
<input
|
||||
|
||||
@@ -35,6 +35,8 @@ import { requireUserId } from "~/services/session.server";
|
||||
import { formatCurrency, formatCurrencyAccurate, formatNumber } from "~/utils/numberFormatter";
|
||||
import { OrganizationParamsSchema, organizationPath } from "~/utils/pathBuilder";
|
||||
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
|
||||
import { InfoPanel } from "~/components/primitives/InfoPanel";
|
||||
import { InformationCircleIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
await requireUserId(request);
|
||||
@@ -213,52 +215,60 @@ export default function Page() {
|
||||
>
|
||||
{(tasks) => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Task</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Runs</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Average duration</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Average cost</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Total duration</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Total cost</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{tasks.length === 0 ? (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>
|
||||
<Paragraph variant="small">No runs.</Paragraph>
|
||||
</TableCell>
|
||||
<TableHeaderCell>Task</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Runs</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Average duration</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Average cost</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Total duration</TableHeaderCell>
|
||||
<TableHeaderCell alignment="right">Total cost</TableHeaderCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
tasks.map((task) => (
|
||||
<TableRow key={task.taskIdentifier}>
|
||||
<TableCell>{task.taskIdentifier}</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatNumber(task.runCount)}
|
||||
</TableCell>
|
||||
<TableCell alignment="right">
|
||||
{formatDurationMilliseconds(task.averageDuration, {
|
||||
style: "short",
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatCurrencyAccurate(task.averageCost)}
|
||||
</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatDurationMilliseconds(task.totalDuration, {
|
||||
style: "short",
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatCurrencyAccurate(task.totalCost)}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{tasks.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>
|
||||
<div className="flex items-center justify-center">
|
||||
<Paragraph variant="small">No runs for this period</Paragraph>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
tasks.map((task) => (
|
||||
<TableRow key={task.taskIdentifier}>
|
||||
<TableCell>{task.taskIdentifier}</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatNumber(task.runCount)}
|
||||
</TableCell>
|
||||
<TableCell alignment="right">
|
||||
{formatDurationMilliseconds(task.averageDuration, {
|
||||
style: "short",
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatCurrencyAccurate(task.averageCost)}
|
||||
</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatDurationMilliseconds(task.totalDuration, {
|
||||
style: "short",
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell alignment="right" className="tabular-nums">
|
||||
{formatCurrencyAccurate(task.totalCost)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<InfoPanel icon={InformationCircleIcon} panelClassName="max-w-[22rem] mt-3">
|
||||
Dev environment runs are excluded from the usage data above, since they do
|
||||
not have an associated compute cost.
|
||||
</InfoPanel>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Await>
|
||||
|
||||
@@ -51,6 +51,7 @@ export default function ChoosePlanPage() {
|
||||
subscription={v3Subscription}
|
||||
organizationSlug={organizationSlug}
|
||||
hasPromotedPlan
|
||||
showGithubVerificationBadge
|
||||
/>
|
||||
</MainCenteredContainer>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { createEnvironment } from "~/models/organization.server";
|
||||
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
|
||||
import { marqs } from "~/v3/marqs/index.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
organizationId: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* It will create a staging environment for all the projects where there isn't one already
|
||||
*/
|
||||
export async function action({ request, params }: ActionFunctionArgs) {
|
||||
// Next authenticate the request
|
||||
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
|
||||
|
||||
if (!authenticationResult) {
|
||||
return json({ error: "Invalid or Missing API key" }, { status: 401 });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: authenticationResult.userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return json({ error: "Invalid or Missing API key" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!user.admin) {
|
||||
return json({ error: "You must be an admin to perform this action" }, { status: 403 });
|
||||
}
|
||||
|
||||
const { organizationId } = ParamsSchema.parse(params);
|
||||
|
||||
const organization = await prisma.organization.findUnique({
|
||||
where: {
|
||||
id: organizationId,
|
||||
},
|
||||
include: {
|
||||
projects: {
|
||||
include: { environments: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
return json({ error: "Organization not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
let created = 0;
|
||||
|
||||
for (const project of organization.projects) {
|
||||
const stagingEnvironment = project.environments.find((env) => env.type === "STAGING");
|
||||
|
||||
if (!stagingEnvironment) {
|
||||
const staging = await createEnvironment(organization, project, "STAGING");
|
||||
await marqs?.updateEnvConcurrencyLimits({ ...staging, organization, project });
|
||||
created++;
|
||||
} else {
|
||||
await marqs?.updateEnvConcurrencyLimits({ ...stagingEnvironment, organization, project });
|
||||
}
|
||||
}
|
||||
|
||||
return json({ success: true, created, total: organization.projects.length });
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
|
||||
import { Form } from "@remix-run/react";
|
||||
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { z } from "zod";
|
||||
import { Button, LinkButton } from "~/components/primitives/Buttons";
|
||||
@@ -16,8 +16,7 @@ import {
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
} from "~/components/primitives/Table";
|
||||
import { adminGetOrganizations, setV3Enabled } from "~/models/admin.server";
|
||||
import { redirectWithSuccessMessage } from "~/models/message.server";
|
||||
import { adminGetOrganizations } from "~/models/admin.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { createSearchParams } from "~/utils/searchParams";
|
||||
|
||||
@@ -40,23 +39,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
||||
return typedjson(result);
|
||||
};
|
||||
|
||||
const FormSchema = z.object({ id: z.string(), v3: z.enum(["enable", "disable"]) });
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
if (request.method.toLowerCase() !== "post") {
|
||||
return new Response("Method not allowed", { status: 405 });
|
||||
}
|
||||
|
||||
const userId = await requireUserId(request);
|
||||
|
||||
const payload = Object.fromEntries(await request.formData());
|
||||
|
||||
const { id, v3 } = FormSchema.parse(payload);
|
||||
|
||||
const result = await setV3Enabled(userId, id, v3 === "enable");
|
||||
|
||||
return redirectWithSuccessMessage("/admin/orgs", request, `v3 ${v3}d for org ${id}`);
|
||||
}
|
||||
export default function AdminDashboardRoute() {
|
||||
const { organizations, filters, page, pageCount } = useTypedLoaderData<typeof loader>();
|
||||
|
||||
@@ -87,6 +69,7 @@ export default function AdminDashboardRoute() {
|
||||
<TableHeaderCell>Slug</TableHeaderCell>
|
||||
<TableHeaderCell>Members</TableHeaderCell>
|
||||
<TableHeaderCell>id</TableHeaderCell>
|
||||
<TableHeaderCell>v2?</TableHeaderCell>
|
||||
<TableHeaderCell>v3?</TableHeaderCell>
|
||||
<TableHeaderCell>Actions</TableHeaderCell>
|
||||
</TableRow>
|
||||
@@ -114,34 +97,9 @@ export default function AdminDashboardRoute() {
|
||||
))}
|
||||
</TableCell>
|
||||
<TableCell>{org.id}</TableCell>
|
||||
<TableCell>{org.v2Enabled ? "✅" : ""}</TableCell>
|
||||
<TableCell>{org.v3Enabled ? "✅" : ""}</TableCell>
|
||||
<TableCell isSticky={true}>
|
||||
<Form method="post" reloadDocument>
|
||||
<input type="hidden" name="id" value={org.id} />
|
||||
|
||||
{org.v3Enabled ? (
|
||||
<Button
|
||||
type="submit"
|
||||
name="v3"
|
||||
value="disable"
|
||||
className="mr-2"
|
||||
variant="tertiary/small"
|
||||
>
|
||||
Disable v3
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
name="v3"
|
||||
value="enable"
|
||||
className="mr-2"
|
||||
variant="tertiary/small"
|
||||
>
|
||||
Enable v3
|
||||
</Button>
|
||||
)}
|
||||
</Form>
|
||||
</TableCell>
|
||||
<TableCell isSticky={true}> </TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { parse } from "@conform-to/zod";
|
||||
import { ActionFunction, json } from "@remix-run/node";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { deploymentIndexingIsRetryable } from "~/v3/deploymentStatus";
|
||||
import { RetryDeploymentIndexingService } from "~/v3/services/retryDeploymentIndexing.server";
|
||||
|
||||
export const rollbackSchema = z.object({
|
||||
redirectUrl: z.string(),
|
||||
});
|
||||
|
||||
const ParamSchema = z.object({
|
||||
projectId: z.string(),
|
||||
deploymentShortCode: z.string(),
|
||||
});
|
||||
|
||||
export const action: ActionFunction = async ({ request, params }) => {
|
||||
const userId = await requireUserId(request);
|
||||
const { projectId, deploymentShortCode } = ParamSchema.parse(params);
|
||||
|
||||
console.log("projectId", projectId);
|
||||
console.log("deploymentShortCode", deploymentShortCode);
|
||||
|
||||
const formData = await request.formData();
|
||||
const submission = parse(formData, { schema: rollbackSchema });
|
||||
|
||||
if (!submission.value) {
|
||||
return json(submission);
|
||||
}
|
||||
|
||||
try {
|
||||
const project = await prisma.project.findUnique({
|
||||
where: {
|
||||
id: projectId,
|
||||
organization: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
return redirectWithErrorMessage(submission.value.redirectUrl, request, "Project not found");
|
||||
}
|
||||
|
||||
const deployment = await prisma.workerDeployment.findUnique({
|
||||
where: {
|
||||
projectId_shortCode: {
|
||||
projectId: project.id,
|
||||
shortCode: deploymentShortCode,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!deployment) {
|
||||
return redirectWithErrorMessage(
|
||||
submission.value.redirectUrl,
|
||||
request,
|
||||
"Deployment not found"
|
||||
);
|
||||
}
|
||||
|
||||
if (!deploymentIndexingIsRetryable(deployment)) {
|
||||
return redirectWithErrorMessage(
|
||||
submission.value.redirectUrl,
|
||||
request,
|
||||
"Deployment indexing not in retryable state"
|
||||
);
|
||||
}
|
||||
|
||||
const startIndexing = new RetryDeploymentIndexingService();
|
||||
await startIndexing.call(deployment.id);
|
||||
|
||||
return redirectWithSuccessMessage(
|
||||
submission.value.redirectUrl,
|
||||
request,
|
||||
"Retrying deployment indexing"
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error("Failed to retry deployment indexing", {
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
},
|
||||
projectId,
|
||||
deploymentShortCode,
|
||||
});
|
||||
submission.error = { runParam: error.message };
|
||||
return json(submission);
|
||||
} else {
|
||||
logger.error("Failed to retry deployment indexing", {
|
||||
error,
|
||||
projectId,
|
||||
deploymentShortCode,
|
||||
});
|
||||
submission.error = { runParam: JSON.stringify(error) };
|
||||
return json(submission);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -133,6 +133,7 @@ type PricingPlansProps = {
|
||||
subscription?: SubscriptionResult;
|
||||
organizationSlug: string;
|
||||
hasPromotedPlan: boolean;
|
||||
showGithubVerificationBadge?: boolean;
|
||||
};
|
||||
|
||||
export function PricingPlans({
|
||||
@@ -140,6 +141,7 @@ export function PricingPlans({
|
||||
subscription,
|
||||
organizationSlug,
|
||||
hasPromotedPlan,
|
||||
showGithubVerificationBadge,
|
||||
}: PricingPlansProps) {
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
@@ -148,6 +150,7 @@ export function PricingPlans({
|
||||
plan={plans.free}
|
||||
subscription={subscription}
|
||||
organizationSlug={organizationSlug}
|
||||
showGithubVerificationBadge={showGithubVerificationBadge}
|
||||
/>
|
||||
<TierHobby
|
||||
plan={plans.hobby}
|
||||
@@ -168,10 +171,12 @@ export function TierFree({
|
||||
plan,
|
||||
subscription,
|
||||
organizationSlug,
|
||||
showGithubVerificationBadge,
|
||||
}: {
|
||||
plan: FreePlanDefinition;
|
||||
subscription?: SubscriptionResult;
|
||||
organizationSlug: string;
|
||||
showGithubVerificationBadge?: boolean;
|
||||
}) {
|
||||
const location = useLocation();
|
||||
const navigation = useNavigation();
|
||||
@@ -184,7 +189,7 @@ export function TierFree({
|
||||
<TierContainer>
|
||||
<div className="relative">
|
||||
<PricingHeader title={plan.title} cost={0} />
|
||||
{status === "approved" && (
|
||||
{showGithubVerificationBadge && status === "approved" && (
|
||||
<SimpleTooltip
|
||||
buttonClassName="absolute right-1 top-1"
|
||||
button={
|
||||
|
||||
@@ -153,6 +153,10 @@ export async function authenticatedEnvironmentForAuthentication(
|
||||
projectRef: string,
|
||||
slug: string
|
||||
): Promise<AuthenticatedEnvironment> {
|
||||
if (slug === "staging") {
|
||||
slug = "stg";
|
||||
}
|
||||
|
||||
switch (auth.type) {
|
||||
case "apiKey": {
|
||||
if (auth.result.environment.project.externalRef !== projectRef) {
|
||||
|
||||
@@ -590,11 +590,11 @@ function getWorkerQueue() {
|
||||
},
|
||||
"v3.triggerScheduledTask": {
|
||||
priority: 0,
|
||||
maxAttempts: 3,
|
||||
maxAttempts: 3, // total delay of 30 seconds
|
||||
handler: async (payload, job) => {
|
||||
const service = new TriggerScheduledTaskService();
|
||||
|
||||
return await service.call(payload.instanceId);
|
||||
return await service.call(payload.instanceId, job.attempts === job.max_attempts);
|
||||
},
|
||||
},
|
||||
"v3.performTaskAttemptAlerts": {
|
||||
|
||||
@@ -442,9 +442,11 @@ export function v3DeploymentsPath(organization: OrgForPath, project: ProjectForP
|
||||
export function v3DeploymentPath(
|
||||
organization: OrgForPath,
|
||||
project: ProjectForPath,
|
||||
deployment: DeploymentForPath
|
||||
deployment: DeploymentForPath,
|
||||
currentPage: number
|
||||
) {
|
||||
return `${v3DeploymentsPath(organization, project)}/${deployment.shortCode}`;
|
||||
const query = currentPage ? `?page=${currentPage}` : "";
|
||||
return `${v3DeploymentsPath(organization, project)}/${deployment.shortCode}${query}`;
|
||||
}
|
||||
|
||||
export function v3BillingPath(organization: OrgForPath) {
|
||||
|
||||
@@ -2,10 +2,12 @@ import { clientWebsocketMessages, serverWebsocketMessages } from "@trigger.dev/c
|
||||
import { ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
|
||||
import { Evt } from "evt";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { CloseEvent, ErrorEvent, MessageEvent, WebSocket } from "ws";
|
||||
import type { CloseEvent, ErrorEvent, MessageEvent } from "ws";
|
||||
import { WebSocket } from "ws";
|
||||
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { DevQueueConsumer } from "./marqs/devQueueConsumer.server";
|
||||
import { HeartbeatService } from "./services/heartbeatService.server";
|
||||
|
||||
export class AuthenticatedSocketConnection {
|
||||
public id: string;
|
||||
@@ -14,6 +16,7 @@ export class AuthenticatedSocketConnection {
|
||||
private _sender: ZodMessageSender<typeof serverWebsocketMessages>;
|
||||
private _consumer: DevQueueConsumer;
|
||||
private _messageHandler: ZodMessageHandler<typeof clientWebsocketMessages>;
|
||||
private _pingService: HeartbeatService;
|
||||
|
||||
constructor(
|
||||
public ws: WebSocket,
|
||||
@@ -50,8 +53,42 @@ export class AuthenticatedSocketConnection {
|
||||
ws.addEventListener("close", this.#handleClose.bind(this));
|
||||
ws.addEventListener("error", this.#handleError.bind(this));
|
||||
|
||||
ws.on("ping", (data) => {
|
||||
logger.debug("[AuthenticatedSocketConnection] Received ping", {
|
||||
id: this.id,
|
||||
envId: this.authenticatedEnv.id,
|
||||
data,
|
||||
});
|
||||
});
|
||||
|
||||
ws.on("pong", (data) => {
|
||||
// logger.debug("[AuthenticatedSocketConnection] Received pong", {
|
||||
// id: this.id,
|
||||
// envId: this.authenticatedEnv.id,
|
||||
// data,
|
||||
// });
|
||||
});
|
||||
|
||||
this._pingService = new HeartbeatService({
|
||||
heartbeat: async () => {
|
||||
if (ws.readyState !== WebSocket.OPEN) {
|
||||
logger.debug("[AuthenticatedSocketConnection] Websocket not open, skipping ping");
|
||||
return;
|
||||
}
|
||||
|
||||
// logger.debug("[AuthenticatedSocketConnection] Sending ping", {
|
||||
// id: this.id,
|
||||
// envId: this.authenticatedEnv.id,
|
||||
// });
|
||||
|
||||
ws.ping();
|
||||
},
|
||||
});
|
||||
this._pingService.start();
|
||||
|
||||
this._messageHandler = new ZodMessageHandler({
|
||||
schema: clientWebsocketMessages,
|
||||
logger,
|
||||
messages: {
|
||||
READY_FOR_TASKS: async (payload) => {
|
||||
await this._consumer.registerBackgroundWorker(
|
||||
@@ -99,14 +136,29 @@ export class AuthenticatedSocketConnection {
|
||||
}
|
||||
|
||||
async #handleMessage(ev: MessageEvent) {
|
||||
const data = JSON.parse(ev.data.toString());
|
||||
try {
|
||||
const data = JSON.parse(ev.data.toString());
|
||||
|
||||
await this._messageHandler.handleMessage(data);
|
||||
await this._messageHandler.handleMessage(data);
|
||||
} catch (error) {
|
||||
logger.error("[AuthenticatedSocketConnection] Failed to handle message", {
|
||||
error:
|
||||
error instanceof Error
|
||||
? {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
}
|
||||
: error,
|
||||
message: ev.data.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async #handleClose(ev: CloseEvent) {
|
||||
logger.debug("[AuthenticatedSocketConnection] Websocket closed", { ev });
|
||||
|
||||
this._pingService.stop();
|
||||
|
||||
await this._consumer.stop();
|
||||
|
||||
const result = this.onClose.post(ev);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { WorkerDeployment } from "@trigger.dev/database";
|
||||
|
||||
export const deploymentIndexingIsRetryable = ({
|
||||
builtAt,
|
||||
status,
|
||||
}: {
|
||||
builtAt: Date | null;
|
||||
status: WorkerDeployment["status"];
|
||||
}) => {
|
||||
return builtAt && (status === "FAILED" || status === "TIMED_OUT");
|
||||
};
|
||||
@@ -91,17 +91,13 @@ export class EnvironmentVariablesRepository implements Repository {
|
||||
return { success: false as const, error: `Environment not found` };
|
||||
}
|
||||
|
||||
// Check to see if any of the variables are `TRIGGER_SECRET_KEY` or `TRIGGER_API_URL`
|
||||
const triggerKeys = options.variables.map((v) => v.key);
|
||||
if (triggerKeys.includes("TRIGGER_SECRET_KEY") || triggerKeys.includes("TRIGGER_API_URL")) {
|
||||
return {
|
||||
success: false as const,
|
||||
error: `You cannot set the variables TRIGGER_SECRET_KEY or TRIGGER_API_URL as they will be set automatically`,
|
||||
};
|
||||
}
|
||||
// Remove `TRIGGER_SECRET_KEY` or `TRIGGER_API_URL`
|
||||
let values = options.variables.filter(
|
||||
(v) => v.key !== "TRIGGER_SECRET_KEY" && v.key !== "TRIGGER_API_URL"
|
||||
);
|
||||
|
||||
//get rid of empty variables
|
||||
const values = options.variables.filter((v) => v.key.trim() !== "" && v.value.trim() !== "");
|
||||
values = values.filter((v) => v.key.trim() !== "" && v.value.trim() !== "");
|
||||
if (values.length === 0) {
|
||||
return { success: false as const, error: `You must set at least one value` };
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ export class MarQS {
|
||||
private redis: Redis;
|
||||
public keys: MarQSKeyProducer;
|
||||
private queuePriorityStrategy: MarQSQueuePriorityStrategy;
|
||||
#requeueingWorkers: Array<AsyncWorker> = [];
|
||||
#rebalanceWorkers: Array<AsyncWorker> = [];
|
||||
|
||||
constructor(private readonly options: MarQSOptions) {
|
||||
@@ -846,10 +845,14 @@ export class MarQS {
|
||||
});
|
||||
|
||||
stream.on("data", async (keys) => {
|
||||
stream.pause();
|
||||
|
||||
const uniqueKeys = Array.from(new Set<string>(keys));
|
||||
|
||||
if (uniqueKeys.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stream.pause();
|
||||
|
||||
logger.debug("Rebalancing parent queues", {
|
||||
component: "marqs",
|
||||
operation: "rebalanceParentQueues",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { marqs } from "~/v3/marqs/index.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { ExecuteTasksWaitingForDeployService } from "./executeTasksWaitingForDeploy";
|
||||
import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server";
|
||||
import { TimeoutDeploymentService } from "./timeoutDeployment.server";
|
||||
|
||||
export class CreateDeployedBackgroundWorkerService extends BaseService {
|
||||
public async call(
|
||||
@@ -101,6 +102,7 @@ export class CreateDeployedBackgroundWorkerService extends BaseService {
|
||||
|
||||
await ExecuteTasksWaitingForDeployService.enqueue(backgroundWorker.id, this._prisma);
|
||||
await PerformDeploymentAlertsService.enqueue(deployment.id, this._prisma);
|
||||
await TimeoutDeploymentService.dequeue(deployment.id, this._prisma);
|
||||
|
||||
return backgroundWorker;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
type HeartbeatServiceOptions = {
|
||||
heartbeat: () => Promise<void>;
|
||||
pingIntervalInMs?: number;
|
||||
leadingEdge?: boolean;
|
||||
};
|
||||
|
||||
export class HeartbeatService {
|
||||
private _heartbeat: () => Promise<void>;
|
||||
private _heartbeatIntervalInMs: number;
|
||||
private _nextHeartbeat: NodeJS.Timeout | undefined;
|
||||
private _leadingEdge: boolean;
|
||||
|
||||
constructor(opts: HeartbeatServiceOptions) {
|
||||
this._heartbeat = opts.heartbeat;
|
||||
this._heartbeatIntervalInMs = opts.pingIntervalInMs ?? 45_000;
|
||||
this._nextHeartbeat = undefined;
|
||||
this._leadingEdge = opts.leadingEdge ?? false;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this._leadingEdge) {
|
||||
this.#doHeartbeat();
|
||||
} else {
|
||||
this.#scheduleNextHeartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.#clearNextHeartbeat();
|
||||
}
|
||||
|
||||
#doHeartbeat = async () => {
|
||||
this.#clearNextHeartbeat();
|
||||
|
||||
await this._heartbeat();
|
||||
|
||||
this.#scheduleNextHeartbeat();
|
||||
};
|
||||
|
||||
#clearNextHeartbeat() {
|
||||
if (this._nextHeartbeat) {
|
||||
clearTimeout(this._nextHeartbeat);
|
||||
}
|
||||
}
|
||||
|
||||
#scheduleNextHeartbeat() {
|
||||
this._nextHeartbeat = setTimeout(this.#doHeartbeat, this._heartbeatIntervalInMs);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,14 @@ export class IndexDeploymentService extends BaseService {
|
||||
|
||||
// just broadcast for now - there should only ever be one provider connected
|
||||
try {
|
||||
// timeout the deployment if 180 seconds have passed and the deployment is still not indexed
|
||||
await TimeoutDeploymentService.enqueue(
|
||||
deployment.id,
|
||||
"DEPLOYING",
|
||||
"Could not index deployment in time",
|
||||
new Date(Date.now() + 180_000)
|
||||
);
|
||||
|
||||
const responses = await socketIo.providerNamespace.timeout(30_000).emitWithAck("INDEX", {
|
||||
version: "v1",
|
||||
shortCode: deployment.shortCode,
|
||||
@@ -61,15 +69,7 @@ export class IndexDeploymentService extends BaseService {
|
||||
|
||||
logger.debug("Index ACK received", { responses });
|
||||
|
||||
if (responses.length === 0) {
|
||||
// timeout the deployment if 180 seconds have passed and the deployment is still not indexed
|
||||
await TimeoutDeploymentService.enqueue(
|
||||
deployment.id,
|
||||
"DEPLOYING",
|
||||
"Could not index deployment in time",
|
||||
new Date(Date.now() + 180_000)
|
||||
);
|
||||
} else {
|
||||
if (responses.length > 0) {
|
||||
const indexFailed = new DeploymentIndexFailed();
|
||||
|
||||
for (const response of responses) {
|
||||
@@ -83,12 +83,20 @@ export class IndexDeploymentService extends BaseService {
|
||||
|
||||
const indexFailed = new DeploymentIndexFailed();
|
||||
|
||||
await indexFailed.call(
|
||||
deployment.friendlyId,
|
||||
error instanceof Error
|
||||
? { message: error.message, name: error.name }
|
||||
: { message: "Could not index deployment in time", name: "TimeoutError" }
|
||||
);
|
||||
let indexError = {
|
||||
message: `Could not index deployment: ${error}`,
|
||||
name: "IndexError",
|
||||
};
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message === "operation has timed out") {
|
||||
indexError = { message: "Provider failed to respond in time", name: "TimeoutError" };
|
||||
} else {
|
||||
indexError = { message: error.message, name: error.name };
|
||||
}
|
||||
}
|
||||
|
||||
await indexFailed.call(deployment.friendlyId, indexError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { deploymentIndexingIsRetryable } from "../deploymentStatus";
|
||||
import { BaseService } from "./baseService.server";
|
||||
import { IndexDeploymentService } from "./indexDeployment.server";
|
||||
|
||||
export class RetryDeploymentIndexingService extends BaseService {
|
||||
public async call(deploymentId: string) {
|
||||
const deployment = await this._prisma.workerDeployment.findUnique({
|
||||
where: {
|
||||
id: deploymentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!deployment) {
|
||||
throw new Error("Deployment not found");
|
||||
}
|
||||
|
||||
if (!deploymentIndexingIsRetryable(deployment)) {
|
||||
throw new Error("Deployment indexing not retryable");
|
||||
}
|
||||
|
||||
const latestDeployment = await this._prisma.workerDeployment.findFirst({
|
||||
where: {
|
||||
projectId: deployment.projectId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
if (!latestDeployment || latestDeployment.id !== deployment.id) {
|
||||
throw new Error("Deployment is not the latest");
|
||||
}
|
||||
|
||||
await this._prisma.workerDeployment.update({
|
||||
where: {
|
||||
id: deploymentId,
|
||||
},
|
||||
data: {
|
||||
status: "DEPLOYING",
|
||||
},
|
||||
});
|
||||
|
||||
await IndexDeploymentService.enqueue(deployment.id);
|
||||
|
||||
return deployment;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export class StartDeploymentIndexing extends BaseService {
|
||||
? registryProxy.rewriteImageReference(body.imageReference)
|
||||
: body.imageReference,
|
||||
status: "DEPLOYING",
|
||||
builtAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { logger } from "~/services/logger.server";
|
||||
import { BaseService } from "./baseService.server";
|
||||
import { workerQueue } from "~/services/worker.server";
|
||||
import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server";
|
||||
import { PrismaClientOrTransaction } from "~/db.server";
|
||||
|
||||
export class TimeoutDeploymentService extends BaseService {
|
||||
public async call(id: string, fromStatus: string, errorMessage: string) {
|
||||
@@ -20,6 +21,10 @@ export class TimeoutDeploymentService extends BaseService {
|
||||
}
|
||||
|
||||
if (deployment.status !== fromStatus) {
|
||||
logger.error("Deployment is not in the correct state to be timed out", {
|
||||
currentStatus: deployment.status,
|
||||
fromStatus,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,4 +62,8 @@ export class TimeoutDeploymentService extends BaseService {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static async dequeue(deploymentId: string, tx?: PrismaClientOrTransaction) {
|
||||
await workerQueue.dequeue(`timeoutDeployment:${deploymentId}`, { tx });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import { findCurrentWorkerDeployment } from "../models/workerDeployment.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
|
||||
export class TriggerScheduledTaskService extends BaseService {
|
||||
public async call(instanceId: string) {
|
||||
public async call(instanceId: string, finalAttempt: boolean) {
|
||||
const registerNextService = new RegisterNextTaskScheduleInstanceService();
|
||||
|
||||
const instance = await this._prisma.taskScheduleInstance.findUnique({
|
||||
where: {
|
||||
id: instanceId,
|
||||
@@ -30,124 +32,135 @@ export class TriggerScheduledTaskService extends BaseService {
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldTrigger = true;
|
||||
|
||||
if (!instance.active) {
|
||||
shouldTrigger = false;
|
||||
}
|
||||
|
||||
if (!instance.taskSchedule.active) {
|
||||
shouldTrigger = false;
|
||||
}
|
||||
|
||||
if (!instance.nextScheduledTimestamp) {
|
||||
shouldTrigger = false;
|
||||
}
|
||||
|
||||
if (
|
||||
instance.environment.type === "DEVELOPMENT" &&
|
||||
(!instance.environment.currentSession || instance.environment.currentSession.disconnectedAt)
|
||||
) {
|
||||
shouldTrigger = false;
|
||||
}
|
||||
|
||||
if (instance.environment.type !== "DEVELOPMENT") {
|
||||
// Get the current backgroundWorker for this environment
|
||||
const currentWorkerDeployment = await findCurrentWorkerDeployment(instance.environment.id);
|
||||
|
||||
if (!currentWorkerDeployment) {
|
||||
logger.debug("No current worker deployment found, skipping task trigger", {
|
||||
instanceId,
|
||||
scheduleId: instance.taskSchedule.friendlyId,
|
||||
environmentId: instance.environment.id,
|
||||
});
|
||||
try {
|
||||
let shouldTrigger = true;
|
||||
|
||||
if (!instance.active) {
|
||||
shouldTrigger = false;
|
||||
} else if (
|
||||
!currentWorkerDeployment.worker ||
|
||||
!currentWorkerDeployment.worker.tasks.some(
|
||||
(t) => t.slug === instance.taskSchedule.taskIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
if (!instance.taskSchedule.active) {
|
||||
shouldTrigger = false;
|
||||
}
|
||||
|
||||
if (!instance.nextScheduledTimestamp) {
|
||||
shouldTrigger = false;
|
||||
}
|
||||
|
||||
if (
|
||||
instance.environment.type === "DEVELOPMENT" &&
|
||||
(!instance.environment.currentSession || instance.environment.currentSession.disconnectedAt)
|
||||
) {
|
||||
logger.debug(
|
||||
"Current worker deployment does not contain the scheduled task identifier, skipping task trigger",
|
||||
{
|
||||
shouldTrigger = false;
|
||||
}
|
||||
|
||||
if (instance.environment.type !== "DEVELOPMENT") {
|
||||
// Get the current backgroundWorker for this environment
|
||||
const currentWorkerDeployment = await findCurrentWorkerDeployment(instance.environment.id);
|
||||
|
||||
if (!currentWorkerDeployment) {
|
||||
logger.debug("No current worker deployment found, skipping task trigger", {
|
||||
instanceId,
|
||||
scheduleId: instance.taskSchedule.friendlyId,
|
||||
environmentId: instance.environment.id,
|
||||
workerDeploymentId: currentWorkerDeployment.id,
|
||||
workerId: currentWorkerDeployment.worker?.id,
|
||||
taskIdentifier: instance.taskSchedule.taskIdentifier,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
shouldTrigger = false;
|
||||
shouldTrigger = false;
|
||||
} else if (
|
||||
!currentWorkerDeployment.worker ||
|
||||
!currentWorkerDeployment.worker.tasks.some(
|
||||
(t) => t.slug === instance.taskSchedule.taskIdentifier
|
||||
)
|
||||
) {
|
||||
logger.debug(
|
||||
"Current worker deployment does not contain the scheduled task identifier, skipping task trigger",
|
||||
{
|
||||
instanceId,
|
||||
scheduleId: instance.taskSchedule.friendlyId,
|
||||
environmentId: instance.environment.id,
|
||||
workerDeploymentId: currentWorkerDeployment.id,
|
||||
workerId: currentWorkerDeployment.worker?.id,
|
||||
taskIdentifier: instance.taskSchedule.taskIdentifier,
|
||||
}
|
||||
);
|
||||
|
||||
shouldTrigger = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const registerNextService = new RegisterNextTaskScheduleInstanceService();
|
||||
if (shouldTrigger) {
|
||||
// Enqueue triggering the task
|
||||
const triggerTask = new TriggerTaskService();
|
||||
|
||||
if (shouldTrigger) {
|
||||
// Enqueue triggering the task
|
||||
const triggerTask = new TriggerTaskService();
|
||||
|
||||
const payload = {
|
||||
scheduleId: instance.taskSchedule.friendlyId,
|
||||
timestamp: instance.nextScheduledTimestamp,
|
||||
lastTimestamp: instance.lastScheduledTimestamp ?? undefined,
|
||||
externalId: instance.taskSchedule.externalId ?? undefined,
|
||||
timezone: instance.taskSchedule.timezone,
|
||||
upcoming: nextScheduledTimestamps(
|
||||
instance.taskSchedule.generatorExpression,
|
||||
instance.taskSchedule.timezone,
|
||||
instance.nextScheduledTimestamp!,
|
||||
10
|
||||
),
|
||||
};
|
||||
|
||||
const payloadPacket = await stringifyIO(payload);
|
||||
|
||||
logger.debug("Triggering scheduled task", {
|
||||
instance,
|
||||
payloadPacket,
|
||||
});
|
||||
|
||||
const run = await triggerTask.call(
|
||||
instance.taskSchedule.taskIdentifier,
|
||||
instance.environment,
|
||||
{ payload: payloadPacket.data, options: { payloadType: payloadPacket.dataType } },
|
||||
{ customIcon: "scheduled" }
|
||||
);
|
||||
|
||||
if (!run) {
|
||||
logger.error("Failed to trigger task", {
|
||||
instanceId,
|
||||
const payload = {
|
||||
scheduleId: instance.taskSchedule.friendlyId,
|
||||
timestamp: instance.nextScheduledTimestamp,
|
||||
lastTimestamp: instance.lastScheduledTimestamp ?? undefined,
|
||||
externalId: instance.taskSchedule.externalId ?? undefined,
|
||||
timezone: instance.taskSchedule.timezone,
|
||||
upcoming: nextScheduledTimestamps(
|
||||
instance.taskSchedule.generatorExpression,
|
||||
instance.taskSchedule.timezone,
|
||||
instance.nextScheduledTimestamp!,
|
||||
10
|
||||
),
|
||||
};
|
||||
|
||||
const payloadPacket = await stringifyIO(payload);
|
||||
|
||||
logger.debug("Triggering scheduled task", {
|
||||
instance,
|
||||
payloadPacket,
|
||||
});
|
||||
} else {
|
||||
await this._prisma.taskRun.update({
|
||||
where: {
|
||||
id: run.id,
|
||||
},
|
||||
data: {
|
||||
scheduleId: instance.taskSchedule.id,
|
||||
scheduleInstanceId: instance.id,
|
||||
},
|
||||
|
||||
const run = await triggerTask.call(
|
||||
instance.taskSchedule.taskIdentifier,
|
||||
instance.environment,
|
||||
{ payload: payloadPacket.data, options: { payloadType: payloadPacket.dataType } },
|
||||
{ customIcon: "scheduled" }
|
||||
);
|
||||
|
||||
if (!run) {
|
||||
logger.error("Failed to trigger task", {
|
||||
instanceId,
|
||||
scheduleId: instance.taskSchedule.friendlyId,
|
||||
payloadPacket,
|
||||
});
|
||||
} else {
|
||||
await this._prisma.taskRun.update({
|
||||
where: {
|
||||
id: run.id,
|
||||
},
|
||||
data: {
|
||||
scheduleId: instance.taskSchedule.id,
|
||||
scheduleInstanceId: instance.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await this._prisma.taskScheduleInstance.update({
|
||||
where: {
|
||||
id: instanceId,
|
||||
},
|
||||
data: {
|
||||
lastScheduledTimestamp: instance.nextScheduledTimestamp,
|
||||
},
|
||||
});
|
||||
|
||||
await registerNextService.call(instanceId);
|
||||
} catch (e) {
|
||||
if (finalAttempt) {
|
||||
logger.error("Failed to trigger scheduled task, rescheduling the next run", {
|
||||
instanceId,
|
||||
error: e,
|
||||
});
|
||||
|
||||
await registerNextService.call(instanceId);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
await this._prisma.taskScheduleInstance.update({
|
||||
where: {
|
||||
id: instanceId,
|
||||
},
|
||||
data: {
|
||||
lastScheduledTimestamp: instance.nextScheduledTimestamp,
|
||||
},
|
||||
});
|
||||
|
||||
await registerNextService.call(instanceId);
|
||||
}
|
||||
|
||||
public static async enqueue(instanceId: string, runAt: Date, tx?: PrismaClientOrTransaction) {
|
||||
|
||||
@@ -96,6 +96,7 @@ export class SharedSocketConnection {
|
||||
|
||||
this._messageHandler = new ZodMessageHandler({
|
||||
schema: clientWebsocketMessages,
|
||||
logger,
|
||||
messages: {
|
||||
READY_FOR_TASKS: async (payload) => {
|
||||
this._sharedQueueConsumerPool.start();
|
||||
|
||||
@@ -39,6 +39,7 @@ class RedisZodSubscriber<TMessageCatalog extends ZodMessageCatalogSchema>
|
||||
this._subscriber = new Redis(_options.redis);
|
||||
this._messageHandler = new ZodMessageHandler({
|
||||
schema: _options.schema,
|
||||
logger: this._logger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,20 +77,26 @@ class RedisZodSubscriber<TMessageCatalog extends ZodMessageCatalogSchema>
|
||||
|
||||
const message = this._messageHandler.parseMessage(parsedMessage);
|
||||
|
||||
if (typeof message.type !== "string") {
|
||||
if (!message.success) {
|
||||
this._logger.error(`Failed to parse message: ${message.error}`, { parsedMessage });
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = this._listeners.get(message.type);
|
||||
if (typeof message.data.type !== "string") {
|
||||
this._logger.error(`Failed to parse message: invalid type`, { parsedMessage });
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = this._listeners.get(message.data.type);
|
||||
|
||||
if (!listener) {
|
||||
this._logger.debug(`No listener for message type: ${message.type}`, { parsedMessage });
|
||||
this._logger.debug(`No listener for message type: ${message.data.type}`, { parsedMessage });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await listener(message.payload);
|
||||
await listener(message.data.payload);
|
||||
} catch (error) {
|
||||
this._logger.error("Error handling message", { error, message });
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@total-typescript/ts-reset": "^0.4.2",
|
||||
"@trigger.dev/eslint-plugin": "workspace:*",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/eslint": "^8.4.6",
|
||||
@@ -221,6 +222,9 @@
|
||||
"esbuild": "^0.15.10",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-turbo": "^2.0.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-import": "^16.0.1",
|
||||
"postcss-loader": "^8.1.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
set -xe
|
||||
|
||||
if [ -n "$DATABASE_HOST" ]; then
|
||||
scripts/wait-for-it.sh ${DATABASE_HOST} -- echo "database is up"
|
||||
@@ -8,10 +8,9 @@ fi
|
||||
# Run migrations
|
||||
pnpm --filter @trigger.dev/database db:migrate:deploy
|
||||
|
||||
# Copy over required prisma files and invoke bundled seed file
|
||||
# Copy over required prisma files
|
||||
cp packages/database/prisma/schema.prisma apps/webapp/prisma/
|
||||
cp node_modules/@prisma/engines/*.node apps/webapp/prisma/
|
||||
pnpm --filter webapp db:seed
|
||||
|
||||
cd /triggerdotdev/apps/webapp
|
||||
# exec dumb-init pnpm run start:local
|
||||
|
||||
+24
-78
@@ -1,14 +1,8 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/schema.json",
|
||||
"name": "Trigger.dev",
|
||||
"openapi": [
|
||||
"/openapi.yml",
|
||||
"/v3-openapi.yaml"
|
||||
],
|
||||
"versions": [
|
||||
"v3",
|
||||
"v2"
|
||||
],
|
||||
"openapi": ["/openapi.yml", "/v3-openapi.yaml"],
|
||||
"versions": ["v3", "v2"],
|
||||
"api": {
|
||||
"playground": {
|
||||
"mode": "simple"
|
||||
@@ -76,9 +70,7 @@
|
||||
{
|
||||
"group": "",
|
||||
"version": "v3",
|
||||
"pages": [
|
||||
"v3/introduction"
|
||||
]
|
||||
"pages": ["v3/introduction"]
|
||||
},
|
||||
{
|
||||
"group": "Getting Started",
|
||||
@@ -101,10 +93,7 @@
|
||||
"v3/apikeys",
|
||||
{
|
||||
"group": "Task types",
|
||||
"pages": [
|
||||
"v3/tasks-regular",
|
||||
"v3/tasks-scheduled"
|
||||
]
|
||||
"pages": ["v3/tasks-regular", "v3/tasks-scheduled"]
|
||||
},
|
||||
"v3/trigger-config"
|
||||
]
|
||||
@@ -112,10 +101,7 @@
|
||||
{
|
||||
"group": "Development",
|
||||
"version": "v3",
|
||||
"pages": [
|
||||
"v3/cli-dev",
|
||||
"v3/run-tests"
|
||||
]
|
||||
"pages": ["v3/cli-dev", "v3/run-tests"]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
@@ -126,9 +112,7 @@
|
||||
"v3/github-actions",
|
||||
{
|
||||
"group": "Deployment integrations",
|
||||
"pages": [
|
||||
"v3/vercel-integration"
|
||||
]
|
||||
"pages": ["v3/vercel-integration"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -154,7 +138,8 @@
|
||||
"v3/machines",
|
||||
"v3/idempotency",
|
||||
"v3/reattempting-replaying",
|
||||
"v3/notifications"
|
||||
"v3/notifications",
|
||||
"v3/run-usage"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -164,10 +149,7 @@
|
||||
"v3/management/overview",
|
||||
{
|
||||
"group": "Tasks API",
|
||||
"pages": [
|
||||
"v3/management/tasks/trigger",
|
||||
"v3/management/tasks/batch-trigger"
|
||||
]
|
||||
"pages": ["v3/management/tasks/trigger", "v3/management/tasks/batch-trigger"]
|
||||
},
|
||||
{
|
||||
"group": "Runs API",
|
||||
@@ -205,26 +187,21 @@
|
||||
},
|
||||
{
|
||||
"group": "Projects API",
|
||||
"pages": [
|
||||
"v3/management/projects/runs"
|
||||
]
|
||||
"pages": ["v3/management/projects/runs"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Open source",
|
||||
"version": "v3",
|
||||
"pages": [
|
||||
"v3/github-repo",
|
||||
"v3/open-source-self-hosting",
|
||||
"v3/open-source-contributing"
|
||||
]
|
||||
"pages": ["v3/github-repo", "v3/open-source-self-hosting", "v3/open-source-contributing"]
|
||||
},
|
||||
{
|
||||
"group": "Troubleshooting",
|
||||
"version": "v3",
|
||||
"pages": [
|
||||
"v3/troubleshooting",
|
||||
"v3/upgrading-packages",
|
||||
"v3/troubleshooting-alerts",
|
||||
"v3/troubleshooting-uptime-status",
|
||||
"v3/troubleshooting-github-issues",
|
||||
@@ -234,11 +211,7 @@
|
||||
{
|
||||
"group": "Help",
|
||||
"version": "v3",
|
||||
"pages": [
|
||||
"v3/community",
|
||||
"v3/help-slack",
|
||||
"v3/help-email"
|
||||
]
|
||||
"pages": ["v3/community", "v3/help-slack", "v3/help-email"]
|
||||
},
|
||||
{
|
||||
"group": "Getting Started",
|
||||
@@ -430,10 +403,7 @@
|
||||
"pages": [
|
||||
{
|
||||
"group": "Airtable",
|
||||
"pages": [
|
||||
"integrations/apis/airtable",
|
||||
"integrations/apis/airtable-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/airtable", "integrations/apis/airtable-tasks"]
|
||||
},
|
||||
{
|
||||
"group": "GitHub",
|
||||
@@ -459,25 +429,16 @@
|
||||
},
|
||||
{
|
||||
"group": "Plain",
|
||||
"pages": [
|
||||
"integrations/apis/plain",
|
||||
"integrations/apis/plain-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/plain", "integrations/apis/plain-tasks"]
|
||||
},
|
||||
"integrations/apis/replicate",
|
||||
{
|
||||
"group": "SendGrid",
|
||||
"pages": [
|
||||
"integrations/apis/sendgrid",
|
||||
"integrations/apis/sendgrid-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/sendgrid", "integrations/apis/sendgrid-tasks"]
|
||||
},
|
||||
{
|
||||
"group": "Resend",
|
||||
"pages": [
|
||||
"integrations/apis/resend",
|
||||
"integrations/apis/resend-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/resend", "integrations/apis/resend-tasks"]
|
||||
},
|
||||
{
|
||||
"group": "Shopify",
|
||||
@@ -489,10 +450,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Slack",
|
||||
"pages": [
|
||||
"integrations/apis/slack",
|
||||
"integrations/apis/slack-tasks"
|
||||
]
|
||||
"pages": ["integrations/apis/slack", "integrations/apis/slack-tasks"]
|
||||
},
|
||||
"integrations/apis/stripe",
|
||||
{
|
||||
@@ -518,9 +476,7 @@
|
||||
"sdk/triggerclient/constructor",
|
||||
{
|
||||
"group": "Instance properties",
|
||||
"pages": [
|
||||
"sdk/triggerclient/store"
|
||||
]
|
||||
"pages": ["sdk/triggerclient/store"]
|
||||
},
|
||||
{
|
||||
"group": "Instance methods",
|
||||
@@ -583,10 +539,7 @@
|
||||
"sdk/dynamictrigger/constructor",
|
||||
{
|
||||
"group": "Instance methods",
|
||||
"pages": [
|
||||
"sdk/dynamictrigger/register",
|
||||
"sdk/dynamictrigger/unregister"
|
||||
]
|
||||
"pages": ["sdk/dynamictrigger/register", "sdk/dynamictrigger/unregister"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -597,10 +550,7 @@
|
||||
"sdk/dynamicschedule/constructor",
|
||||
{
|
||||
"group": "Instance methods",
|
||||
"pages": [
|
||||
"sdk/dynamicschedule/register",
|
||||
"sdk/dynamicschedule/unregister"
|
||||
]
|
||||
"pages": ["sdk/dynamicschedule/register", "sdk/dynamicschedule/unregister"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -613,9 +563,7 @@
|
||||
{
|
||||
"group": "HTTP Reference",
|
||||
"version": "v2",
|
||||
"pages": [
|
||||
"sdk/api-reference/events/create-an-event"
|
||||
]
|
||||
"pages": ["sdk/api-reference/events/create-an-event"]
|
||||
},
|
||||
{
|
||||
"group": "React SDK",
|
||||
@@ -631,9 +579,7 @@
|
||||
{
|
||||
"group": "Overview",
|
||||
"version": "v2",
|
||||
"pages": [
|
||||
"examples/introduction"
|
||||
]
|
||||
"pages": ["examples/introduction"]
|
||||
}
|
||||
],
|
||||
"footerSocials": {
|
||||
@@ -641,4 +587,4 @@
|
||||
"github": "https://github.com/triggerdotdev",
|
||||
"linkedin": "https://www.linkedin.com/company/triggerdotdev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-7
@@ -925,7 +925,6 @@ paths:
|
||||
override: false
|
||||
});
|
||||
|
||||
|
||||
"/api/v1/projects/{projectRef}/envvars/{env}/{name}":
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/projectRef"
|
||||
@@ -1314,10 +1313,6 @@ paths:
|
||||
]
|
||||
}'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
components:
|
||||
parameters:
|
||||
taskIdentifier:
|
||||
@@ -1496,14 +1491,33 @@ components:
|
||||
type: object
|
||||
properties:
|
||||
queue:
|
||||
$ref: '#/components/schemas/QueueOptions'
|
||||
$ref: "#/components/schemas/QueueOptions"
|
||||
concurrencyKey:
|
||||
type: string
|
||||
description: Scope the concurrency limit to a specific key.
|
||||
idempotencyKey:
|
||||
type: string
|
||||
description: An optional property that specifies the idempotency key used to prevent creating duplicate runs. If you provide an existing idempotency key, we will return the existing run ID.
|
||||
|
||||
ttl:
|
||||
$ref: "#/components/schemas/TTL"
|
||||
delay:
|
||||
$ref: "#/components/schemas/Delay"
|
||||
TTL:
|
||||
type:
|
||||
- string
|
||||
- number
|
||||
description: "The time-to-live for this run. If the run is not executed within this time, it will be removed from the queue and never execute. You can use a string in this format: `1h`, `1m`, `1h42m` or a number of seconds (min. 1)."
|
||||
Delay:
|
||||
type: string
|
||||
description: |
|
||||
The delay before the task is executed. This can be a Date object, a string like `1h` or a date-time string.
|
||||
|
||||
* "1h" - 1 hour
|
||||
* "30d" - 30 days
|
||||
* "15m" - 15 minutes
|
||||
* "2w" - 2 weeks
|
||||
* "60s" - 60 seconds
|
||||
* new Date("2025-01-01T00:00:00Z")
|
||||
EnvFilter:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -14,12 +14,11 @@ description: "What features are currently available in the Developer Preview"
|
||||
| [Atomic versioning](/v3/versioning) | Each deploy creates a new version | ✅ |
|
||||
| [Deploy via CLI](/v3/cli-deploy) | Deploy from the command line | ✅ |
|
||||
| [Deploy via GitHub Actions](/v3/github-actions) | Deploy using GitHub Actions | ✅ |
|
||||
| Alerts | Add alerts in the UI for errors and deploys | ✅ |
|
||||
| [Alerts](/v3/troubleshooting-alerts) | Add alerts in the UI for errors and deploys | ✅ |
|
||||
| [Scheduled tasks](/v3/tasks-scheduled) | A task that can be triggered on a schedule | ✅ |
|
||||
| [Zod tasks](/v3/tasks-zod) | Define tasks using Zod schemas | ⏳ |
|
||||
| Full text search of runs | Find a run by searching the payload and output | ⏳ |
|
||||
| Logs view with search | All logs view with filtering and full text search | ⏳ |
|
||||
| Notifications | Send data to your web app from a run | ⏳ |
|
||||
| Rollbacks | Easily rollback changes when errors happen | ⏳ |
|
||||
|
||||
[Let us know](https://trigger.dev/discord) what we should prioritize and what we are missing.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "GitHub Actions"
|
||||
description: "You can easily deploy your tasks with GitHub actions."
|
||||
---
|
||||
|
||||
This simple GitHub action file will deploy you Trigger.dev tasks when new code is pushed to the `main` branch and the `trigger` directory has changes in it.
|
||||
This simple GitHub action file will deploy your Trigger.dev tasks when new code is pushed to the `main` branch and the `trigger` directory has changes in it.
|
||||
|
||||
<Warning>
|
||||
The deploy step will fail if any version mismatches are detected. Please see the [version
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
If you already have a GitHub action file, you can just add the final step "🚀 Deploy Trigger.dev" to your existing file.
|
||||
|
||||
You need to add the `TRIGGER_ACCESS_TOKEN` secret to your repository. You can create a new access token by going to your profile page and then clicking on the "Personal Access Tokens" tab.
|
||||
You need to add the `TRIGGER_ACCESS_TOKEN` secret to your repository. You can create a new access token by going to your profile page and then clicking on the ["Personal Access Tokens"](https://cloud.trigger.dev/account/tokens) tab.
|
||||
|
||||
To set it in GitHub go to your repository, click on "Settings", "Secrets and variables" and then "Actions". Add a new secret with the name `TRIGGER_ACCESS_TOKEN` and use the value of your access token.
|
||||
|
||||
|
||||
@@ -39,3 +39,5 @@ export const config: TriggerConfig = {
|
||||
| medium-2x | 2 | 4 |
|
||||
| large-1x | 4 | 8 |
|
||||
| large-2x | 8 | 16 |
|
||||
|
||||
You can view the Trigger.dev cloud pricing for these machines [here](https://trigger.dev/pricing#computePricing).
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: "Usage"
|
||||
description: "Get compute duration and cost from inside a run, or for a specific block of code."
|
||||
---
|
||||
|
||||
## Getting the run cost and duration
|
||||
|
||||
You can get the cost and duration of the current including retries of the same run.
|
||||
|
||||
```ts
|
||||
export const heavyTask = task({
|
||||
id: "heavy-task",
|
||||
machine: {
|
||||
preset: "medium-2x",
|
||||
},
|
||||
run: async (payload, { ctx }) => {
|
||||
// Do some compute
|
||||
const result = await convertVideo(payload.videoUrl);
|
||||
|
||||
// Get the current cost and duration up until this line of code
|
||||
// This includes the compute time of the previous lines
|
||||
let currentUsage = usage.getCurrent();
|
||||
/* currentUsage = {
|
||||
compute: {
|
||||
attempt: {
|
||||
costInCents: 0.01700,
|
||||
durationMs: 1000,
|
||||
},
|
||||
total: {
|
||||
costInCents: 0.0255,
|
||||
durationMs: 1500,
|
||||
},
|
||||
},
|
||||
baseCostInCents: 0.0025,
|
||||
totalCostInCents: 0.028,
|
||||
}
|
||||
*/
|
||||
|
||||
// In the cloud product we do not count waits towards the compute cost or duration.
|
||||
// We also don't include time between attempts or before the run starts executing your code.
|
||||
// So this line does not affect the cost or duration.
|
||||
await wait.for({ seconds: 5 });
|
||||
|
||||
// This will give the same result as before the wait.
|
||||
currentUsage = usage.getCurrent();
|
||||
|
||||
// Do more compute
|
||||
const result = await convertVideo(payload.videoUrl);
|
||||
|
||||
// This would give a different value
|
||||
currentUsage = usage.getCurrent();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
In Trigger.dev cloud we do not include time between attempts, before your code executes, or waits
|
||||
towards the compute cost or duration.
|
||||
</Note>
|
||||
|
||||
## Getting the cost and duration of a block of code
|
||||
|
||||
You can also wrap code with `usage.measure` to get the cost and duration of that block of code:
|
||||
|
||||
```ts
|
||||
// Inside a task run function, or inside a function that's called from there.
|
||||
const { result, compute } = await usage.measure(async () => {
|
||||
//...Do something for 1 second
|
||||
return {
|
||||
foo: "bar",
|
||||
};
|
||||
});
|
||||
|
||||
logger.info("Result", { result, compute });
|
||||
/* result = {
|
||||
foo: "bar"
|
||||
}
|
||||
compute = {
|
||||
costInCents: 0.01700,
|
||||
durationMs: 1000,
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
This will work from inside the `run` function, our lifecycle hooks (like `onStart`, `onFailure`, `onSuccess`, etc.), or any function you're calling from the `run` function. It won't work for code that's not executed using Trigger.dev.
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: "How to upgrade the Trigger.dev packages"
|
||||
sidebarTitle: "Upgrading packages"
|
||||
description: "When we release fixes and new features we recommend you upgrade your Trigger.dev packages."
|
||||
---
|
||||
|
||||
## Update command
|
||||
|
||||
Run this command in your project:
|
||||
|
||||
```sh
|
||||
npx trigger.dev@beta update
|
||||
```
|
||||
|
||||
This will update all of the Trigger.dev packages in your project to the latest version.
|
||||
|
||||
## Running the CLI locally
|
||||
|
||||
When you run the CLI locally use the latest version for the `dev` and `deploy` commands:
|
||||
|
||||
```sh
|
||||
npx trigger.dev@beta dev
|
||||
```
|
||||
|
||||
```sh
|
||||
npx trigger.dev@beta deploy
|
||||
```
|
||||
|
||||
These commands will also give you the option to upgrade if you are behind on versions.
|
||||
|
||||
## Deploying with GitHub Actions
|
||||
|
||||
You can deploy using [GitHub Actions](/v3/github-actions). We recommend that you lock your version in the workflow file so make sure to upgrade.
|
||||
|
||||
<Warning>
|
||||
The deploy step will fail if version mismatches are detected. It's important that you update the
|
||||
version using the steps below.
|
||||
</Warning>
|
||||
|
||||
<Steps>
|
||||
<Step title="Find your workflow file">
|
||||
In your `.githubs/workflows` folder you can find your workflow yml files. You may have a prod
|
||||
and staging one.
|
||||
</Step>
|
||||
<Step title="Update the version for the run command">
|
||||
In the steps you'll see a `run` command. It will run the trigger.dev deploy CLI command. Make
|
||||
sure to update this version to the latest version (e.g. `npx trigger.dev@3.0.0-beta.48 deploy`).
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## package.json dev dependency
|
||||
|
||||
Instead of using `npx`, `pnpm dlx` or `yarn dlx` you can add the Trigger.dev CLI as a dev dependency to your package.json file.
|
||||
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"trigger.dev": "3.0.0-beta.48"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you've done this make sure to update the version to match the `@trigger.dev/sdk` package.
|
||||
|
||||
Once you have added the `trigger.dev` package to your `devDependencies`, you can use `npm exec trigger.dev`, `pnpm exec trigger.dev`, or `yarn exec trigger.dev` to run the CLI.
|
||||
|
||||
But we recommend adding your dev and deploy commands to the `scripts` section of your `package.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev:trigger": "trigger.dev dev",
|
||||
"deploy:trigger": "trigger.dev deploy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then you can run `npm run dev:trigger` and `npm run deploy:trigger` to run the CLI.
|
||||
@@ -19,8 +19,3 @@ Before digging deeper into the details of writing tasks, you should read the [fu
|
||||
| [Idempotency](/v3/idempotency) | Protect against mutations happening twice. |
|
||||
| [Reattempting & Replaying](/v3/reattempting-replaying) | You can reattempt a task that has failed all of its attempts. You can also replay a task with a new version of your code. |
|
||||
| [Notifications](/v3/notifications) | Send realtime notifications from your task that you can subscribe to from your backend or frontend. |
|
||||
| [Rollbacks](/v3/rollbacks) | Rollback code inside a task when errors happen to provide transactional guarantees. |
|
||||
| [Using APIs](/v3/using-apis) | How to use APIs from within your tasks. |
|
||||
| [Trigger filters](/v3/trigger-filters) | Prevent unwanted filters where the payload doesn't match your filter. |
|
||||
| [Middleware](/v3/middleware) | Middleware can wrap the task run function. |
|
||||
| [Automated tests](/v3/automated-tests) | Write automated tests in code. |
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/airtable
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/airtable",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "Trigger.dev integration for airtable",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,8 +25,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"airtable": "^0.12.1",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/github
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/github",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "The official GitHub integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -30,8 +30,8 @@
|
||||
"@octokit/request-error": "^5.0.1",
|
||||
"@octokit/webhooks": "^12.0.10",
|
||||
"octokit": "^3.1.2",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/linear
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/linear",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "Trigger.dev integration for @linear/sdk",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@linear/sdk": "^8.0.0",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/slack
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/openai",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "The official OpenAI integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"openai": "^4.16.1",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47"
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/plain
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/plain",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "The official Plain.com integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -24,8 +24,8 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"@team-plain/typescript-sdk": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/replicate
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/replicate",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "Trigger.dev integration for replicate",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,8 +25,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"replicate": "^0.18.1",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/resend
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/resend",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "The official Resend.com integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -24,8 +24,8 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"resend": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/sendgrid
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/sendgrid",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "Trigger.dev integration for @sendgrid/mail",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47"
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.8.0"
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/shopify
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/shopify",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "Trigger.dev integration for @shopify/shopify-api",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@shopify/shopify-api": "^8.0.2",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @trigger.dev/slack
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/slack",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "The official Slack integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@slack/web-api": "^6.8.1",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/stripe
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/stripe",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "Trigger.dev integration for stripe",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,8 +25,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"stripe": "^12.14.0",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/supabase
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/supabase",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "Trigger.dev integration for @supabase/supabase-js",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.26.0",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"supabase-management-js": "^1.0.0",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @trigger.dev/typeform
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.49
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
- @trigger.dev/integration-kit@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/typeform",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "The official Typeform integration for Trigger.dev",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -24,8 +24,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47",
|
||||
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.49",
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49",
|
||||
"@typeform/api-client": "^1.8.0",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"@types/node": "18.17.1",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"eslint-plugin-turbo": "^2.0.4",
|
||||
"prettier": "^3.0.0",
|
||||
"tsx": "^3.7.1",
|
||||
"turbo": "^1.10.3",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @trigger.dev/astro
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/sdk@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ecf1110ab]
|
||||
- @trigger.dev/sdk@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@trigger.dev/astro",
|
||||
"description": "An Astro-native integration for Trigger.dev background jobs platform",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
@@ -20,7 +20,7 @@
|
||||
"build:tsup": "tsup"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.47"
|
||||
"@trigger.dev/sdk": "workspace:^3.0.0-beta.49"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^3.0.12",
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# trigger.dev
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9971de6a1: Increase span attribute value length limit to 2048
|
||||
- dbda820a7: - Prevent uncaught exceptions when handling WebSocket messages
|
||||
- Improve CLI dev command WebSocket debug and error logging
|
||||
- c2707800a: Improve prisma errors for missing postinstall
|
||||
- d6c6dc993: try/catch opening the login URL
|
||||
- e417aca87: Added config option extraCACerts to ProjectConfig type. This copies the ca file along with additionalFiles and sets NODE_EXTRA_CA_CERTS environment variable in built image as well as running the task.
|
||||
- Updated dependencies [dbda820a7]
|
||||
- Updated dependencies [e417aca87]
|
||||
- Updated dependencies [d934feb02]
|
||||
- @trigger.dev/core@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/core@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trigger.dev",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -87,7 +87,7 @@
|
||||
"@opentelemetry/sdk-trace-base": "^1.22.0",
|
||||
"@opentelemetry/sdk-trace-node": "^1.22.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.22.0",
|
||||
"@trigger.dev/core": "workspace:3.0.0-beta.47",
|
||||
"@trigger.dev/core": "workspace:3.0.0-beta.49",
|
||||
"@types/degit": "^2.8.3",
|
||||
"chalk": "^5.2.0",
|
||||
"chokidar": "^3.5.3",
|
||||
|
||||
@@ -43,12 +43,14 @@ ARG TRIGGER_DEPLOYMENT_ID
|
||||
ARG TRIGGER_DEPLOYMENT_VERSION
|
||||
ARG TRIGGER_CONTENT_HASH
|
||||
ARG TRIGGER_PROJECT_REF
|
||||
ARG NODE_EXTRA_CA_CERTS
|
||||
|
||||
ENV TRIGGER_PROJECT_ID=${TRIGGER_PROJECT_ID} \
|
||||
TRIGGER_DEPLOYMENT_ID=${TRIGGER_DEPLOYMENT_ID} \
|
||||
TRIGGER_DEPLOYMENT_VERSION=${TRIGGER_DEPLOYMENT_VERSION} \
|
||||
TRIGGER_CONTENT_HASH=${TRIGGER_CONTENT_HASH} \
|
||||
TRIGGER_PROJECT_REF=${TRIGGER_PROJECT_REF} \
|
||||
NODE_EXTRA_CA_CERTS=${NODE_EXTRA_CA_CERTS} \
|
||||
NODE_ENV=production
|
||||
|
||||
USER node
|
||||
|
||||
@@ -304,6 +304,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
pushImage: options.push,
|
||||
selfHostedRegistry: !!options.registry,
|
||||
noCache: options.noCache,
|
||||
extraCACerts: resolvedConfig.config.extraCACerts,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,6 +331,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
loadImage: options.loadImage,
|
||||
buildPlatform: options.buildPlatform,
|
||||
noCache: options.noCache,
|
||||
extraCACerts: resolvedConfig.config.extraCACerts,
|
||||
},
|
||||
deploymentSpinner
|
||||
);
|
||||
@@ -637,6 +639,13 @@ function checkLogsForErrors(logs: string) {
|
||||
docs.config.prisma
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
regex: /@prisma\/client did not initialize yet/,
|
||||
message: `Prisma client not initialized yet.\nDid you forget to add the postinstall script? ${cliLink(
|
||||
"Config docs",
|
||||
docs.config.prisma
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
regex: /sh: 1: (?<packageOrBinary>.*): not found/,
|
||||
message: `$packageOrBinary not found\n\nIf it's a package: Include it in ${cliLink(
|
||||
@@ -779,6 +788,7 @@ type BuildAndPushImageOptions = {
|
||||
loadImage: boolean;
|
||||
buildPlatform: string;
|
||||
noCache: boolean;
|
||||
extraCACerts?: string;
|
||||
};
|
||||
|
||||
type BuildAndPushImageResults =
|
||||
@@ -837,6 +847,9 @@ async function buildAndPushImage(
|
||||
`TRIGGER_CONTENT_HASH=${options.contentHash}`,
|
||||
"--build-arg",
|
||||
`TRIGGER_PROJECT_REF=${options.projectRef}`,
|
||||
...(options.extraCACerts
|
||||
? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`]
|
||||
: []),
|
||||
"-t",
|
||||
`${options.registryHost}/${options.imageTag}`,
|
||||
".",
|
||||
@@ -961,6 +974,9 @@ async function buildAndPushSelfHostedImage(
|
||||
`TRIGGER_CONTENT_HASH=${options.contentHash}`,
|
||||
"--build-arg",
|
||||
`TRIGGER_PROJECT_REF=${options.projectRef}`,
|
||||
...(options.extraCACerts
|
||||
? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`]
|
||||
: []),
|
||||
"-t",
|
||||
imageRef,
|
||||
".", // The build context
|
||||
|
||||
@@ -293,9 +293,18 @@ function useDev({
|
||||
`${dashboardUrl}/projects/v3/${config.project}`
|
||||
);
|
||||
|
||||
websocket.addEventListener("open", async (event) => {});
|
||||
websocket.addEventListener("close", (event) => {});
|
||||
websocket.addEventListener("error", (event) => {});
|
||||
websocket.addEventListener("open", async (event) => {
|
||||
logger.debug("WebSocket opened", { event });
|
||||
});
|
||||
|
||||
websocket.addEventListener("close", (event) => {
|
||||
logger.debug("WebSocket closed", { event });
|
||||
});
|
||||
|
||||
websocket.addEventListener("error", (event) => {
|
||||
logger.log(`${chalkError("WebSocketError:")} ${event.error.message}`);
|
||||
logger.debug("WebSocket error", { event, rawError: event.error });
|
||||
});
|
||||
|
||||
// This is the deprecated task heart beat that uses the friendly attempt ID
|
||||
// It will only be used if the worker does not support lazy attempts
|
||||
@@ -362,31 +371,42 @@ function useDev({
|
||||
});
|
||||
|
||||
websocket.addEventListener("message", async (event) => {
|
||||
const data = JSON.parse(
|
||||
typeof event.data === "string" ? event.data : new TextDecoder("utf-8").decode(event.data)
|
||||
);
|
||||
try {
|
||||
const data = JSON.parse(
|
||||
typeof event.data === "string" ? event.data : new TextDecoder("utf-8").decode(event.data)
|
||||
);
|
||||
|
||||
const messageHandler = new ZodMessageHandler({
|
||||
schema: serverWebsocketMessages,
|
||||
messages: {
|
||||
SERVER_READY: async (payload) => {
|
||||
for (const worker of backgroundWorkerCoordinator.currentWorkers) {
|
||||
await sender.send("READY_FOR_TASKS", {
|
||||
backgroundWorkerId: worker.id,
|
||||
inProgressRuns: worker.worker.inProgressRuns,
|
||||
});
|
||||
}
|
||||
const messageHandler = new ZodMessageHandler({
|
||||
schema: serverWebsocketMessages,
|
||||
messages: {
|
||||
SERVER_READY: async (payload) => {
|
||||
for (const worker of backgroundWorkerCoordinator.currentWorkers) {
|
||||
await sender.send("READY_FOR_TASKS", {
|
||||
backgroundWorkerId: worker.id,
|
||||
inProgressRuns: worker.worker.inProgressRuns,
|
||||
});
|
||||
}
|
||||
},
|
||||
BACKGROUND_WORKER_MESSAGE: async (payload) => {
|
||||
await backgroundWorkerCoordinator.handleMessage(
|
||||
payload.backgroundWorkerId,
|
||||
payload.data
|
||||
);
|
||||
},
|
||||
},
|
||||
BACKGROUND_WORKER_MESSAGE: async (payload) => {
|
||||
await backgroundWorkerCoordinator.handleMessage(
|
||||
payload.backgroundWorkerId,
|
||||
payload.data
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await messageHandler.handleMessage(data);
|
||||
await messageHandler.handleMessage(data);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error("Error while handling websocket message", { error: error.message });
|
||||
} else {
|
||||
logger.error(
|
||||
"Unkown error while handling websocket message, use `-l debug` for additional output"
|
||||
);
|
||||
logger.debug("Error while handling websocket message", { error });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let ctx: BuildContext | undefined;
|
||||
|
||||
@@ -204,7 +204,10 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
`Please visit the following URL to login:\n${chalkLink(authorizationCodeResult.url)}`
|
||||
);
|
||||
|
||||
await open(authorizationCodeResult.url);
|
||||
try {
|
||||
//this can throw an error on Ubuntu
|
||||
await open(authorizationCodeResult.url);
|
||||
} catch (e) {}
|
||||
|
||||
//poll for personal access token (we need to poll for it)
|
||||
const getPersonalAccessTokenSpinner = spinner();
|
||||
|
||||
@@ -39,7 +39,7 @@ function initializeTracing(): NodeTracerProvider | undefined {
|
||||
resource,
|
||||
spanLimits: {
|
||||
attributeCountLimit: 1000,
|
||||
attributeValueLengthLimit: 1000,
|
||||
attributeValueLengthLimit: 2048,
|
||||
eventCountLimit: 100,
|
||||
attributePerEventCountLimit: 100,
|
||||
linkCountLimit: 10,
|
||||
|
||||
@@ -247,6 +247,15 @@ export async function resolveConfig(path: string, config: Config): Promise<Resol
|
||||
config.tsconfigPath = await findFilePath(path, "tsconfig.json");
|
||||
}
|
||||
|
||||
if (!config.additionalFiles) {
|
||||
config.additionalFiles = [];
|
||||
}
|
||||
|
||||
if (config.extraCACerts) {
|
||||
config.additionalFiles.push(config.extraCACerts);
|
||||
config.extraCACerts = config.extraCACerts.replace(/^(\.[.]?\/)+/, "");
|
||||
}
|
||||
|
||||
return config as ResolvedConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -379,20 +379,32 @@ export class BackgroundWorker {
|
||||
child.on("message", async (msg: any) => {
|
||||
const message = this._handler.parseMessage(msg);
|
||||
|
||||
if (message.type === "TASKS_READY" && !resolved) {
|
||||
if (!message.success) {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
resolve(message.payload.tasks);
|
||||
reject(new Error(`Failed to parse message: ${message.error}`));
|
||||
child.kill();
|
||||
} else if (message.type === "UNCAUGHT_EXCEPTION") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.data.type === "TASKS_READY" && !resolved) {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new UncaughtExceptionError(message.payload.error, message.payload.origin));
|
||||
resolve(message.data.payload.tasks);
|
||||
child.kill();
|
||||
} else if (message.type === "TASKS_FAILED_TO_PARSE") {
|
||||
} else if (message.data.type === "UNCAUGHT_EXCEPTION") {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new TaskMetadataParseError(message.payload.zodIssues, message.payload.tasks));
|
||||
reject(
|
||||
new UncaughtExceptionError(message.data.payload.error, message.data.payload.origin)
|
||||
);
|
||||
child.kill();
|
||||
} else if (message.data.type === "TASKS_FAILED_TO_PARSE") {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(
|
||||
new TaskMetadataParseError(message.data.payload.zodIssues, message.data.payload.tasks)
|
||||
);
|
||||
child.kill();
|
||||
}
|
||||
});
|
||||
@@ -942,9 +954,14 @@ class TaskRunProcess {
|
||||
async #handleMessage(msg: any) {
|
||||
const message = this._handler.parseMessage(msg);
|
||||
|
||||
switch (message.type) {
|
||||
if (!message.success) {
|
||||
logger.error(`Dropping message: ${message.error}`, { message });
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.data.type) {
|
||||
case "TASK_RUN_COMPLETED": {
|
||||
const { result, execution } = message.payload;
|
||||
const { result, execution } = message.data.payload;
|
||||
|
||||
logger.debug(`[${this.runId}] task run completed`, {
|
||||
result,
|
||||
@@ -981,7 +998,7 @@ class TaskRunProcess {
|
||||
if (this.messageId) {
|
||||
this.onTaskRunHeartbeat.post(this.messageId);
|
||||
} else {
|
||||
this.onTaskHeartbeat.post(message.payload.id);
|
||||
this.onTaskHeartbeat.post(message.data.payload.id);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -1485,6 +1485,7 @@ function gatherProcessEnv() {
|
||||
TERM: process.env.TERM,
|
||||
NODE_PATH: process.env.NODE_PATH,
|
||||
HOME: process.env.HOME,
|
||||
NODE_EXTRA_CA_CERTS: process.env.NODE_EXTRA_CA_CERTS,
|
||||
};
|
||||
|
||||
// Filter out undefined values
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# create-trigger
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dbda820a7]
|
||||
- Updated dependencies [e417aca87]
|
||||
- Updated dependencies [d934feb02]
|
||||
- @trigger.dev/core@3.0.0-beta.49
|
||||
- @trigger.dev/yalt@3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @trigger.dev/core@3.0.0-beta.48
|
||||
- @trigger.dev/yalt@3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/cli",
|
||||
"version": "3.0.0-beta.47",
|
||||
"version": "3.0.0-beta.49",
|
||||
"description": "The Trigger.dev CLI",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @trigger.dev/core-apps
|
||||
|
||||
## 3.0.0-beta.49
|
||||
|
||||
## 3.0.0-beta.48
|
||||
|
||||
## 3.0.0-beta.47
|
||||
|
||||
## 3.0.0-beta.46
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user