Compare commits

...

32 Commits

Author SHA1 Message Date
nicktrn 4a0368dec3 lockfile 2024-04-12 18:18:49 +01:00
github-actions[bot] f10f120e55 chore: Update version for release (beta) (#1028)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-12 18:18:03 +01:00
nicktrn d3a18fbdf6 v3: cli and misc fixes for windows (#1027)
* fix: otlp-importer script

* update pluginPath

* Refactor script to ensure compatibility with both Windows and Unix platforms

* Revert command changes

* Fix: Init command was failing on Windows because of bad template paths

* Removed resolveInternalFilePath

* Path fixes for the dev command

* Fix for the import paths being wrong on Windows because the contain backslashes

* The metaOutputKey should have forward slashes in it

* Print the banner immediately, otherwise with bad internet you get a blank console for a long time

* Try normalizing the trigger.config import path

* Allow tsx and jsx files

* Improved task file names and paths

* Removed closing bracket for the upgrade message

* Log out the metafile outputs for debugging

* Log out the entryPointContents before esbuild

* Log the metafile out

* ballmerize the cli

* replace npm-watch with plain old nodemon

* fix text inputs on windows

* changeset

---------

Co-authored-by: Kritik Jiyaviya <kritikjiyaviya07@gmail.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2024-04-12 18:12:01 +01:00
nicktrn b82a07ad1c add missing ca certs to prod worker 2024-04-12 18:07:54 +01:00
Eric Allam fa81bf356a Fix pnpm lock file 2024-04-12 12:34:32 +01:00
github-actions[bot] fec4dc3bef chore: Update version for release (beta) (#1025)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-12 12:34:02 +01:00
Eric Allam a80089c88f Fix the package version of trigger.dev with the rest of the @trigger.dev/* packages 2024-04-12 11:19:04 +01:00
nicktrn 63a643b7c9 v3: fix digest extraction (#1024)
* fix image digest extraction

* fix project ref cli arg override

* Create late-steaks-behave.md

---------

Co-authored-by: Eric Allam <eric@trigger.dev>
2024-04-12 11:10:52 +01:00
nicktrn efd970a901 v3: correctly handle triggering tasks prior to deploy (#1019)
* add waiting for deploy status

* sort tasks filter

* marqs: optional timestamp on enqueue

* add new status to consts

* detect and execute tasks waiting for deploy

* display runs list even without any deploys

* ensure we only execute after the run creation tx has completed

* ensure correct execution order after waiting for deploy
2024-04-12 11:03:22 +01:00
Eric Allam 89a5e9f7da Move gc to it’s own admin endpoint 2024-04-11 16:24:40 +01:00
James Ritchie 404931e224 Made the message when you’re in a developer preview project punchier 2024-04-11 14:34:45 +01:00
Eric Allam 57bf98307a Run gc before taking heapsnapshot 2024-04-11 13:53:14 +01:00
Eric Allam 7551adfeb6 Add ability to disable tracing to test if it’s the cause of the memory leak 2024-04-11 13:14:02 +01:00
Matt Aitken 69117e8ba1 Re-arrange the mint.json file so the v3 docs become the default 2024-04-11 11:22:18 +01:00
Eric Allam b580d53b59 Better identifiy heap snapshots 2024-04-11 11:13:36 +01:00
Eric Allam d81e21d2ec Remove admin config endpoint 2024-04-11 10:44:26 +01:00
Matt Aitken 826a64fe6f If your dev run hasn’t started, display an info panel saying you need to run the CLI 2024-04-11 10:36:44 +01:00
Eric Allam d1849b0ea9 Adding admin config endpoint (temp) 2024-04-11 10:30:45 +01:00
Eric Allam 3aa634d2b8 Add a metric for number of event repo subscribers and project subscribers 2024-04-11 10:30:37 +01:00
Matt Aitken 7de3fb01c7 Added more docs for replaying a run 2024-04-10 18:35:43 +01:00
github-actions[bot] 4ae9a3feac chore: Update version for release (beta) (#1023)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-10 18:04:20 +01:00
Matt Aitken 5b18298fef Multiple machine CLI support (#1022)
* If a cli PAT exists, associate the authorization code with it

* Improved the error messages
2024-04-10 17:59:21 +01:00
nicktrn 7a9bd18ba2 v3: stop swallowing deployment errors and display them better (#1020)
* exit worker with code 111 for handled errors

* task monitor will ignore previously handled errors

* fix logging of index errors

* deployment error component

* changeset
2024-04-10 17:58:51 +01:00
Matt Aitken 03af44e545 Improved the title of one of the debugger scripts 2024-04-10 11:23:17 +01:00
Matt Aitken 5776257663 Changed the Discord link to use the trigger.dev one. Mainly to bump the GitHub action 2024-04-10 11:08:56 +01:00
Matt Aitken d3997c9fc6 Merge branch 'main' into changeset-release/main 2024-04-10 10:20:56 +01:00
github-actions[bot] 0676ea9668 chore: Update version for release (beta) (#1013)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-10 10:14:00 +01:00
github-actions[bot] 7a4721e122 chore: Update version for release (beta) 2024-04-09 16:44:12 +00:00
Eric Allam a5aed0d139 Fix memory leak by calling punsubscribe instead of unsubscribe
Also adding a /metrics endpoint with a few custom metrics and the built-in prom-client ones
2024-04-09 17:42:28 +01:00
Eric Allam f1480595d5 Stop swallowing webapp logs when the span isn’t being recorded 2024-04-09 17:11:09 +01:00
Eric Allam 328947dbfd Use the dashboard url instead of the API url for the View logs link 2024-04-09 15:15:16 +01:00
Eric Allam 279717b092 v3: pnpm deploy & various fixes for dependency resolution (#1012)
* Don’t swallow some error messages when deploying

* Try and resolve dependency versions using pnpm/npm/yarn

* Adding some debug logs around dependency resolution

* Use npm list instead of npm show to resolve deps

* Improve the dependency resolution
2024-04-09 15:03:31 +01:00
142 changed files with 1962 additions and 1005 deletions
+2 -1
View File
@@ -4,7 +4,8 @@
"commit": false,
"fixed": [
[
"@trigger.dev/*"
"@trigger.dev/*",
"trigger.dev"
]
],
"linked": [],
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Dont swallow some error messages when deploying
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
v3: fix digest extraction
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Use the dashboard url instead of the API url for the View logs link
+6
View File
@@ -48,11 +48,13 @@
"chilled-hornets-move",
"clean-pianos-listen",
"cool-glasses-bake",
"cuddly-feet-approve",
"eight-pumas-float",
"few-students-share",
"green-bags-wink",
"khaki-apricots-design",
"late-icons-lie",
"late-steaks-behave",
"lemon-jobs-repair",
"light-bulldogs-press",
"light-dragons-complain",
@@ -62,9 +64,11 @@
"new-rivers-tell",
"odd-poets-own",
"polite-ducks-switch",
"poor-flowers-cross",
"rare-roses-float",
"real-planets-stare",
"sharp-zebras-serve",
"silly-suits-switch",
"smart-olives-eat",
"spicy-lamps-smoke",
"strange-ghosts-matter",
@@ -72,6 +76,8 @@
"sweet-lizards-press",
"tall-bees-wave",
"tame-guests-know",
"tender-oranges-rhyme",
"tidy-balloons-suffer",
"tiny-doors-type"
]
}
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/otlp-importer": patch
"trigger.dev": patch
---
Fix package builds and CLI commands on Windows
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Stop swallowing deployment errors and display them better
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Init command was failing on Windows because of bad template paths
+1 -1
View File
@@ -23,7 +23,7 @@
{
"type": "node-terminal",
"request": "launch",
"name": "Debug BYO Auth",
"name": "Debug v2 job catalog",
"command": "pnpm run byo-auth",
"envFile": "${workspaceFolder}/references/job-catalog/.env",
"cwd": "${workspaceFolder}/references/job-catalog",
+1 -1
View File
@@ -7,7 +7,7 @@
### The open source background jobs framework
[Discord](https://discord.gg/JtBAxBr2m3) | [Website](https://trigger.dev) | [Issues](https://github.com/triggerdotdev/trigger.dev/issues) | [Docs](https://trigger.dev/docs)
[Discord](https://trigger.dev/discord) | [Website](https://trigger.dev) | [Issues](https://github.com/triggerdotdev/trigger.dev/issues) | [Docs](https://trigger.dev/docs)
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/triggerdotdev.svg?style=social&label=Follow%20%40trigger.dev)](https://twitter.com/triggerdotdev)
[![GitHub Repo stars](https://img.shields.io/github/stars/triggerdotdev/trigger.dev?style=social)](https://github.com/triggerdotdev/trigger.dev)
+7 -1
View File
@@ -136,6 +136,13 @@ export class TaskMonitor {
const podStatus = this.#getPodStatusSummary(pod.status);
const containerState = this.#getContainerStateSummary(containerStatus.state);
const exitCode = containerState.exitCode ?? -1;
// We use this special exit code to signal any errors were already handled elsewhere
if (exitCode === 111) {
return;
}
const rawLogs = await this.#getLogTail(podName);
this.#logger.log(`${podName} failed with:`, {
@@ -144,7 +151,6 @@ export class TaskMonitor {
rawLogs,
});
const exitCode = containerState.exitCode ?? -1;
const rawReason = podStatus.reason ?? containerState.reason ?? "";
const message = podStatus.message ?? containerState.message ?? "";
@@ -189,9 +189,11 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
</div>
</div>
<div className="m-2">
<Callout variant={"info"}>
{`This is a ${project.version === "V2" ? "v2" : "v3"} project`}
</Callout>
{project.version === "V2" ? (
<Callout variant={"info"}>This is a v2 project</Callout>
) : (
<Callout variant={"idea"}>This is a v3 project in Developer Preview</Callout>
)}
</div>
<div className="flex flex-col gap-1 border-t border-grid-bright p-1">
{project.version === "V2" && (
@@ -0,0 +1,39 @@
import { CodeBlock } from "~/components/code/CodeBlock";
import { Callout } from "~/components/primitives/Callout";
import { Header2 } from "~/components/primitives/Headers";
import type { ErrorData } from "~/presenters/v3/DeploymentPresenter.server";
type DeploymentErrorProps = {
errorData: ErrorData;
};
export function DeploymentError({ errorData }: DeploymentErrorProps) {
return (
<div className="flex flex-col gap-2 rounded-sm border border-rose-500/50 p-3">
<DeploymentErrorHeader title={errorData.name ?? "Error"} titleClassName="text-rose-500" />
{errorData.message && <Callout variant="error">{errorData.message}</Callout>}
{errorData.stack && (
<CodeBlock
showCopyButton={false}
showLineNumbers={false}
code={errorData.stack}
maxLines={20}
/>
)}
</div>
);
}
function DeploymentErrorHeader({
title,
titleClassName,
}: {
title: string;
titleClassName?: string;
}) {
return (
<div className="flex items-center justify-between">
<Header2 className={titleClassName}>{title}</Header2>
</div>
);
}
@@ -27,6 +27,7 @@ import { TaskRunStatusCombo, descriptionForTaskRunStatus } from "./TaskRunStatus
export const allTaskRunStatuses = [
"PENDING",
"WAITING_FOR_DEPLOY",
"EXECUTING",
"RETRYING_AFTER_FAILURE",
"WAITING_TO_RESUME",
@@ -228,16 +229,18 @@ export function RunsFilters({ possibleEnvironments, possibleTasks }: RunFiltersP
All tasks
</Paragraph>
</SelectItem>
{possibleTasks.map((task) => (
<SelectItem key={task} value={task}>
<Paragraph
variant="extra-small"
className="pl-0.5 transition group-hover:text-text-bright"
>
{task}
</Paragraph>
</SelectItem>
))}
{possibleTasks
.sort((a, b) => a.localeCompare(b)) // 🔤
.map((task) => (
<SelectItem key={task} value={task}>
<Paragraph
variant="extra-small"
className="pl-0.5 transition group-hover:text-text-bright"
>
{task}
</Paragraph>
</SelectItem>
))}
</SelectContent>
</Select>
</SelectGroup>
@@ -16,6 +16,7 @@ import { cn } from "~/utils/cn";
const taskRunStatusDescriptions: Record<TaskRunStatus, string> = {
PENDING: "Task is waiting to be executed",
WAITING_FOR_DEPLOY: "Task needs to be deployed first to start executing",
EXECUTING: "Task is currently being executed",
RETRYING_AFTER_FAILURE: "Task is being reattempted after a failure",
WAITING_TO_RESUME: "Task has been frozen and is waiting to be resumed",
@@ -63,6 +64,8 @@ export function TaskRunStatusIcon({
switch (status) {
case "PENDING":
return <RectangleStackIcon className={cn(runStatusClassNameColor(status), className)} />;
case "WAITING_FOR_DEPLOY":
return <RectangleStackIcon className={cn(runStatusClassNameColor(status), className)} />;
case "EXECUTING":
return <Spinner className={cn(runStatusClassNameColor(status), className)} />;
case "WAITING_TO_RESUME":
@@ -95,6 +98,8 @@ export function runStatusClassNameColor(status: TaskRunStatus): string {
switch (status) {
case "PENDING":
return "text-charcoal-500";
case "WAITING_FOR_DEPLOY":
return "text-amber-500";
case "EXECUTING":
case "RETRYING_AFTER_FAILURE":
return "text-pending";
@@ -125,6 +130,8 @@ export function runStatusTitle(status: TaskRunStatus): string {
switch (status) {
case "PENDING":
return "Queued";
case "WAITING_FOR_DEPLOY":
return "Waiting for deploy";
case "EXECUTING":
return "Executing";
case "WAITING_TO_RESUME":
+1
View File
@@ -139,6 +139,7 @@ const EnvironmentSchema = z.object({
// this means 1/20 traces or 5% of traces will be sampled (sampled = recorded)
INTERNAL_OTEL_TRACE_SAMPLING_RATE: z.string().default("20"),
INTERNAL_OTEL_TRACE_INSTRUMENT_PRISMA_ENABLED: z.string().default("0"),
INTERNAL_OTEL_TRACE_DISABLED: z.string().default("0"),
});
export type Environment = z.infer<typeof EnvironmentSchema>;
+19
View File
@@ -0,0 +1,19 @@
import { OpenMetricsContentType, Registry, collectDefaultMetrics, register } from "prom-client";
import { singleton } from "./utils/singleton";
import { env } from "./env.server";
export const metricsRegister = singleton("metricsRegister", initializeMetricsRegister);
function initializeMetricsRegister() {
const registry = new Registry<OpenMetricsContentType>();
register.setDefaultLabels({
serviceName: env.SERVICE_NAME,
});
registry.setContentType("application/openmetrics-text; version=1.0.0; charset=utf-8");
collectDefaultMetrics({ register: registry });
return registry;
}
@@ -12,6 +12,12 @@ import { User } from "~/models/user.server";
import { safeJsonParse } from "~/utils/json";
import { getUsername } from "~/utils/username";
export type ErrorData = {
name: string;
message: string;
stack?: string;
};
export class DeploymentPresenter {
#prismaClient: PrismaClient;
@@ -133,7 +139,7 @@ export class DeploymentPresenter {
};
}
#prepareErrorData(errorData: WorkerDeployment["errorData"]) {
#prepareErrorData(errorData: WorkerDeployment["errorData"]): ErrorData | undefined {
if (!errorData) {
return;
}
@@ -19,6 +19,7 @@ import {
TableHeaderCell,
TableRow,
} from "~/components/primitives/Table";
import { DeploymentError } from "~/components/runs/v3/DeploymentError";
import { DeploymentStatus } from "~/components/runs/v3/DeploymentStatus";
import { TaskFunctionName } from "~/components/runs/v3/TaskPath";
import { useOrganization } from "~/hooks/useOrganizations";
@@ -158,25 +159,7 @@ export default function Page() {
</Table>
</div>
) : deployment.errorData ? (
<div className="flex flex-col">
{deployment.errorData.stack ? (
<CodeBlock
language="markdown"
rowTitle={deployment.errorData.message}
code={deployment.errorData.stack}
maxLines={20}
/>
) : (
<div className="flex flex-col">
<Paragraph
variant="base/bright"
className="w-full border-b border-grid-dimmed py-2.5"
>
{deployment.errorData.message}
</Paragraph>
</div>
)}
</div>
<DeploymentError errorData={deployment.errorData} />
) : null}
</div>
</div>
@@ -12,16 +12,19 @@ import {
millisecondsToNanoseconds,
nanosecondsToMilliseconds,
} from "@trigger.dev/core/v3";
import { RuntimeEnvironmentType } from "@trigger.dev/database";
import { motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { ShowParentIcon, ShowParentIconSelected } from "~/assets/icons/ShowParentIcon";
import tileBgPath from "~/assets/images/error-banner-tile@2x.png";
import { BlankstateInstructions } from "~/components/BlankstateInstructions";
import { InlineCode } from "~/components/code/InlineCode";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { MainCenteredContainer, PageBody } from "~/components/layout/AppLayout";
import { Badge } from "~/components/primitives/Badge";
import { LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { Input } from "~/components/primitives/Input";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
@@ -181,6 +184,7 @@ export default function Page() {
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
/>
) : (
<ResizablePanelGroup
@@ -209,6 +213,7 @@ export default function Page() {
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
/>
</ResizablePanel>
<ResizableHandle withHandle />
@@ -231,6 +236,7 @@ type TasksTreeViewProps = {
totalDuration: number;
rootSpanStatus: "executing" | "completed" | "failed";
rootStartedAt: Date | undefined;
environmentType: RuntimeEnvironmentType;
};
function TasksTreeView({
@@ -241,6 +247,7 @@ function TasksTreeView({
totalDuration,
rootSpanStatus,
rootStartedAt,
environmentType,
}: TasksTreeViewProps) {
const [filterText, setFilterText] = useState("");
const [errorsOnly, setErrorsOnly] = useState(false);
@@ -345,63 +352,68 @@ function TasksTreeView({
getNodeProps={getNodeProps}
getTreeProps={getTreeProps}
renderNode={({ node, state }) => (
<div
className={cn(
"delay-[25ms] flex h-8 cursor-pointer items-center overflow-hidden rounded-l-sm pr-2 transition-colors",
state.selected
? "bg-grid-dimmed hover:bg-grid-bright"
: "bg-transparent hover:bg-grid-dimmed"
)}
onClick={() => {
toggleNodeSelection(node.id);
}}
>
<div className="flex h-8 items-center">
{Array.from({ length: node.level }).map((_, index) => (
<TaskLine
key={index}
isError={node.data.isError}
isSelected={state.selected}
/>
))}
<div
className={cn(
"flex h-8 w-4 items-center",
node.hasChildren && "hover:bg-charcoal-600"
)}
onClick={(e) => {
e.stopPropagation();
toggleExpandNode(node.id);
scrollToNode(node.id);
}}
>
{node.hasChildren ? (
state.expanded ? (
<ChevronDownIcon className="h-4 w-4 text-charcoal-400" />
<>
<div
className={cn(
"delay-[25ms] flex h-8 cursor-pointer items-center overflow-hidden rounded-l-sm pr-2 transition-colors",
state.selected
? "bg-grid-dimmed hover:bg-grid-bright"
: "bg-transparent hover:bg-grid-dimmed"
)}
onClick={() => {
toggleNodeSelection(node.id);
}}
>
<div className="flex h-8 items-center">
{Array.from({ length: node.level }).map((_, index) => (
<TaskLine
key={index}
isError={node.data.isError}
isSelected={state.selected}
/>
))}
<div
className={cn(
"flex h-8 w-4 items-center",
node.hasChildren && "hover:bg-charcoal-600"
)}
onClick={(e) => {
e.stopPropagation();
toggleExpandNode(node.id);
scrollToNode(node.id);
}}
>
{node.hasChildren ? (
state.expanded ? (
<ChevronDownIcon className="h-4 w-4 text-charcoal-400" />
) : (
<ChevronRightIcon className="h-4 w-4 text-charcoal-400" />
)
) : (
<ChevronRightIcon className="h-4 w-4 text-charcoal-400" />
)
) : (
<div className="h-8 w-4" />
)}
<div className="h-8 w-4" />
)}
</div>
</div>
</div>
<div className="flex w-full items-center justify-between gap-2 pl-1">
<div className="flex items-center gap-2 overflow-x-hidden">
<RunIcon
name={node.data.style?.icon}
spanName={node.data.message}
className="h-4 min-h-4 w-4 min-w-4"
/>
<NodeText node={node} />
{node.data.isRoot && <Badge variant="outline-rounded">Root</Badge>}
</div>
<div className="flex items-center gap-1">
<NodeStatusIcon node={node} />
<div className="flex w-full items-center justify-between gap-2 pl-1">
<div className="flex items-center gap-2 overflow-x-hidden">
<RunIcon
name={node.data.style?.icon}
spanName={node.data.message}
className="h-4 min-h-4 w-4 min-w-4"
/>
<NodeText node={node} />
{node.data.isRoot && <Badge variant="outline-rounded">Root</Badge>}
</div>
<div className="flex items-center gap-1">
<NodeStatusIcon node={node} />
</div>
</div>
</div>
</div>
{events.length === 1 && environmentType === "DEVELOPMENT" && (
<ConnectedDevWarning />
)}
</>
)}
onScroll={(scrollTop) => {
//sync the scroll to the tree
@@ -841,3 +853,34 @@ function CurrentTimeIndicator({ totalDuration }: { totalDuration: number }) {
</Timeline.FollowCursor>
);
}
function ConnectedDevWarning() {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(true);
}, 3000);
return () => clearTimeout(timer);
}, []);
return (
<div
className={cn(
"flex items-center overflow-hidden pl-5 pr-2 transition-opacity duration-500",
isVisible ? "opacity-100" : "h-0 opacity-0"
)}
>
<Callout variant="info">
<div className="flex flex-col gap-1">
<Paragraph variant="small">
Runs usually start within 1 second in{" "}
<EnvironmentLabel environment={{ type: "DEVELOPMENT" }} />. Check you're running the
CLI: <InlineCode className="whitespace-nowrap">npx trigger.dev@beta dev</InlineCode>
</Paragraph>
</div>
</Callout>
</div>
);
}
@@ -64,10 +64,12 @@ export default function Page() {
<PageTitle title="Runs" />
</NavBar>
<PageBody>
{list.possibleTasks.length === 0 ? (
<CreateFirstTaskInstructions />
) : list.runs.length === 0 && !list.hasFilters ? (
<RunTaskInstructions />
{list.runs.length === 0 && !list.hasFilters ? (
list.possibleTasks.length === 0 ? (
<CreateFirstTaskInstructions />
) : (
<RunTaskInstructions />
)
) : (
<div className={cn("grid h-fit grid-cols-1 gap-4")}>
<div>
+52
View File
@@ -0,0 +1,52 @@
import { type DataFunctionArgs } from "@remix-run/node";
import { PerformanceObserver } from "node:perf_hooks";
import { runInNewContext } from "node:vm";
import v8 from "v8";
import { requireUser } from "~/services/session.server";
async function waitTillGcFinishes() {
let resolver: (value: PerformanceEntry) => void;
let rejector: (reason?: any) => void;
const promise = new Promise<PerformanceEntry>((resolve, reject) => {
resolver = resolve;
rejector = reject;
});
const obs = new PerformanceObserver((list) => {
const entry = list.getEntries()[0];
if (entry.name === "gc") {
resolver(entry);
}
});
obs.observe({ entryTypes: ["gc"] });
v8.setFlagsFromString("--expose-gc");
const gc = global.gc ?? runInNewContext("gc");
gc();
// disable expose-gc
v8.setFlagsFromString("--noexpose-gc");
return promise;
}
export async function loader({ request }: DataFunctionArgs) {
const user = await requireUser(request);
if (!user.admin) {
throw new Response("You must be an admin to perform this action", { status: 403 });
}
const entry = await waitTillGcFinishes();
return new Response(JSON.stringify(entry), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}
@@ -1,11 +1,9 @@
import path from "path";
import os from "os";
import { type DataFunctionArgs } from "@remix-run/node";
import fs from "fs";
import v8 from "v8";
import os from "os";
import path from "path";
import { PassThrough } from "stream";
import { json, type DataFunctionArgs } from "@remix-run/node";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import v8 from "v8";
import { requireUser } from "~/services/session.server";
// Format date as yyyy-MM-dd HH_mm_ss_SSS
@@ -32,10 +30,11 @@ export async function loader({ request }: DataFunctionArgs) {
throw new Response("You must be an admin to perform this action", { status: 403 });
}
const host = request.headers.get("X-Forwarded-Host") ?? request.headers.get("host");
const tempDir = os.tmpdir();
const filepath = path.join(tempDir, `${host}-${formatDate(new Date())}.heapsnapshot`);
const filepath = path.join(
tempDir,
`${getTaskIdentifier()}-${formatDate(new Date())}.heapsnapshot`
);
const snapshotPath = v8.writeHeapSnapshot(filepath);
if (!snapshotPath) {
@@ -57,3 +56,13 @@ export async function loader({ request }: DataFunctionArgs) {
},
});
}
function getTaskIdentifier() {
if (!process.env.ECS_CONTAINER_METADATA_URI) {
return "local";
}
const url = new URL(process.env.ECS_CONTAINER_METADATA_URI);
return url.pathname.split("/")[2].split("-")[0];
}
+2 -2
View File
@@ -23,7 +23,7 @@ export async function action({ request }: ActionFunctionArgs) {
const anyBody = await request.json();
const body = GetPersonalAccessTokenRequestSchema.safeParse(anyBody);
if (!body.success) {
return json({ message: generateErrorMessage(body.error.issues) }, { status: 422 });
return json({ error: generateErrorMessage(body.error.issues) }, { status: 422 });
}
try {
@@ -45,6 +45,6 @@ export async function action({ request }: ActionFunctionArgs) {
return json({ error: error.message }, { status: 400 });
}
return json({ error: "Something went wrong" }, { status: 500 });
return json({ error: "Something went wrong" }, { status: 400 });
}
}
+28 -23
View File
@@ -8,29 +8,34 @@ import { authenticateApiRequestWithPersonalAccessToken } from "~/services/person
export async function loader({ request }: LoaderFunctionArgs) {
logger.info("whoami v2", { url: request.url });
try {
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
}
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
const user = await prisma.user.findUnique({
select: {
email: true,
},
where: {
id: authenticationResult.userId,
},
});
if (!user) {
return json({ error: "User not found" }, { status: 404 });
}
const result: WhoAmIResponse = {
userId: authenticationResult.userId,
email: user.email,
dashboardUrl: env.APP_ORIGIN,
};
return json(result);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Something went wrong";
logger.error("Error in whoami v2", { error: errorMessage });
return json({ error: errorMessage }, { status: 400 });
}
const user = await prisma.user.findUnique({
select: {
email: true,
},
where: {
id: authenticationResult.userId,
},
});
if (!user) {
return json({ error: "User not found" }, { status: 404 });
}
const result: WhoAmIResponse = {
userId: authenticationResult.userId,
email: user.email,
dashboardUrl: env.APP_ORIGIN,
};
return json(result);
}
+10
View File
@@ -0,0 +1,10 @@
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { metricsRegister } from "~/metrics.server";
export async function loader({ request }: LoaderFunctionArgs) {
return new Response(await metricsRegister.metrics(), {
headers: {
"Content-Type": metricsRegister.contentType,
},
});
}
@@ -1,10 +1,10 @@
import { PersonalAccessToken } from "@trigger.dev/database";
import { customAlphabet, nanoid } from "nanoid";
import nodeCrypto from "node:crypto";
import { z } from "zod";
import { prisma } from "~/db.server";
import { env } from "~/env.server";
import { logger } from "./logger.server";
import { PersonalAccessToken } from "@trigger.dev/database";
const tokenValueLength = 40;
//lowercase only, removed 0 and l to avoid confusion
@@ -54,7 +54,6 @@ export async function getPersonalAccessTokenFromAuthorizationCode(authorizationC
},
where: {
code: authorizationCode,
createdAt: {
gte: tenMinutesAgo,
},
@@ -199,13 +198,25 @@ export async function createPersonalAccessTokenFromAuthorizationCode(
},
});
//we only allow you to have one CLI PAT at a time
//we only allow you to have one CLI PAT at a time, so return this
if (existingCliPersonalAccessToken) {
await prisma.personalAccessToken.delete({
//associate this authorization code with the existing personal access token
await prisma.authorizationCode.update({
where: {
id: existingCliPersonalAccessToken.id,
code: authorizationCode,
},
data: {
personalAccessTokenId: existingCliPersonalAccessToken.id,
},
});
//we don't return the decrypted token
return {
id: existingCliPersonalAccessToken.id,
name: existingCliPersonalAccessToken.name,
userId: existingCliPersonalAccessToken.userId,
obfuscateToken: existingCliPersonalAccessToken.obfuscatedToken,
};
}
const token = await createPersonalAccessToken({
+13
View File
@@ -35,6 +35,7 @@ import { ResumeBatchRunService } from "~/v3/services/resumeBatchRun.server";
import { ResumeTaskDependencyService } from "~/v3/services/resumeTaskDependency.server";
import { TimeoutDeploymentService } from "~/v3/services/timeoutDeployment.server";
import { eventRepository } from "~/v3/eventRepository.server";
import { ExecuteTasksWaitingForDeployService } from "~/v3/services/executeTasksWaitingForDeploy";
const workerCatalog = {
indexEndpoint: z.object({
@@ -128,6 +129,9 @@ const workerCatalog = {
fromStatus: z.string(),
errorMessage: z.string(),
}),
"v3.executeTasksWaitingForDeploy": z.object({
backgroundWorkerId: z.string(),
}),
};
const executionWorkerCatalog = {
@@ -507,6 +511,15 @@ function getWorkerQueue() {
return await service.call(payload.deploymentId, payload.fromStatus, payload.errorMessage);
},
},
"v3.executeTasksWaitingForDeploy": {
priority: 0,
maxAttempts: 5,
handler: async (payload, job) => {
const service = new ExecuteTasksWaitingForDeployService();
return await service.call(payload.backgroundWorkerId);
},
},
},
});
}
@@ -6,11 +6,10 @@ import {
} from "@trigger.dev/core/v3";
import { Evt } from "evt";
import { randomUUID } from "node:crypto";
import type { CloseEvent, ErrorEvent, MessageEvent, WebSocket } from "ws";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { DevQueueConsumer } from "./marqs/devQueueConsumer.server";
import type { WebSocket, MessageEvent, CloseEvent, ErrorEvent } from "ws";
import { env } from "~/env.server";
export class AuthenticatedSocketConnection {
public id: string;
@@ -93,7 +92,11 @@ export class AuthenticatedSocketConnection {
await this._consumer.stop();
this.onClose.post(ev);
const result = this.onClose.post(ev);
logger.debug("[AuthenticatedSocketConnection] Called onClose", {
result,
});
}
async #handleError(ev: ErrorEvent) {
+41 -13
View File
@@ -26,6 +26,9 @@ import { env } from "~/env.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { DynamicFlushScheduler } from "./dynamicFlushScheduler.server";
import { singleton } from "~/utils/singleton";
import { Gauge } from "prom-client";
import { metricsRegister } from "~/metrics.server";
export type CreatableEvent = Omit<
Prisma.TaskEventCreateInput,
@@ -148,6 +151,11 @@ export class EventRepository {
private readonly _flushScheduler: DynamicFlushScheduler<CreatableEvent>;
private _randomIdGenerator = new RandomIdGenerator();
private _redisPublishClient: Redis;
private _subscriberCount = 0;
get subscriberCount() {
return this._subscriberCount;
}
constructor(private db: PrismaClient = prisma, private readonly _config: EventRepoConfig) {
this._flushScheduler = new DynamicFlushScheduler({
@@ -721,6 +729,9 @@ export class EventRepository {
// Subscribe to the channel.
await redis.psubscribe(channel);
// Increment the subscriber count.
this._subscriberCount++;
const eventEmitter = new EventEmitter();
// Define the message handler.
@@ -733,6 +744,8 @@ export class EventRepository {
// Return a function that can be used to unsubscribe.
const unsubscribe = async () => {
await redis.punsubscribe(channel);
redis.quit();
this._subscriberCount--;
};
return {
@@ -796,19 +809,34 @@ export class EventRepository {
}
}
export const eventRepository = new EventRepository(prisma, {
batchSize: env.EVENTS_BATCH_SIZE,
batchInterval: env.EVENTS_BATCH_INTERVAL,
retentionInDays: env.EVENTS_DEFAULT_LOG_RETENTION,
redis: {
port: env.REDIS_PORT,
host: env.REDIS_HOST,
username: env.REDIS_USERNAME,
password: env.REDIS_PASSWORD,
enableAutoPipelining: true,
...(env.REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }),
},
});
export const eventRepository = singleton("eventRepo", initializeEventRepo);
function initializeEventRepo() {
const repo = new EventRepository(prisma, {
batchSize: env.EVENTS_BATCH_SIZE,
batchInterval: env.EVENTS_BATCH_INTERVAL,
retentionInDays: env.EVENTS_DEFAULT_LOG_RETENTION,
redis: {
port: env.REDIS_PORT,
host: env.REDIS_HOST,
username: env.REDIS_USERNAME,
password: env.REDIS_PASSWORD,
enableAutoPipelining: true,
...(env.REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }),
},
});
new Gauge({
name: "event_repository_subscriber_count",
help: "Number of event repository subscribers",
collect() {
this.set(repo.subscriberCount);
},
registers: [metricsRegister],
});
return repo;
}
export function stripAttributePrefix(attributes: Attributes, prefix: string) {
const result: Attributes = {};
+16 -2
View File
@@ -4,6 +4,8 @@ import { authenticateApiKey } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { singleton } from "../utils/singleton";
import { AuthenticatedSocketConnection } from "./authenticatedSocketConnection.server";
import { Gauge } from "prom-client";
import { metricsRegister } from "~/metrics.server";
export const wss = singleton("wss", initalizeWebSocketServer);
@@ -16,6 +18,15 @@ function initalizeWebSocketServer() {
authenticatedConnections = new Map();
new Gauge({
name: "dev_authenticated_connections",
help: "Number of authenticated dev connections",
collect() {
this.set(authenticatedConnections.size);
},
registers: [metricsRegister],
});
return server;
}
@@ -47,8 +58,11 @@ async function handleWebSocketConnection(ws: WebSocket, req: IncomingMessage) {
authenticatedConnections.set(authenticatedConnection.id, authenticatedConnection);
authenticatedConnection.onClose.attach((closeEvent) => {
logger.debug("Websocket closed", { closeEvent });
authenticatedConnection.onClose.attachOnce((closeEvent) => {
logger.debug("Websocket closed", {
closeEvent,
authenticatedConnectionId: authenticatedConnection.id,
});
authenticatedConnections.delete(authenticatedConnection.id);
});
+14 -1
View File
@@ -2,6 +2,8 @@ import { z } from "zod";
import { singleton } from "~/utils/singleton";
import { ZodPubSub, ZodSubscriber } from "../utils/zodPubSub.server";
import { env } from "~/env.server";
import { Gauge } from "prom-client";
import { metricsRegister } from "~/metrics.server";
const messageCatalog = {
CANCEL_ATTEMPT: z.object({
@@ -17,7 +19,7 @@ export type DevSubscriber = ZodSubscriber<typeof messageCatalog>;
export const devPubSub = singleton("devPubSub", initializeDevPubSub);
function initializeDevPubSub() {
return new ZodPubSub({
const pubSub = new ZodPubSub({
redis: {
port: env.REDIS_PORT,
host: env.REDIS_HOST,
@@ -28,4 +30,15 @@ function initializeDevPubSub() {
},
schema: messageCatalog,
});
new Gauge({
name: "dev_pub_sub_subscribers",
help: "Number of dev pub sub subscribers",
collect() {
this.set(pubSub.subscriberCount);
},
registers: [metricsRegister],
});
return pubSub;
}
+3 -4
View File
@@ -92,15 +92,14 @@ export class MarQS {
queue: string,
messageId: string,
messageData: Record<string, unknown>,
concurrencyKey?: string
concurrencyKey?: string,
timestamp?: number
) {
return await this.#trace(
"enqueueMessage",
async (span) => {
const messageQueue = this.keys.queueKey(env, queue, concurrencyKey);
const timestamp = Date.now();
const parentQueue = this.keys.envSharedQueueKey(env);
propagation.inject(context.active(), messageData);
@@ -110,7 +109,7 @@ export class MarQS {
data: messageData,
queue: messageQueue,
concurrencyKey,
timestamp,
timestamp: timestamp ?? Date.now(),
messageId,
parentQueue,
};
@@ -77,7 +77,6 @@ export class SharedQueueConsumer {
private _currentSpan: Span | undefined;
private _endSpanInNextIteration = false;
private _tasks = sharedQueueTasks;
private _inProgressAttempts: Map<string, string> = new Map(); // Keys are task attempt friendly IDs, values are TaskRun ids/queue message ids
constructor(
private _sender: ZodMessageSender<typeof serverWebsocketMessages>,
@@ -151,38 +150,6 @@ export class SharedQueueConsumer {
}
}
async #cancelInProgressAttempts(reason: string) {
const service = new CancelAttemptService();
const cancelledAt = new Date();
const inProgressAttempts = new Map(this._inProgressAttempts);
this._inProgressAttempts.clear();
for (const [attemptId, messageId] of inProgressAttempts) {
await this.#cancelInProgressAttempt(attemptId, messageId, service, cancelledAt, reason);
}
}
async #cancelInProgressAttempt(
attemptId: string,
messageId: string,
cancelAttemptService: CancelAttemptService,
cancelledAt: Date,
reason: string
) {
try {
await cancelAttemptService.call(attemptId, messageId, cancelledAt, reason);
} catch (e) {
logger.error("Failed to cancel in progress attempt", {
attemptId,
messageId,
error: e,
});
}
}
#enable() {
if (this._enabled) {
return;
@@ -275,10 +242,12 @@ export class SharedQueueConsumer {
error: messageBody.error,
});
this.#ackAndDoMoreWork(message.messageId);
await this.#ackAndDoMoreWork(message.messageId);
return;
}
// TODO: For every ACK, decide what should be done with the existing run and attempts. Make sure to check the current statuses first.
switch (messageBody.data.type) {
case "EXECUTE": {
const existingTaskRun = await prisma.taskRun.findUnique({
@@ -293,7 +262,10 @@ export class SharedQueueConsumer {
messageId: message.messageId,
});
this.#ackAndDoMoreWork(message.messageId);
// INFO: There used to be a race condition where tasks could be triggered, but execute messages could be dequeued before the run finished being created in the DB
// This should not be happening anymore. In case it does, consider reqeueuing here with a brief delay while limiting total retries.
await this.#ackAndDoMoreWork(message.messageId);
return;
}
@@ -321,7 +293,7 @@ export class SharedQueueConsumer {
retryingFromCheckpoint,
});
this.#ackAndDoMoreWork(message.messageId);
await this.#ackAndDoMoreWork(message.messageId);
return;
}
@@ -333,7 +305,9 @@ export class SharedQueueConsumer {
messageId: message.messageId,
});
this.#ackAndDoMoreWork(message.messageId);
await this.#markRunAsWaitingForDeploy(existingTaskRun.id);
await this.#ackAndDoMoreWork(message.messageId);
return;
}
@@ -344,7 +318,9 @@ export class SharedQueueConsumer {
deployment: deployment.id,
});
this.#ackAndDoMoreWork(message.messageId);
await this.#markRunAsWaitingForDeploy(existingTaskRun.id);
await this.#ackAndDoMoreWork(message.messageId);
return;
}
@@ -353,15 +329,39 @@ export class SharedQueueConsumer {
);
if (!backgroundTask) {
logger.warn("No matching background task found for task run", {
taskRun: existingTaskRun.id,
taskIdentifier: existingTaskRun.taskIdentifier,
deployment: deployment.id,
backgroundWorker: deployment.worker.id,
taskSlugs: deployment.worker.tasks.map((task) => task.slug),
const nonCurrentTask = await prisma.backgroundWorkerTask.findFirst({
where: {
slug: existingTaskRun.taskIdentifier,
projectId: existingTaskRun.projectId,
runtimeEnvironmentId: existingTaskRun.runtimeEnvironmentId,
},
include: {
worker: {
include: {
deployment: {
include: {},
},
},
},
},
});
this.#ackAndDoMoreWork(message.messageId);
if (nonCurrentTask) {
logger.warn("Task for this run exists but is not part of the current deploy", {
taskRun: existingTaskRun.id,
taskIdentifier: existingTaskRun.taskIdentifier,
});
} else {
logger.warn("Task for this run has never been deployed", {
taskRun: existingTaskRun.id,
taskIdentifier: existingTaskRun.taskIdentifier,
});
}
await this.#markRunAsWaitingForDeploy(existingTaskRun.id);
// If this task is ever deployed, a new message will be enqueued after successful indexing
await this.#ackAndDoMoreWork(message.messageId);
return;
}
@@ -398,7 +398,7 @@ export class SharedQueueConsumer {
messageId: message.messageId,
});
this.#ackAndDoMoreWork(message.messageId);
await this.#ackAndDoMoreWork(message.messageId);
return;
}
@@ -495,8 +495,6 @@ export class SharedQueueConsumer {
},
});
}
this._inProgressAttempts.set(taskRunAttempt.friendlyId, message.messageId);
} catch (e) {
if (e instanceof Error) {
this._currentSpan?.recordException(e);
@@ -757,9 +755,23 @@ export class SharedQueueConsumer {
this.#doMoreWork(intervalInMs);
}
async #nackAndDoMoreWork(messageId: string, intervalInMs?: number) {
await marqs?.nackMessage(messageId);
this.#doMoreWork(intervalInMs);
async #nackAndDoMoreWork(messageId: string, queueIntervalInMs?: number, nackRetryInMs?: number) {
const retryAt = nackRetryInMs ? Date.now() + nackRetryInMs : undefined;
await marqs?.nackMessage(messageId, retryAt);
this.#doMoreWork(queueIntervalInMs);
}
async #markRunAsWaitingForDeploy(runId: string) {
logger.debug("Marking run as waiting for deploy", { runId });
return await prisma.taskRun.update({
where: {
id: runId,
},
data: {
status: "WAITING_FOR_DEPLOY",
},
});
}
}
@@ -10,6 +10,7 @@ import { logger } from "~/services/logger.server";
export const CANCELLABLE_STATUSES: Array<TaskRunStatus> = [
"PENDING",
"WAITING_FOR_DEPLOY",
"EXECUTING",
"PAUSED",
"WAITING_TO_RESUME",
@@ -13,6 +13,7 @@ import { ResumeTaskRunDependenciesService } from "./resumeTaskRunDependencies.se
export const CRASHABLE_RUN_STATUSES: Array<TaskRunStatus> = [
"PENDING",
"WAITING_FOR_DEPLOY",
"EXECUTING",
"PAUSED",
"WAITING_TO_RESUME",
@@ -8,6 +8,7 @@ import { CURRENT_DEPLOYMENT_LABEL } from "~/consts";
import { projectPubSub } from "./projectPubSub.server";
import { marqs } from "~/v3/marqs/index.server";
import { logger } from "~/services/logger.server";
import { ExecuteTasksWaitingForDeployService } from "./executeTasksWaitingForDeploy";
export class CreateDeployedBackgroundWorkerService extends BaseService {
public async call(
@@ -96,6 +97,8 @@ export class CreateDeployedBackgroundWorkerService extends BaseService {
logger.error("Failed to publish WORKER_CREATED event", { err });
}
await ExecuteTasksWaitingForDeployService.enqueue(backgroundWorker.id, this._prisma);
return backgroundWorker;
});
}
@@ -0,0 +1,122 @@
import { PrismaClientOrTransaction } from "~/db.server";
import { workerQueue } from "~/services/worker.server";
import { marqs } from "~/v3/marqs/index.server";
import { BaseService } from "./baseService.server";
import { logger } from "~/services/logger.server";
export class ExecuteTasksWaitingForDeployService extends BaseService {
public async call(backgroundWorkerId: string) {
const backgroundWorker = await this._prisma.backgroundWorker.findFirst({
where: {
id: backgroundWorkerId,
},
include: {
runtimeEnvironment: {
include: {
project: true,
organization: true,
},
},
tasks: true,
},
});
if (!backgroundWorker) {
logger.error("Background worker not found", { id: backgroundWorkerId });
return;
}
const runsWaitingForDeploy = await this._prisma.taskRun.findMany({
where: {
runtimeEnvironmentId: backgroundWorker.runtimeEnvironmentId,
projectId: backgroundWorker.projectId,
status: "WAITING_FOR_DEPLOY",
taskIdentifier: {
in: backgroundWorker.tasks.map((task) => task.slug),
},
},
orderBy: {
number: "asc",
},
});
if (!runsWaitingForDeploy.length) {
return;
}
// Clear any runs awaiting deployment for execution
const pendingRuns = await this._prisma.taskRun.updateMany({
where: {
id: {
in: runsWaitingForDeploy.map((run) => run.id),
},
},
data: {
status: "PENDING",
},
});
if (pendingRuns.count) {
logger.debug("Task runs waiting for deploy are now ready for execution", {
tasks: runsWaitingForDeploy.map((run) => run.id),
total: pendingRuns.count,
});
}
if (!marqs) {
return;
}
const enqueues: Promise<any>[] = [];
let i = 0;
for (const run of runsWaitingForDeploy) {
enqueues.push(
marqs.enqueueMessage(
backgroundWorker.runtimeEnvironment,
run.queue,
run.id,
{
type: "EXECUTE",
taskIdentifier: run.taskIdentifier,
},
run.concurrencyKey ?? undefined,
Date.now() + i * 5 // slight delay to help preserve order
)
);
i++;
}
const settled = await Promise.allSettled(enqueues);
if (settled.some((s) => s.status === "rejected")) {
const rejectedRuns: { id: string; reason: any }[] = [];
runsWaitingForDeploy.forEach((run, i) => {
if (settled[i].status === "rejected") {
const rejected = settled[i] as PromiseRejectedResult;
rejectedRuns.push({ id: run.id, reason: rejected.reason });
}
});
logger.error("Failed to requeue task runs for immediate execution", {
rejectedRuns,
});
}
}
static async enqueue(backgroundWorkerId: string, tx: PrismaClientOrTransaction, runAt?: Date) {
return await workerQueue.enqueue(
"v3.executeTasksWaitingForDeploy",
{
backgroundWorkerId,
},
{
tx,
runAt,
}
);
}
}
@@ -2,6 +2,8 @@ import { z } from "zod";
import { singleton } from "~/utils/singleton";
import { ZodPubSub, ZodSubscriber } from "../utils/zodPubSub.server";
import { env } from "~/env.server";
import { Gauge } from "prom-client";
import { metricsRegister } from "~/metrics.server";
const messageCatalog = {
WORKER_CREATED: z.object({
@@ -18,7 +20,7 @@ export type ProjectSubscriber = ZodSubscriber<typeof messageCatalog>;
export const projectPubSub = singleton("projectPubSub", initializeProjectPubSub);
function initializeProjectPubSub() {
return new ZodPubSub({
const pubSub = new ZodPubSub({
redis: {
port: env.REDIS_PORT,
host: env.REDIS_HOST,
@@ -29,4 +31,15 @@ function initializeProjectPubSub() {
},
schema: messageCatalog,
});
new Gauge({
name: "project_pub_sub_subscribers",
help: "Number of project pub sub subscribers",
collect() {
this.set(pubSub.subscriberCount);
},
registers: [metricsRegister],
});
return pubSub;
}
@@ -71,7 +71,7 @@ export class TriggerTaskService extends BaseService {
async (event, traceContext) => {
const lockId = taskIdentifierToLockId(taskId);
return await $transaction(this._prisma, async (tx) => {
const run = await $transaction(this._prisma, async (tx) => {
await tx.$executeRaw`SELECT pg_advisory_xact_lock(${lockId})`;
const lockedToBackgroundWorker = body.options?.lockToVersion
@@ -169,17 +169,23 @@ export class TriggerTaskService extends BaseService {
}
}
// We need to enqueue the task run into the appropriate queue
await marqs?.enqueueMessage(
environment,
queueName,
taskRun.id,
{ type: "EXECUTE", taskIdentifier: taskId },
body.options?.concurrencyKey
);
return taskRun;
});
if (!run) {
return;
}
// We need to enqueue the task run into the appropriate queue. This is done after the tx completes to prevent a race condition where the task run hasn't been created yet by the time we dequeue.
await marqs?.enqueueMessage(
environment,
run.queue,
run.id,
{ type: "EXECUTE", taskIdentifier: taskId },
body.options?.concurrencyKey
);
return run;
}
);
});
+6
View File
@@ -77,6 +77,12 @@ class CustomWebappSampler implements Sampler {
export const tracer = singleton("tracer", getTracer);
function getTracer() {
if (env.INTERNAL_OTEL_TRACE_DISABLED === "1") {
console.log(`🔦 Tracer disabled, returning a noop tracer`);
return trace.getTracer("trigger.dev", "3.0.0.dp.1");
}
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);
const samplingRate = 1.0 / Math.max(parseInt(env.INTERNAL_OTEL_TRACE_SAMPLING_RATE, 10), 1);
+23 -1
View File
@@ -1,5 +1,6 @@
import { Logger } from "@trigger.dev/core-backend";
import { ZodMessageCatalogSchema, ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3";
import { Evt } from "evt";
import Redis, { RedisOptions } from "ioredis";
import { z } from "zod";
import { logger } from "~/services/logger.server";
@@ -26,6 +27,10 @@ class RedisZodSubscriber<TMessageCatalog extends ZodMessageCatalogSchema>
private _listeners: Map<string, (payload: unknown) => Promise<void>> = new Map();
private _messageHandler: ZodMessageHandler<TMessageCatalog>;
public onUnsubscribed: Evt<{
pattern: string;
}> = new Evt();
constructor(
private readonly _pattern: string,
private readonly _options: ZodPubSubOptions<TMessageCatalog>,
@@ -51,7 +56,11 @@ class RedisZodSubscriber<TMessageCatalog extends ZodMessageCatalogSchema>
public async stopListening(): Promise<void> {
this._listeners.clear();
await this._subscriber.unsubscribe();
await this._subscriber.punsubscribe();
this.onUnsubscribed.post({ pattern: this._pattern });
this._subscriber.quit();
}
async #onMessage(pattern: string, channel: string, serializedMessage: string) {
@@ -90,6 +99,11 @@ class RedisZodSubscriber<TMessageCatalog extends ZodMessageCatalogSchema>
export class ZodPubSub<TMessageCatalog extends ZodMessageCatalogSchema> {
private _publisher: Redis;
private _logger = logger.child({ module: "ZodPubSub" });
private _subscriberCount = 0;
get subscriberCount() {
return this._subscriberCount;
}
constructor(private _options: ZodPubSubOptions<TMessageCatalog>) {
this._publisher = new Redis(_options.redis);
@@ -112,6 +126,14 @@ export class ZodPubSub<TMessageCatalog extends ZodMessageCatalogSchema> {
await subscriber.initialize();
this._subscriberCount++;
subscriber.onUnsubscribed.attachOnce(({ pattern }) => {
logger.debug("Subscriber unsubscribed", { pattern });
this._subscriberCount--;
});
return subscriber;
}
}
+1
View File
@@ -126,6 +126,7 @@
"posthog-node": "^3.1.3",
"prism-react-renderer": "^1.3.5",
"prismjs": "^1.29.0",
"prom-client": "^15.1.0",
"random-words": "^2.0.0",
"react": "^18.2.0",
"react-aria": "^3.31.1",
Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

+184 -184
View File
@@ -42,6 +42,16 @@
"name": "Home"
},
"tabs": [
{
"name": "v3 Developer Preview",
"url": "https://trigger.dev/docs/v3",
"version": "v2"
},
{
"name": "v2",
"url": "https://trigger.dev/docs",
"version": "v3 (Developer Preview)"
},
{
"name": "Integrations",
"url": "integrations",
@@ -56,16 +66,6 @@
"name": "Examples",
"url": "https://trigger.dev/apis",
"version": "v2"
},
{
"name": "v3 Developer Preview",
"url": "https://trigger.dev/docs/v3",
"version": "v2"
},
{
"name": "v2",
"url": "https://trigger.dev/docs",
"version": "v3 (Developer Preview)"
}
],
"redirects": [
@@ -87,6 +87,180 @@
}
],
"navigation": [
{
"group": "",
"version": "v3 (Developer Preview)",
"pages": ["v3/introduction"]
},
{
"group": "Getting Started",
"version": "v3 (Developer Preview)",
"pages": [
"v3/quick-start",
"v3/upgrading-from-v2",
"v3/changelog",
"v3/feature-matrix",
"v3/known-issues",
"v3/limits-performance"
]
},
{
"group": "Fundamentals",
"version": "v3 (Developer Preview)",
"pages": [
"v3/trigger-folder",
"v3/tasks-overview",
"v3/triggering",
"v3/apikeys",
{
"group": "Task types",
"pages": ["v3/tasks-regular", "v3/tasks-scheduled", "v3/tasks-zod", "v3/tasks-webhooks"]
},
"v3/trigger-config"
]
},
{
"group": "Development",
"version": "v3 (Developer Preview)",
"pages": ["v3/cli-dev", "v3/run-tests"]
},
{
"group": "Deployment",
"version": "v3 (Developer Preview)",
"pages": [
"v3/deploy-environment-variables",
"v3/cli-deploy",
"v3/github-actions",
{
"group": "Deployment integrations",
"pages": ["v3/vercel-integration"]
}
]
},
{
"group": "Writing tasks",
"version": "v3 (Developer Preview)",
"pages": [
"v3/writing-tasks-introduction",
"v3/logging",
"v3/errors-retrying",
{
"group": "Wait",
"pages": [
"v3/wait",
"v3/wait-for",
"v3/wait-until",
"v3/wait-for-event",
"v3/wait-for-request"
]
},
"v3/queue-concurrency",
"v3/versioning",
"v3/machines",
"v3/idempotency",
"v3/reattempting-replaying",
"v3/trigger-filters",
"v3/notifications",
"v3/rollbacks",
"v3/using-apis",
"v3/middleware",
"v3/automated-tests"
]
},
{
"group": "Dashboard",
"version": "v3 (Developer Preview)",
"pages": [
"v3/dashboard-overview",
"v3/dashboard-runs",
"v3/dashboard-tests",
"v3/dashboard-environment-variables"
]
},
{
"group": "API reference",
"version": "v3 (Developer Preview)",
"pages": [
{
"group": "Functions",
"pages": [
"v3/reference-task",
"v3/reference-cron-task",
"v3/reference-cron-dynamic",
"v3/reference-interval-task",
"v3/reference-interval-dynamic",
"v3/reference-zod-task",
"v3/reference-zod-catalog",
"v3/reference-task-trigger",
"v3/reference-task-trigger-and-wait",
"v3/reference-task-batch-trigger",
"v3/reference-task-batch-trigger-and-wait",
"v3/reference-wait-for",
"v3/reference-wait-until",
"v3/reference-wait-for-event",
"v3/reference-wait-for-request",
"v3/reference-retry-on-throw",
"v3/reference-retry-fetch",
"v3/reference-retry-intercept-fetch",
"v3/reference-notification-catalog",
"v3/reference-notify",
"v3/reference-queue"
]
},
{
"group": "Objects",
"pages": ["v3/reference-context"]
},
{
"group": "CLI",
"pages": [
"v3/reference-cli-init",
"v3/reference-cli-dev",
"v3/reference-cli-deploy",
"v3/reference-cli-login",
"v3/reference-cli-logout",
"v3/reference-cli-update",
"v3/reference-cli-build",
"v3/reference-cli-who-am-i"
]
},
{
"group": "Management API",
"pages": [
"v3/management-start-run",
"v3/management-get-run",
"v3/management-get-runs",
"v3/management-replay-run",
"v3/management-cancel-run"
]
}
]
},
{
"group": "Architecture",
"version": "v3 (Developer Preview)",
"pages": [
"v3/architecture-how-it-works",
"v3/architecture-multi-tenant-queue",
"v3/architecture-reliability"
]
},
{
"group": "Open source",
"version": "v3 (Developer Preview)",
"pages": ["v3/github-repo", "v3/open-source-self-hosting", "v3/open-source-contributing"]
},
{
"group": "Help",
"version": "v3 (Developer Preview)",
"pages": [
"v3/help-faqs",
"v3/community",
"v3/help-email",
"v3/help-slack",
"v3/help-uptime-status"
]
},
{
"group": "Getting Started",
"version": "v2",
@@ -449,180 +623,6 @@
"group": "Overview",
"version": "v2",
"pages": ["examples/introduction"]
},
{
"group": "",
"version": "v3 (Developer Preview)",
"pages": ["v3/introduction"]
},
{
"group": "Getting Started",
"version": "v3 (Developer Preview)",
"pages": [
"v3/quick-start",
"v3/upgrading-from-v2",
"v3/changelog",
"v3/feature-matrix",
"v3/known-issues",
"v3/limits-performance"
]
},
{
"group": "Fundamentals",
"version": "v3 (Developer Preview)",
"pages": [
"v3/trigger-folder",
"v3/tasks-overview",
"v3/triggering",
"v3/apikeys",
{
"group": "Task types",
"pages": ["v3/tasks-regular", "v3/tasks-scheduled", "v3/tasks-zod", "v3/tasks-webhooks"]
},
"v3/trigger-config"
]
},
{
"group": "Development",
"version": "v3 (Developer Preview)",
"pages": ["v3/cli-dev", "v3/run-tests"]
},
{
"group": "Deployment",
"version": "v3 (Developer Preview)",
"pages": [
"v3/deploy-environment-variables",
"v3/cli-deploy",
"v3/github-actions",
{
"group": "Deployment integrations",
"pages": ["v3/vercel-integration"]
}
]
},
{
"group": "Writing tasks",
"version": "v3 (Developer Preview)",
"pages": [
"v3/writing-tasks-introduction",
"v3/logging",
"v3/errors-retrying",
{
"group": "Wait",
"pages": [
"v3/wait",
"v3/wait-for",
"v3/wait-until",
"v3/wait-for-event",
"v3/wait-for-request"
]
},
"v3/queue-concurrency",
"v3/versioning",
"v3/machines",
"v3/idempotency",
"v3/reattempting-replaying",
"v3/trigger-filters",
"v3/notifications",
"v3/rollbacks",
"v3/using-apis",
"v3/middleware",
"v3/automated-tests"
]
},
{
"group": "Dashboard",
"version": "v3 (Developer Preview)",
"pages": [
"v3/dashboard-overview",
"v3/dashboard-runs",
"v3/dashboard-tests",
"v3/dashboard-environment-variables"
]
},
{
"group": "API reference",
"version": "v3 (Developer Preview)",
"pages": [
{
"group": "Functions",
"pages": [
"v3/reference-task",
"v3/reference-cron-task",
"v3/reference-cron-dynamic",
"v3/reference-interval-task",
"v3/reference-interval-dynamic",
"v3/reference-zod-task",
"v3/reference-zod-catalog",
"v3/reference-task-trigger",
"v3/reference-task-trigger-and-wait",
"v3/reference-task-batch-trigger",
"v3/reference-task-batch-trigger-and-wait",
"v3/reference-wait-for",
"v3/reference-wait-until",
"v3/reference-wait-for-event",
"v3/reference-wait-for-request",
"v3/reference-retry-on-throw",
"v3/reference-retry-fetch",
"v3/reference-retry-intercept-fetch",
"v3/reference-notification-catalog",
"v3/reference-notify",
"v3/reference-queue"
]
},
{
"group": "Objects",
"pages": ["v3/reference-context"]
},
{
"group": "CLI",
"pages": [
"v3/reference-cli-init",
"v3/reference-cli-dev",
"v3/reference-cli-deploy",
"v3/reference-cli-login",
"v3/reference-cli-logout",
"v3/reference-cli-update",
"v3/reference-cli-build",
"v3/reference-cli-who-am-i"
]
},
{
"group": "Management API",
"pages": [
"v3/management-start-run",
"v3/management-get-run",
"v3/management-get-runs",
"v3/management-replay-run",
"v3/management-cancel-run"
]
}
]
},
{
"group": "Architecture",
"version": "v3 (Developer Preview)",
"pages": [
"v3/architecture-how-it-works",
"v3/architecture-multi-tenant-queue",
"v3/architecture-reliability"
]
},
{
"group": "Open source",
"version": "v3 (Developer Preview)",
"pages": ["v3/github-repo", "v3/open-source-self-hosting", "v3/open-source-contributing"]
},
{
"group": "Help",
"version": "v3 (Developer Preview)",
"pages": [
"v3/help-faqs",
"v3/community",
"v3/help-email",
"v3/help-slack",
"v3/help-uptime-status"
]
}
],
"footerSocials": {
+49
View File
@@ -3,4 +3,53 @@ title: "Reattempting & Replaying"
description: "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."
---
## Replaying
A replay is a copy of a run with the same payload but against the latest version in that environment. This is useful if something went wrong and you want to try again with the latest version of your code.
### Replaying from the UI
<Tabs>
<Tab title="From a run">
Select a task, then in the bottom right click "Replay" ![Select a task, then in the bottom right
click "Replay"](/images/v3/replay-run-action.png)
</Tab>
<Tab title="Runs list">
<Steps>
<Step title="Click the action button on a run">
![On the runs page, press the triple dot button](/images/v3/replay-runs-list.png)
</Step>
<Step title="Click replay">![Click replay](/images/v3/replay-runs-list-popover.png)</Step>
</Steps>
</Tab>
</Tabs>
### Replaying using the SDK
You can replay a run using the SDK:
```ts
const replayedRun = await runs.replay(run.id);
```
When you call `trigger()` or `batchTrigger()` on a task you receive back a run handle which has an `id` property. You can use that `id` to replay the run.
You can also access the run id from inside a run. You could write this to your database and then replay it later.
```ts
export const simpleChildTask = task({
id: "simple-child-task",
run: async (payload, { ctx }) => {
// the run ID (and other useful info) is in ctx
const runId = ctx.run.id;
},
});
```
### Reattempting
Tasks can [automatically reattempt](/v3/errors-retrying) based on the settings you provide.
Sometimes a task will fail all of its attempts. In that case, you can continue reattempting.
<Snippet file="coming-soon.mdx" />
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/airtable
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/airtable",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"airtable": "^0.12.1",
"zod": "3.22.3"
},
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/github
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"zod": "3.22.3"
},
"engines": {
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/linear
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/linear",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"zod": "3.22.3"
},
"engines": {
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/slack
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/openai",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.7"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12"
},
"engines": {
"node": ">=18.0.0"
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/plain
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/plain",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"@team-plain/typescript-sdk": "^2.7.0"
},
"engines": {
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/replicate
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/replicate",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"replicate": "^0.18.1",
"zod": "3.22.3"
},
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/resend
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"resend": "^2.1.0"
},
"engines": {
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/sendgrid
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sendgrid",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.7"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12"
},
"engines": {
"node": ">=16.8.0"
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/shopify
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/shopify",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"zod": "3.22.3"
},
"engines": {
+12
View File
@@ -1,5 +1,17 @@
# @trigger.dev/slack
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/slack",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"zod": "3.22.3"
},
"engines": {
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/stripe
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/stripe",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"stripe": "^12.14.0",
"zod": "3.22.3"
},
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/supabase
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/supabase",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"supabase-management-js": "^1.0.0",
"zod": "3.22.3"
},
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/typeform
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.12
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.11
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/typeform",
"version": "3.0.0-beta.7",
"version": "3.0.0-beta.12",
"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.7",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.7",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.12",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12",
"@typeform/api-client": "^1.8.0",
"zod": "3.22.3"
},
+12
View File
@@ -1,5 +1,17 @@
# @trigger.dev/astro
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.11
## 3.0.0-beta.7
### 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.7",
"version": "3.0.0-beta.12",
"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.7"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.12"
},
"devDependencies": {
"astro": "^3.0.12",
+28
View File
@@ -1,5 +1,33 @@
# trigger.dev
## 3.0.0-beta.12
### Patch Changes
- d3a18fbdf: Fix package builds and CLI commands on Windows
- d3a18fbdf: Init command was failing on Windows because of bad template paths
- @trigger.dev/core@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- 63a643b7c: v3: fix digest extraction
- @trigger.dev/core@3.0.0-beta.11
## 3.0.0-beta.10
### Patch Changes
- 7a9bd18ba: Stop swallowing deployment errors and display them better
## 3.0.0-beta.9
### Patch Changes
- 279717b09: Dont swallow some error messages when deploying
- 328947dbf: Use the dashboard url instead of the API url for the View logs link
## 3.0.0-beta.8
### Patch Changes
+5 -10
View File
@@ -1,6 +1,6 @@
{
"name": "trigger.dev",
"version": "3.0.0-beta.8",
"version": "3.0.0-beta.12",
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -38,7 +38,6 @@
"@trigger.dev/core-apps": "workspace:*",
"@trigger.dev/tsconfig": "workspace:*",
"@types/gradient-string": "^1.1.2",
"@types/jsonlines": "^0.1.5",
"@types/mock-fs": "^4.13.1",
"@types/node": "18",
"@types/object-hash": "^3.0.6",
@@ -46,8 +45,8 @@
"@types/semver": "^7.3.13",
"@types/ws": "^8.5.3",
"cpy-cli": "^5.0.0",
"nodemon": "^3.0.1",
"npm-run-all": "^4.1.5",
"npm-watch": "^0.11.0",
"open": "^10.0.3",
"p-retry": "^6.1.0",
"rimraf": "^3.0.2",
@@ -57,9 +56,6 @@
"vitest": "^0.34.4",
"xdg-app-paths": "^8.3.0"
},
"watch": {
"build:prod-containerfile": "src/Containerfile.prod"
},
"scripts": {
"typecheck": "tsc -p tsconfig.check.json",
"build": "npm run clean && run-p build:**",
@@ -69,7 +65,7 @@
"dev": "npm run clean && run-p dev:**",
"dev:main": "tsup --watch",
"dev:workers": "tsup --config tsup.workers.config.ts --watch",
"dev:prod-containerfile": "npm-watch",
"dev:test": "nodemon -w src/Containerfile.prod -x npm run build:prod-containerfile",
"clean": "rimraf dist",
"start": "node dist/index.js",
"test": "vitest"
@@ -89,7 +85,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.7",
"@trigger.dev/core": "workspace:^3.0.0-beta.12",
"@types/degit": "^2.8.3",
"chalk": "^5.2.0",
"chokidar": "^3.5.3",
@@ -106,7 +102,6 @@
"import-meta-resolve": "^4.0.0",
"ink": "^4.4.1",
"jsonc-parser": "^3.2.1",
"jsonlines": "^0.1.1",
"liquidjs": "^10.9.2",
"mock-fs": "^5.2.0",
"nanoid": "^4.0.2",
@@ -135,4 +130,4 @@
"engines": {
"node": ">=18.0.0"
}
}
}
+1
View File
@@ -3,6 +3,7 @@ FROM node:20-bookworm-slim@sha256:d4cdfc305abe5ea78da7167bf78263c22596dc332f2654
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
busybox \
ca-certificates \
dumb-init \
openssl \
&& rm -rf /var/lib/apt/lists/*
+41 -43
View File
@@ -1,4 +1,4 @@
import { intro, log, outro, spinner } from "@clack/prompts";
import { intro, log, outro } from "@clack/prompts";
import { depot } from "@depot/cli";
import { context, trace } from "@opentelemetry/api";
import {
@@ -12,11 +12,10 @@ import chalk from "chalk";
import { Command, Option as CommandOption } from "commander";
import { Metafile, build } from "esbuild";
import { execa } from "execa";
import { resolve as importResolve } from "import-meta-resolve";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join, relative } from "node:path";
import { dirname, join, relative, posix } from "node:path";
import { setTimeout } from "node:timers/promises";
import terminalLink from "terminal-link";
import invariant from "tiny-invariant";
@@ -55,6 +54,9 @@ import {
parseNpmInstallError,
} from "../utilities/deployErrors";
import { safeJsonParse } from "../utilities/safeJsonParse";
import { JavascriptProject } from "../utilities/javascriptProject";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { escapeImportPath, spinner } from "../utilities/windows";
const DeployCommandOptions = CommonCommandOptions.extend({
skipTypecheck: z.boolean().default(false),
@@ -93,7 +95,7 @@ export function configureDeployCommand(program: Command) {
.option("-c, --config <config file>", "The name of the config file, found at [path]")
.option(
"-p, --project-ref <project ref>",
"The project ref. Required if there is no config file."
"The project ref. Required if there is no config file. This will override the project specified in the config file."
)
)
.addOption(
@@ -423,7 +425,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
}
const parsedError = finishedDeployment.errorData.stack
? parseBuildErrorStack(finishedDeployment.errorData)
? parseBuildErrorStack(finishedDeployment.errorData) ??
finishedDeployment.errorData.message
: finishedDeployment.errorData.message;
if (typeof parsedError === "string") {
@@ -686,7 +689,10 @@ async function buildAndPushImage(
childProcess.stderr?.on("data", (data: Buffer) => {
const text = data.toString();
errors.push(text);
// Emitted data chunks can contain multiple lines. Remove empty lines.
const lines = text.split("\n").filter(Boolean);
errors.push(...lines);
logger.debug(text);
});
@@ -894,14 +900,15 @@ async function buildAndPushSelfHostedImage(
}
function extractImageDigest(outputs: string[]) {
const imageDigestRegex = /sha256:[a-f0-9]{64}/;
const imageDigestRegex = /pushing manifest for .+(?<digest>sha256:[a-f0-9]{64})/;
for (const line of outputs) {
if (line.includes("pushing manifest")) {
const imageDigestMatch = line.match(imageDigestRegex);
if (imageDigestMatch) {
return imageDigestMatch[0];
}
const imageDigestMatch = line.match(imageDigestRegex);
const digest = imageDigestMatch?.groups?.digest;
if (digest) {
return digest;
}
}
}
@@ -933,27 +940,27 @@ async function compileProject(
const taskFiles = await gatherTaskFiles(config);
const workerFacade = readFileSync(
new URL(importResolve("./workers/prod/worker-facade.js", import.meta.url)).href.replace(
"file://",
""
),
join(cliRootPath(), "workers", "prod", "worker-facade.js"),
"utf-8"
);
const workerSetupPath = new URL(
importResolve("./workers/prod/worker-setup.js", import.meta.url)
).href.replace("file://", "");
const workerSetupPath = join(cliRootPath(), "workers", "dev", "worker-setup.js");
let workerContents = workerFacade
.replace("__TASKS__", createTaskFileImports(taskFiles))
.replace("__WORKER_SETUP__", `import { tracingSDK } from "${workerSetupPath}";`);
.replace(
"__WORKER_SETUP__",
`import { tracingSDK } from "${escapeImportPath(workerSetupPath)}";`
);
if (configPath) {
logger.debug("Importing project config from", { configPath });
workerContents = workerContents.replace(
"__IMPORTED_PROJECT_CONFIG__",
`import * as importedConfigExports from "${configPath}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;`
`import * as importedConfigExports from "${escapeImportPath(
configPath
)}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;`
);
} else {
workerContents = workerContents.replace(
@@ -1009,10 +1016,7 @@ async function compileProject(
}
const entryPointContents = readFileSync(
new URL(importResolve("./workers/prod/entry-point.js", import.meta.url)).href.replace(
"file://",
""
),
join(cliRootPath(), "workers", "prod", "entry-point.js"),
"utf-8"
);
@@ -1070,12 +1074,13 @@ async function compileProject(
logger.debug(`Writing compiled files to ${tempDir}`);
// Get the metaOutput for the result build
const metaOutput = result.metafile!.outputs[join("out", "stdin.js")];
const metaOutput = result.metafile!.outputs[posix.join("out", "stdin.js")];
invariant(metaOutput, "Meta output for the result build is missing");
// Get the metaOutput for the entryPoint build
const entryPointMetaOutput = entryPointResult.metafile!.outputs[join("out", "stdin.js")];
const entryPointMetaOutput =
entryPointResult.metafile!.outputs[posix.join("out", "stdin.js")];
invariant(entryPointMetaOutput, "Meta output for the entryPoint build is missing");
@@ -1118,13 +1123,9 @@ async function compileProject(
// Get all the required dependencies from the metaOutputs and save them to /tmp/dir/package.json
const allImports = [...metaOutput.imports, ...entryPointMetaOutput.imports];
const externalPackageJson = await readJSONFile(join(config.projectDir, "package.json"));
const javascriptProject = new JavascriptProject(config.projectDir);
const dependencies = await gatherRequiredDependencies(
allImports,
config,
externalPackageJson
);
const dependencies = await gatherRequiredDependencies(allImports, config, javascriptProject);
const packageJsonContents = {
name: "trigger-worker",
@@ -1132,7 +1133,7 @@ async function compileProject(
description: "",
dependencies,
scripts: {
postinstall: externalPackageJson?.scripts?.postinstall,
...javascriptProject.scripts,
},
};
@@ -1154,9 +1155,7 @@ async function compileProject(
}
// Write the Containerfile to /tmp/dir/Containerfile
const containerFilePath = new URL(
importResolve("./Containerfile.prod", import.meta.url)
).href.replace("file://", "");
const containerFilePath = join(cliRootPath(), "Containerfile.prod");
// Copy the Containerfile to /tmp/dir/Containerfile
await copyFile(containerFilePath, join(tempDir, "Containerfile"));
@@ -1377,7 +1376,7 @@ async function typecheckProject(config: ResolvedConfig, options: DeployCommandOp
async function gatherRequiredDependencies(
imports: Metafile["outputs"][string]["imports"],
config: ResolvedConfig,
projectPackageJson: any
project: JavascriptProject
) {
const dependencies: Record<string, string> = {};
@@ -1392,7 +1391,7 @@ async function gatherRequiredDependencies(
continue;
}
const externalDependencyVersion = (projectPackageJson?.dependencies ?? {})[packageName];
const externalDependencyVersion = await project.resolve(packageName);
if (externalDependencyVersion) {
dependencies[packageName] = stripWorkspaceFromVersion(externalDependencyVersion);
@@ -1420,10 +1419,9 @@ async function gatherRequiredDependencies(
dependencies[packageParts.name] = packageParts.version;
continue;
} else {
const externalDependencyVersion = {
...projectPackageJson?.devDependencies,
...projectPackageJson?.dependencies,
}[packageName];
const externalDependencyVersion = await project.resolve(packageParts.name, {
allowDev: true,
});
if (externalDependencyVersion) {
dependencies[packageParts.name] = externalDependencyVersion;
+27 -19
View File
@@ -11,12 +11,11 @@ import {
import { watch } from "chokidar";
import { Command } from "commander";
import { BuildContext, Metafile, context } from "esbuild";
import { resolve as importResolve } from "import-meta-resolve";
import { render, useInput } from "ink";
import { createHash } from "node:crypto";
import fs, { readFileSync } from "node:fs";
import { ClientRequestArgs } from "node:http";
import { basename, dirname, join } from "node:path";
import { basename, dirname, join, normalize } from "node:path";
import pDebounce from "p-debounce";
import { WebSocket } from "partysocket";
import React, { Suspense, useEffect } from "react";
@@ -52,6 +51,8 @@ import {
parseNpmInstallError,
} from "../utilities/deployErrors";
import { findUp, pathExists } from "find-up";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { escapeImportPath } from "../utilities/windows";
let apiClient: CliApiClient | undefined;
@@ -112,7 +113,7 @@ export async function devCommand(dir: string, options: DevCommandOptions) {
return;
}
const devInstance = await startDev(dir, options, authorization.auth);
const devInstance = await startDev(dir, options, authorization.auth, authorization.dashboardUrl);
const { waitUntilExit } = devInstance.devReactElement;
await waitUntilExit();
}
@@ -120,7 +121,8 @@ export async function devCommand(dir: string, options: DevCommandOptions) {
async function startDev(
dir: string,
options: DevCommandOptions,
authorization: { apiUrl: string; accessToken: string }
authorization: { apiUrl: string; accessToken: string },
dashboardUrl: string
) {
let rerender: (node: React.ReactNode) => void | undefined;
@@ -174,6 +176,7 @@ async function startDev(
return (
<DevUI
dashboardUrl={dashboardUrl}
config={configParam}
apiUrl={apiUrl}
apiKey={devEnv.data.apiKey}
@@ -209,6 +212,7 @@ async function startDev(
type DevProps = {
config: ResolvedConfig;
dashboardUrl: string;
apiUrl: string;
apiKey: string;
environmentClient: CliApiClient;
@@ -220,6 +224,7 @@ type DevProps = {
function useDev({
config,
dashboardUrl,
apiUrl,
apiKey,
environmentClient,
@@ -251,7 +256,7 @@ function useDev({
});
const backgroundWorkerCoordinator = new BackgroundWorkerCoordinator(
`${apiUrl}/projects/v3/${config.project}`
`${dashboardUrl}/projects/v3/${config.project}`
);
websocket.addEventListener("open", async (event) => {});
@@ -334,28 +339,27 @@ function useDev({
const taskFiles = await gatherTaskFiles(config);
const workerFacade = readFileSync(
new URL(importResolve("./workers/dev/worker-facade.js", import.meta.url)).href.replace(
"file://",
""
),
"utf-8"
);
const workerFacadePath = join(cliRootPath(), "workers", "dev", "worker-facade.js");
const workerFacade = readFileSync(workerFacadePath, "utf-8");
const workerSetupPath = new URL(
importResolve("./workers/dev/worker-setup.js", import.meta.url)
).href.replace("file://", "");
const workerSetupPath = join(cliRootPath(), "workers", "dev", "worker-setup.js");
let entryPointContents = workerFacade
.replace("__TASKS__", createTaskFileImports(taskFiles))
.replace("__WORKER_SETUP__", `import { tracingSDK, sender } from "${workerSetupPath}";`);
.replace(
"__WORKER_SETUP__",
`import { tracingSDK, sender } from "${escapeImportPath(workerSetupPath)}";`
);
if (configPath) {
configPath = normalize(configPath);
logger.debug("Importing project config from", { configPath });
entryPointContents = entryPointContents.replace(
"__IMPORTED_PROJECT_CONFIG__",
`import * as importedConfigExports from "${configPath}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;`
`import * as importedConfigExports from "${escapeImportPath(
configPath
)}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;`
);
} else {
entryPointContents = entryPointContents.replace(
@@ -410,7 +414,11 @@ function useDev({
logger.log(chalkGrey("○ Building background worker…"));
}
const metaOutputKey = join("out", `stdin.js`);
const metaOutputKey = join("out", `stdin.js`).replace(/\\/g, "/");
logger.debug("Metafile", {
metafileOutputs: JSON.stringify(result.metafile?.outputs),
});
const metaOutput = result.metafile!.outputs[metaOutputKey];
@@ -561,7 +569,7 @@ function useDev({
} else if (e instanceof UncaughtExceptionError) {
const parsedBuildError = parseBuildErrorStack(e.originalError);
if (typeof parsedBuildError !== "string") {
if (parsedBuildError && typeof parsedBuildError !== "string") {
logESMRequireError(
parsedBuildError,
configPath
+9 -6
View File
@@ -1,4 +1,4 @@
import { intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
import { intro, isCancel, log, outro, select, text } from "@clack/prompts";
import { context, trace } from "@opentelemetry/api";
import {
GetProjectResponseBody,
@@ -30,8 +30,9 @@ import { createFile, pathExists, readFile } from "../utilities/fileSystem";
import { getUserPackageManager } from "../utilities/getUserPackageManager";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import { logger } from "../utilities/logger";
import { resolveInternalFilePath } from "../utilities/resolveInternalFilePath";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { login } from "./login";
import { spinner } from "../utilities/windows";
const InitCommandOptions = CommonCommandOptions.extend({
projectRef: z.string().optional(),
@@ -185,7 +186,7 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
async function createTriggerDir(dir: string, options: InitCommandOptions) {
return await tracer.startActiveSpan("createTriggerDir", async (span) => {
try {
const defaultValue = `${dir}/src/trigger`;
const defaultValue = join(dir, "src", "trigger");
const location = await text({
message: "Where would you like to create the Trigger.dev directory?",
@@ -199,6 +200,8 @@ async function createTriggerDir(dir: string, options: InitCommandOptions) {
const triggerDir = resolve(process.cwd(), location);
logger.debug({ triggerDir });
span.setAttributes({
"cli.triggerDir": triggerDir,
});
@@ -239,11 +242,11 @@ async function createTriggerDir(dir: string, options: InitCommandOptions) {
return { location, isCustomValue: location !== defaultValue };
}
const exampleFile = resolveInternalFilePath(`./templates/examples/${example}.ts.template`);
const templatePath = join(cliRootPath(), "templates", "examples", `${example}.ts.template`);
const outputPath = join(triggerDir, "example.ts");
await createFileFromTemplate({
templatePath: exampleFile,
templatePath,
outputPath,
replacements: {},
});
@@ -440,7 +443,7 @@ async function writeConfigFile(
spnnr.start("Creating config file");
const projectDir = resolve(process.cwd(), dir);
const templatePath = resolveInternalFilePath("./templates/trigger.config.ts.template");
const templatePath = join(cliRootPath(), "templates", "trigger.config.ts.template");
const outputPath = join(projectDir, "trigger.config.ts");
span.setAttributes({
+2 -1
View File
@@ -1,4 +1,4 @@
import { intro, log, outro, select, spinner } from "@clack/prompts";
import { intro, log, outro, select } from "@clack/prompts";
import { recordSpanException } from "@trigger.dev/core/v3";
import { Command } from "commander";
import open from "open";
@@ -20,6 +20,7 @@ import { printInitialBanner } from "../utilities/initialBanner.js";
import { LoginResult } from "../utilities/session.js";
import { whoAmI } from "./whoami.js";
import { logger } from "../utilities/logger.js";
import { spinner } from "../utilities/windows.js";
export const LoginCommandOptions = CommonCommandOptions.extend({
apiUrl: z.string(),
+2 -1
View File
@@ -1,10 +1,11 @@
import { confirm, spinner } from "@clack/prompts";
import { confirm } from "@clack/prompts";
import { RunOptions, run } from "npm-check-updates";
import path from "path";
import { z } from "zod";
import { chalkError, chalkSuccess } from "../utilities/cliOutput.js";
import { readJSONFileSync, writeJSONFile } from "../utilities/fileSystem.js";
import { installDependencies } from "../utilities/installDependencies.js";
import { spinner } from "../utilities/windows.js";
export const UpdateCommandOptionsSchema = z.object({
to: z.string().optional(),
+2 -1
View File
@@ -1,4 +1,4 @@
import { intro, note, spinner } from "@clack/prompts";
import { intro, note } from "@clack/prompts";
import { chalkLink } from "../utilities/cliOutput.js";
import { logger } from "../utilities/logger.js";
import { isLoggedIn } from "../utilities/session.js";
@@ -12,6 +12,7 @@ import {
} from "../cli/common.js";
import { z } from "zod";
import { CliApiClient } from "../apiClient.js";
import { spinner } from "../utilities/windows.js";
type WhoAmIResult =
| {
+4 -1
View File
@@ -4,6 +4,7 @@ import { readFileSync } from "node:fs";
import { extname, isAbsolute } from "node:path";
import tsConfigPaths from "tsconfig-paths";
import { logger } from "./logger";
import { escapeImportPath } from "./windows";
export function bundleTriggerDevCore(buildIdentifier: string, tsconfigPath?: string): Plugin {
return {
@@ -56,7 +57,9 @@ export function workerSetupImportConfigPlugin(configPath?: string): Plugin {
workerSetupContents = workerSetupContents.replace(
"__SETUP_IMPORTED_PROJECT_CONFIG__",
`import * as setupImportedConfigExports from "${configPath}"; const setupImportedConfig = setupImportedConfigExports.config;`
`import * as setupImportedConfigExports from "${escapeImportPath(
configPath
)}"; const setupImportedConfig = setupImportedConfigExports.config;`
);
logger.debug("Loading worker setup", {
+12 -4
View File
@@ -164,9 +164,13 @@ export async function readConfig(
// import the config file
const userConfigModule = await import(builtConfigFileHref);
// The --project-ref CLI arg will always override the project specified in the config file
const rawConfig = await normalizeConfig(
userConfigModule ? userConfigModule.config : { project: options?.projectRef }
userConfigModule?.config,
options?.projectRef ? { project: options?.projectRef } : undefined
);
const config = Config.parse(rawConfig);
return {
@@ -198,10 +202,14 @@ export async function resolveConfig(path: string, config: Config): Promise<Resol
return config as ResolvedConfig;
}
export async function normalizeConfig(config: any): Promise<any> {
export async function normalizeConfig(config: any, overrides?: Record<string, any>): Promise<any> {
let normalized = config;
if (typeof config === "function") {
config = config();
normalized = await config();
}
return await config;
normalized = { ...normalized, ...overrides };
return normalized;
}
@@ -20,7 +20,7 @@ function errorIsErrorLike(error: unknown): error is Error {
);
}
export function parseBuildErrorStack(error: unknown): BuildError {
export function parseBuildErrorStack(error: unknown): BuildError | undefined {
if (typeof error === "string") {
return error;
}
@@ -48,8 +48,6 @@ export function parseBuildErrorStack(error: unknown): BuildError {
return error.message;
}
}
return "Unknown error";
}
export function logESMRequireError(parsedError: ESMRequireError, resolvedConfig: ReadConfigResult) {
@@ -1,114 +0,0 @@
import { randomUUID } from "crypto";
import { pathExists } from "./fileSystem.js";
import { getUserPackageManager } from "./getUserPackageManager.js";
import * as pathModule from "path";
import { Mock } from "vitest";
vi.mock("path", () => {
const path = {
join: vi.fn().mockImplementation((...paths: string[]) => paths.join("/")),
};
return {
...path,
default: path,
};
});
vi.mock("./fileSystem.ts", () => ({
pathExists: vi.fn().mockResolvedValue(false),
}));
describe(getUserPackageManager.name, () => {
let path: string;
beforeEach(() => {
path = randomUUID();
});
afterEach(() => {
vi.clearAllMocks();
});
afterAll(() => {
vi.restoreAllMocks();
});
describe(`should use ${pathExists.name} to check for package manager artifacts`, () => {
it("should join the path with the artifact name", async () => {
await getUserPackageManager(path);
expect(pathModule.join).toBeCalledWith(path, "yarn.lock");
expect(pathModule.join).toBeCalledWith(path, "pnpm-lock.yaml");
expect(pathModule.join).toBeCalledWith(path, "package-lock.json");
});
it(`should call ${pathExists.name} with the path.join result`, async () => {
const expected = randomUUID();
(pathModule.join as Mock).mockReturnValueOnce(expected);
await getUserPackageManager(path);
expect(pathExists).toBeCalledWith(expected);
});
it('should return "yarn" if yarn.lock exists', async () => {
(pathExists as Mock).mockImplementation((path: string) => path.endsWith("yarn.lock"));
expect(await getUserPackageManager(path)).toBe("yarn");
});
it('should return "pnpm" if pnpm-lock.yaml exists', async () => {
(pathExists as Mock).mockImplementation(async (path: string) =>
path.endsWith("pnpm-lock.yaml")
);
expect(await getUserPackageManager(path)).toBe("pnpm");
});
it('should return "npm" if package-lock.json exists', async () => {
(pathExists as Mock).mockImplementation((path: string) => path.endsWith("package-lock.json"));
expect(await getUserPackageManager(path)).toBe("npm");
});
it('should return "npm" if npm-shrinkwrap.json exists', async () => {
(pathExists as Mock).mockImplementation((path: string) =>
path.endsWith("npm-shrinkwrap.json")
);
expect(await getUserPackageManager(path)).toBe("npm");
});
});
describe(`if doesn't found artifacts, should use process.env.npm_config_user_agent to detect package manager`, () => {
beforeEach(() => {
(pathExists as Mock).mockResolvedValue(false);
});
it('should return "yarn" if process.env.npm_config_user_agent starts with "yarn"', async () => {
process.env.npm_config_user_agent = "yarn";
expect(await getUserPackageManager(path)).toBe("yarn");
});
it('should return "pnpm" if process.env.npm_config_user_agent starts with "pnpm"', async () => {
process.env.npm_config_user_agent = "pnpm";
expect(await getUserPackageManager(path)).toBe("pnpm");
});
it('if doesn\'t start with "yarn" or "pnpm", should return "npm"', async () => {
process.env.npm_config_user_agent = randomUUID();
expect(await getUserPackageManager(path)).toBe("npm");
});
it('should return "npm" if process.env.npm_config_user_agent is not set', async () => {
delete process.env.npm_config_user_agent;
expect(await getUserPackageManager(path)).toBe("npm");
});
});
});
@@ -1,5 +1,4 @@
import pathModule from "path";
import { pathExists } from "./fileSystem.js";
import { findUp } from "find-up";
export type PackageManager = "npm" | "pnpm" | "yarn";
@@ -38,8 +37,8 @@ async function detectPackageManagerFromArtifacts(path: string): Promise<PackageM
];
for (const { name, pm } of packageFiles) {
const exists = await pathExists(pathModule.join(path, name));
if (exists) {
const foundPath = await findUp(name, { cwd: path });
if (typeof foundPath === "string") {
return pm;
}
}
@@ -1,4 +1,3 @@
import { spinner } from "@clack/prompts";
import chalk from "chalk";
import type { Result } from "update-check";
import checkForUpdate from "update-check";
@@ -6,6 +5,7 @@ import pkg from "../../package.json";
import { chalkGrey, chalkRun, chalkTask, chalkWorker, green, logo } from "./cliOutput.js";
import { getVersion } from "./getVersion.js";
import { logger } from "./logger.js";
import { spinner } from "./windows";
export async function printInitialBanner(performUpdateCheck = true) {
const packageVersion = getVersion();
@@ -40,18 +40,18 @@ After installation, run Trigger.dev with \`npx trigger.dev\`.`
export async function printStandloneInitialBanner(performUpdateCheck = true) {
const packageVersion = getVersion();
let text = `\n${logo()} ${chalkGrey("(v3 Developer Preview)")}`;
logger.log(`\n${logo()} ${chalkGrey("(v3 Developer Preview)")}`);
if (performUpdateCheck) {
const maybeNewVersion = await updateCheck();
// Log a slightly more noticeable message if this is a major bump
if (maybeNewVersion !== undefined) {
text = `${text} (update available ${chalk.green(maybeNewVersion)})`;
logger.log(`Update available ${chalk.green(maybeNewVersion)}`);
}
}
logger.log(text + "\n" + chalkGrey("-".repeat(54)));
logger.log(`${chalkGrey("-".repeat(54))}`);
}
export function printDevBanner() {
@@ -1,8 +1,8 @@
import { spinner } from "@clack/prompts";
import chalk from "chalk";
import { execa } from "execa";
import { getUserPackageManager, type PackageManager } from "./getUserPackageManager.js";
import { logger } from "./logger.js";
import { spinner } from "./windows.js";
export async function installDependencies(projectDir: string) {
logger.info("Installing dependencies...");
@@ -0,0 +1,250 @@
import { $ } from "execa";
import { join } from "node:path";
import { readJSONFileSync } from "./fileSystem";
import { logger } from "./logger";
import { PackageManager, getUserPackageManager } from "./getUserPackageManager";
export type ResolveOptions = { allowDev: boolean };
const BuiltInModules = new Set([
"assert",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"dns",
"domain",
"events",
"fs",
"http",
"http2",
"https",
"inspector",
"module",
"net",
"os",
"path",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"repl",
"stream",
"string_decoder",
"timers",
"tls",
"trace_events",
"tty",
"url",
"util",
"v8",
"vm",
"worker_threads",
"zlib",
]);
export class JavascriptProject {
private _packageJson?: any;
private _packageManager?: PackageManager;
constructor(private projectPath: string) {}
private get packageJson() {
if (!this._packageJson) {
this._packageJson = readJSONFileSync(join(this.projectPath, "package.json"));
}
return this._packageJson;
}
public get scripts(): Record<string, string> {
return {
postinstall: this.packageJson.scripts?.postinstall,
};
}
async resolve(packageName: string, options?: ResolveOptions): Promise<string | undefined> {
if (BuiltInModules.has(packageName)) {
return undefined;
}
if (!this._packageManager) {
this._packageManager = await getUserPackageManager(this.projectPath);
}
const packageManager = this._packageManager;
const opts = { allowDev: false, ...options };
const packageJsonVersion = this.packageJson.dependencies?.[packageName];
if (typeof packageJsonVersion === "string") {
return packageJsonVersion;
}
if (opts.allowDev) {
const devPackageJsonVersion = this.packageJson.devDependencies?.[packageName];
if (typeof devPackageJsonVersion === "string") {
return devPackageJsonVersion;
}
}
const command =
packageManager === "npm"
? new NPMCommands()
: packageManager === "pnpm"
? new PNPMCommands()
: new YarnCommands();
try {
const version = await command.resolveDependencyVersion(packageName, {
cwd: this.projectPath,
});
if (version) {
return version;
}
} catch (error) {
logger.debug(`Failed to resolve dependency version using ${command.name}`, {
packageName,
error,
});
}
}
}
type PnpmList = {
name: string;
path: string;
version: string;
private: boolean;
dependencies?: Record<
string,
{
from: string;
version: string;
resolved: string;
path: string;
}
>;
}[];
type PackageManagerOptions = {
cwd?: string;
};
interface PackageManagerCommands {
resolveDependencyVersion(
packageName: string,
options: PackageManagerOptions
): Promise<string | undefined>;
}
class PNPMCommands implements PackageManagerCommands {
get name() {
return "pnpm";
}
async resolveDependencyVersion(
packageName: string,
options: PackageManagerOptions
): Promise<string | undefined> {
const cmd = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
const { stdout } = await $({ cwd: options.cwd })`${cmd} list ${packageName} -r --json`;
const result = JSON.parse(stdout) as PnpmList;
logger.debug(`Resolving ${packageName} version using pnpm`, { result });
// Return the first dependency version that matches the package name
for (const dep of result) {
const dependency = dep.dependencies?.[packageName];
if (dependency) {
return dependency.version;
}
}
}
}
type NpmDependency = {
version: string;
resolved: string;
overridden: boolean;
required?: { version: string };
dependencies?: Record<string, NpmDependency>;
};
type NpmListOutput = {
dependencies: Record<string, NpmDependency>;
};
class NPMCommands implements PackageManagerCommands {
get name() {
return "npm";
}
async resolveDependencyVersion(
packageName: string,
options: PackageManagerOptions
): Promise<string | undefined> {
const cmd = process.platform === "win32" ? "npm.cmd" : "npm";
const { stdout } = await $({ cwd: options.cwd })`${cmd} list ${packageName} --json`;
const output = JSON.parse(stdout) as NpmListOutput;
logger.debug(`Resolving ${packageName} version using npm`, { output });
return this.#recursivelySearchDependencies(output.dependencies, packageName);
}
#recursivelySearchDependencies(
dependencies: Record<string, NpmDependency>,
packageName: string
): string | undefined {
for (const [name, dependency] of Object.entries(dependencies)) {
if (name === packageName) {
return dependency.version;
}
if (dependency.dependencies) {
const result = this.#recursivelySearchDependencies(dependency.dependencies, packageName);
if (result) {
return result;
}
}
}
}
}
class YarnCommands implements PackageManagerCommands {
get name() {
return "yarn";
}
async resolveDependencyVersion(
packageName: string,
options: PackageManagerOptions
): Promise<string | undefined> {
const cmd = process.platform === "win32" ? "yarn.cmd" : "yarn";
const { stdout } = await $({ cwd: options.cwd })`${cmd} info ${packageName} --json`;
const lines = stdout.split("\n");
logger.debug(`Resolving ${packageName} version using yarn`, { lines });
for (const line of lines) {
const json = JSON.parse(line);
if (json.value === packageName) {
return json.children.Version;
}
}
}
}
@@ -1,171 +0,0 @@
import { $ } from "execa";
import jsonlines from "jsonlines";
import { getUserPackageManager } from "./getUserPackageManager";
import { keyValueBy } from "./keyValueBy";
export async function listPackageDependencies(
path: string,
tag: string | undefined = undefined
): Promise<Record<string, string | undefined>> {
const packageManager = await getPackageManagerCommands(path);
const list = await packageManager.list({ cwd: path });
return Object.keys(list).reduce(
(acc, dependency) => {
const version = list[dependency];
if (!version) {
return acc;
}
if (dependency.startsWith("@trigger.dev/") && version.startsWith("link:")) {
acc[dependency] = tag ?? "latest";
} else {
acc[dependency] = version;
}
return acc;
},
{} as Record<string, string | undefined>
);
}
type PnpmList = {
path: string;
private: boolean;
dependencies: Record<
string,
{
from: string;
version: string;
resolved: string;
}
>;
}[];
async function getPackageManagerCommands(path: string): Promise<PackageManagerCommands> {
const packageManager = await getUserPackageManager(path);
switch (packageManager) {
case "npm":
return new NPMCommands();
case "pnpm":
return new PNPMCommands();
case "yarn":
return new YarnCommands();
}
}
type ListOptions = {
cwd?: string;
};
interface PackageManagerCommands {
list(options: ListOptions): Promise<Record<string, string | undefined>>;
}
class PNPMCommands implements PackageManagerCommands {
async list(options: ListOptions): Promise<Record<string, string | undefined>> {
const cmd = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
const { stdout } = await $({ cwd: options.cwd })`${cmd} ls --depth 1 --json --long`;
const result = JSON.parse(stdout) as PnpmList;
const list = keyValueBy(result[0]?.dependencies ?? {}, (name, { version }) => ({
[name]: version,
}));
return list;
}
}
class NPMCommands implements PackageManagerCommands {
async list(options: ListOptions): Promise<Record<string, string | undefined>> {
const cmd = process.platform === "win32" ? "npm.cmd" : "npm";
const { stdout } = await $({ cwd: options.cwd })`${cmd} ls --depth=0 --json`;
const dependencies = (
JSON.parse(stdout) as {
dependencies: Record<string, { version?: string; required?: { version: string } }>;
}
).dependencies;
return keyValueBy(dependencies, (name, info) => ({
// unmet peer dependencies have a different structure
[name]: info.version || info.required?.version,
}));
}
}
interface YarnParsedDep {
version: string;
from: string;
required?: {
version: string;
};
}
class YarnCommands implements PackageManagerCommands {
async list(options: ListOptions): Promise<Record<string, string | undefined>> {
const cmd = process.platform === "win32" ? "yarn.cmd" : "yarn";
const { stdout } = await $`${cmd} list --depth=0 --json --no-progress`;
const json: { dependencies: Record<string, YarnParsedDep> } = await this.#parseJsonLines(
stdout
);
const keyValues: Record<string, string | undefined> = keyValueBy<
YarnParsedDep,
string | undefined
>(json.dependencies, (name, info): { [key: string]: string | undefined } => ({
// unmet peer dependencies have a different structure
[name]: info.version || info.required?.version,
}));
return keyValues;
}
/**
* Parse JSON lines and throw an informative error on failure.
*
* Note: although this is similar to the NPM parseJson() function we always return the
* same concrete-type here, for now.
*
* @param result Output from `yarn list --json` to be parsed
*/
#parseJsonLines(result: string): Promise<{ dependencies: Record<string, YarnParsedDep> }> {
return new Promise((resolve, reject) => {
const dependencies: Record<string, YarnParsedDep> = {};
const parser = jsonlines.parse();
parser.on("data", (d) => {
// only parse info data
// ignore error info, e.g. "Visit https://yarnpkg.com/en/docs/cli/list for documentation about this command."
if (d.type === "info" && !d.data.match(/^Visit/)) {
// parse package name and version number from info data, e.g. "nodemon@2.0.4" has binaries
const [, pkgName, pkgVersion] = d.data.match(/"(@?.*)@(.*)"/) || [];
dependencies[pkgName] = {
version: pkgVersion,
from: pkgName,
};
} else if (d.type === "error") {
reject(new Error(d.data));
}
});
parser.on("end", () => {
resolve({ dependencies });
});
parser.on("error", reject);
parser.write(result);
parser.end();
});
}
}
@@ -1,5 +1,8 @@
import { resolve as importResolve } from "import-meta-resolve";
import path from "path";
import { fileURLToPath } from "url";
export function resolveInternalFilePath(filePath: string): string {
return new URL(importResolve(filePath, import.meta.url)).href.replace("file://", "");
export function cliRootPath() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
return __dirname;
}
+14 -4
View File
@@ -24,13 +24,23 @@ export async function gatherTaskFiles(config: ResolvedConfig): Promise<Array<Tas
const files = await fs.promises.readdir(triggerDir, { withFileTypes: true });
for (const file of files) {
if (!file.isFile()) continue;
if (!file.name.endsWith(".js") && !file.name.endsWith(".ts")) continue;
if (
!file.name.endsWith(".js") &&
!file.name.endsWith(".ts") &&
!file.name.endsWith(".jsx") &&
!file.name.endsWith(".tsx")
) {
continue;
}
const fullPath = join(triggerDir, file.name);
const filePath = relative(config.projectDir, fullPath);
const importPath = filePath.replace(/\.(js|ts)$/, "");
const importName = importPath.replace(/\//g, "_").replace(/\./g, "_").replace(/-/g, "_");
//remove the file extension and replace any invalid characters with underscores
const importName = filePath.replace(/\..+$/, "").replace(/[^a-zA-Z0-9_$]/g, "_");
//change backslashes to forward slashes
const importPath = filePath.replace(/\\/g, "/");
taskFiles.push({ triggerDir, importPath, importName, filePath });
}
+23
View File
@@ -0,0 +1,23 @@
import { log, spinner as clackSpinner } from "@clack/prompts";
export const isWindows = process.platform === "win32";
export function escapeImportPath(path: string) {
return isWindows ? path.replaceAll("\\", "\\\\") : path;
}
const ballmerSpinner = () => ({
start: (msg?: string): void => {
log.step(msg ?? "");
},
stop: (msg?: string, code?: number): void => {
log.message(msg ?? "");
},
message: (msg?: string): void => {
log.message(msg ?? "");
},
});
// This will become unecessary with the next clack release, the bug was fixed here:
// https://github.com/natemoo-re/clack/pull/182
export const spinner = () => (isWindows ? ballmerSpinner() : clackSpinner());
+23 -15
View File
@@ -417,7 +417,10 @@ class ProdWorker {
}
} catch (e) {
if (e instanceof TaskMetadataParseError) {
logger.error("tasks metadata parse error", { message: e.zodIssues, tasks: e.tasks });
logger.error("tasks metadata parse error", {
zodIssues: e.zodIssues,
tasks: e.tasks,
});
socket.emit("INDEXING_FAILED", {
version: "v1",
@@ -429,31 +432,35 @@ class ProdWorker {
},
});
} else if (e instanceof UncaughtExceptionError) {
logger.error("uncaught exception", { message: e.originalError.message });
const error = {
name: e.originalError.name,
message: e.originalError.message,
stack: e.originalError.stack,
};
logger.error("uncaught exception", { originalError: error });
socket.emit("INDEXING_FAILED", {
version: "v1",
deploymentId: this.deploymentId,
error: {
name: e.originalError.name,
message: e.originalError.message,
stack: e.originalError.stack,
},
error,
});
} else if (e instanceof Error) {
logger.error("error", { message: e.message });
const error = {
name: e.name,
message: e.message,
stack: e.stack,
};
logger.error("error", { error });
socket.emit("INDEXING_FAILED", {
version: "v1",
deploymentId: this.deploymentId,
error: {
name: e.name,
message: e.message,
stack: e.stack,
},
error,
});
} else if (typeof e === "string") {
logger.error("string error", { message: e });
logger.error("string error", { error: { message: e } });
socket.emit("INDEXING_FAILED", {
version: "v1",
@@ -477,7 +484,8 @@ class ProdWorker {
}
await setTimeout(200);
process.exit(1);
// Use exit code 111 so we can ignore those failures in the task monitor
process.exit(111);
}
}
+11 -3
View File
@@ -1,7 +1,8 @@
import { cp } from "fs/promises";
import { join } from "path";
import { defineConfig } from "tsup";
const isDev = process.env.npm_lifecycle_event === "dev";
const copyTemplates = "cp -r src/templates dist";
const isDev = process.env.npm_lifecycle_event === "dev:main"; // This must match the npm script name
export default defineConfig({
clean: false,
@@ -15,7 +16,14 @@ export default defineConfig({
sourcemap: true,
target: "esnext",
outDir: "dist",
onSuccess: isDev ? `${copyTemplates} && node dist/index.js` : copyTemplates,
async onSuccess() {
if (isDev) {
console.debug("Running onSuccess() in dev");
// exec: node dist/index.js
}
await cp(join("src", "templates"), "dist/templates", { recursive: true });
},
banner: {
js: "import { createRequire as createRequireFromMetaUrl } from 'node:module';const require = createRequireFromMetaUrl(import.meta.url);",
},
+14
View File
@@ -1,5 +1,19 @@
# create-trigger
## 3.0.0-beta.12
### Patch Changes
- @trigger.dev/core@3.0.0-beta.12
- @trigger.dev/yalt@3.0.0-beta.12
## 3.0.0-beta.11
### Patch Changes
- @trigger.dev/core@3.0.0-beta.11
- @trigger.dev/yalt@3.0.0-beta.11
## 3.0.0-beta.7
### Patch Changes

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