Compare commits

...

42 Commits

Author SHA1 Message Date
nicktrn 2d6fd62fb8 Release 3.0.0-beta.49
🚀 Publish Trigger.dev Docker / units (push) Failing after 5s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 21s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped
2024-07-17 14:20:21 +01:00
github-actions[bot] e8993ee882 chore: Update version for release (beta) (#1211)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-17 14:18:40 +01:00
nicktrn e0cdebbe09 Disable excessive websocket heartbeat logs 2024-07-17 13:28:40 +01:00
Matt Aitken d6c6dc993d try/catch opening the v3 CLI login URL. Fix for Ubuntu 2024-07-17 13:15:42 +01:00
nicktrn 27de884f93 Remove unused ping message schema 2024-07-17 13:01:43 +01:00
nicktrn c2707800aa Improve prisma errors for missing generate step 2024-07-17 09:53:38 +01:00
nicktrn dbda820a71 v3: fix websocket timeouts (#1220)
* prevent uncaught websocket exceptions

* dev cli sends regular pings to keep connection alive

* add changeset

* fix for prod message forwarding

* add server-side websocket ping

* Revert "dev cli sends regular pings to keep connection alive"

This reverts commit 9a6a7a21433b9f0665f72be015d0ba5a8d83eba4.

* add dev cli websocket debug logs, always print errors

* actually start the ping service

* update changeset

* catch remaining dev cli message handler errors
2024-07-17 09:05:15 +01:00
Gautam Singh e417aca879 v3: add extraCACerts config (#1214)
* Added extraCACerts config to help with self signed/private ca cert chain

* changeset

* Update changeset

* resolved pr comments

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2024-07-17 08:56:52 +01:00
Ray Amjad 5a3b4a450b Update github-actions.mdx (#1222) 2024-07-17 08:55:14 +01:00
Matt Aitken f91a118958 Fix for dev env vars getting deleted when another user edits theirs 2024-07-16 10:57:35 +01:00
nicktrn 9971de6a1e Increase span attribute value length limit to 2048 2024-07-16 08:15:05 +01:00
Matt Aitken 0847bee5b8 Fix for stripping out TRIGGER_ env vars 2024-07-15 17:11:15 +01:00
Matt Aitken 6152b2f3dc Don’t error when setting TRIGGER_SECRET_KEY or TRIGGER_API_URL env vars, just filter them out 2024-07-15 16:39:13 +01:00
Matt Aitken ec8381e2d0 Fix for staging environment variable API endpoints
Ugly but effective fix for the fact we can pass the slug in as “staging” when we use “stg” internally
2024-07-15 16:33:00 +01:00
Matt Aitken 76a15fd278 The run filter for tasks query was very slow if you had a lot of synced tasks 2024-07-15 15:59:11 +01:00
nicktrn 824a6205f7 Fix deployments stuck in deploying state 2024-07-15 12:00:16 +01:00
Matt Aitken a0984d5292 Allow alerts from external and unlinked Slack channels 2024-07-12 11:14:57 +01:00
Matt Aitken c61d8ed890 Removed badge from the Tasks in the side menu
It was showing the v2 job count. Mostly this didn't actually show anything but one user reported they saw a number here and it's definitely a bug.
2024-07-12 10:51:54 +01:00
Eric Allam 76a0f61dee v3: improve schedule reliability by ensuring next tick is always scheduled 2024-07-11 15:35:34 +01:00
Jacob Paris d934feb02a feature: add no-trigger-core-import (#1175)
* feature: add no-trigger-core-import

* fix: handle aliases and limit to exported modules

* fix: use longest export map name

* fix: support export type and interface

* fix: simplify message when export map is needed

* feature: fine grained modules for core

* fix: add prerequisite lint plugins

* fix: why do I have to fix these components again???

* fix: tests

* Add eslint config and vscode settings

* Create yellow-roses-arrive.md

* Remove the vscode linting

This is temporary so no one fixes these until we merge the remix branch

---------

Co-authored-by: Eric Allam <eric@trigger.dev>
Co-authored-by: Eric Allam <eallam@icloud.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2024-07-11 15:14:55 +01:00
Eric Allam 7fdedf3137 Fix excessive logs from the MarQS rebalancer 2024-07-11 14:20:59 +01:00
Eric Allam f6e23e94d1 Ensure failed migrations fail deployment by not swallowing exit codes in entrypoint.sh 2024-07-11 13:22:40 +01:00
Matt Aitken 3d6cfe5ead Admin API endpoint that adds v3 staging environments, and updates their concurrency 2024-07-11 10:22:21 +01:00
nicktrn 946efd1fb4 v3: deployment index retry (#1205)
* add built at date to worker deployments

* stay on correct deployment page when clicking row

* expose built at via presenters

* more specific deployment statuses

* add deployment indexing retry

* improve deploy timeout logs and add dequeue on completion

* unify deployment indexing retryable status

* only allow index retries of the latest deployment

* improve jsxfu
2024-07-10 17:54:36 +01:00
Matt Aitken 3d645e2b2c Fix for usage of zero displaying weirdly 2024-07-10 17:31:40 +01:00
Matt Aitken 45cb75b8a3 Only show dev runs in the tasks usage table 2024-07-10 17:22:35 +01:00
Matt Aitken 91d45e5ba6 Added a link to the pricing page for machines 2024-07-10 17:04:29 +01:00
James Ritchie fa5b3fe11e Only show the github verified badge on the Select Plan onboarding route 2024-07-10 16:50:52 +01:00
James Ritchie 90506322b5 Hide the tooltip content on the usage graph 2024-07-10 16:46:10 +01:00
Matt Aitken e59dd465d8 Documented the task usage functions 2024-07-10 15:19:45 +01:00
Matt Aitken 98d5f005e4 Added delay to the docs and improved the ttl 2024-07-10 10:28:49 +01:00
Matt Aitken a6b5d2edc5 Added ttl to trigger docs 2024-07-10 09:41:09 +01:00
Matt Aitken f4a1d5f36d Removed broken links from writing tasks page 2024-07-10 06:35:41 +01:00
Matt Aitken 6fb6cb6aaf Docs: added alerts link from feature matrix 2024-07-10 06:35:41 +01:00
Eric Allam 0b6417f1d8 Improve the section on adding trigger.dev to dev deps 2024-07-09 21:37:37 +01:00
Matt Aitken a685f5a4ec Removed the enable/disable v3 from the admin area 2024-07-09 19:00:19 +01:00
Matt Aitken 62532d5ce1 Fix for package name mistake 2024-07-09 15:53:03 +01:00
Matt Aitken 786d5508f0 Cover upgrading dev dependency 2024-07-09 15:52:42 +01:00
Matt Aitken 187f02780b Added upgrading packages guide 2024-07-09 15:49:46 +01:00
Eric Allam b8b3ea07eb Release 3.0.0-beta.48
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped
2024-07-09 14:06:41 +01:00
github-actions[bot] e341df806c chore: Update version for release (beta) (#1209)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-09 14:05:49 +01:00
Eric Allam ecf1110ab7 v3: Export AbortTaskRunError from @trigger.dev/sdk/v3 2024-07-09 14:00:44 +01:00
147 changed files with 3333 additions and 745 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Increase span attribute value length limit to 2048
+7
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Improve prisma errors for missing postinstall
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
try/catch opening the login URL
+6
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
v3: Export AbortTaskRunError from @trigger.dev/sdk/v3
+7
View File
@@ -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"
]
}
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---
Add more package exports that can be used from the web app
+33 -10
View File
@@ -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>
);
}
+1
View File
@@ -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
@@ -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 ? (
@@ -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>
);
}
@@ -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 });
}
+5 -47
View File
@@ -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>
);
})
@@ -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) {
+2 -2
View File
@@ -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": {
+4 -2
View File
@@ -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);
+11
View File
@@ -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` };
}
+6 -3
View File
@@ -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();
+11 -4
View File
@@ -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 });
}
+4
View File
@@ -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",
+2 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+1 -2
View File
@@ -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.
+2 -2
View File
@@ -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.
+2
View File
@@ -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).
+85
View File
@@ -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.
+80
View File
@@ -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.
-5
View File
@@ -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. |
+15
View File
@@ -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
+3 -3
View File
@@ -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"
},
+15
View File
@@ -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
+3 -3
View File
@@ -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": {
+15
View File
@@ -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
+3 -3
View File
@@ -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": {
+15
View File
@@ -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
+3 -3
View File
@@ -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"
+15
View File
@@ -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
+3 -3
View File
@@ -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": {
+15
View File
@@ -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
+3 -3
View File
@@ -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"
},
+15
View File
@@ -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
+3 -3
View File
@@ -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": {
+15
View File
@@ -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
+3 -3
View File
@@ -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"
+15
View File
@@ -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
+3 -3
View File
@@ -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": {
+13
View File
@@ -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
+2 -2
View File
@@ -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": {
+15
View File
@@ -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
+3 -3
View File
@@ -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"
},
+15
View File
@@ -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
+3 -3
View File
@@ -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"
},
+15
View File
@@ -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
+3 -3
View File
@@ -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"
},
+1
View File
@@ -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",
+13
View File
@@ -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
+2 -2
View File
@@ -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",
+21
View File
@@ -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
+2 -2
View File
@@ -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",
+2
View File
@@ -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
+16
View File
@@ -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
+45 -25
View File
@@ -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;
+4 -1
View File
@@ -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();
+1 -1
View File
@@ -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
+17
View File
@@ -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 -1
View File
@@ -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",
+4
View File
@@ -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