Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a0368dec3 | |||
| f10f120e55 | |||
| d3a18fbdf6 | |||
| b82a07ad1c | |||
| fa81bf356a | |||
| fec4dc3bef | |||
| a80089c88f | |||
| 63a643b7c9 | |||
| efd970a901 | |||
| 89a5e9f7da | |||
| 404931e224 | |||
| 57bf98307a | |||
| 7551adfeb6 | |||
| 69117e8ba1 | |||
| b580d53b59 | |||
| d81e21d2ec | |||
| 826a64fe6f | |||
| d1849b0ea9 | |||
| 3aa634d2b8 | |||
| 7de3fb01c7 | |||
| 4ae9a3feac | |||
| 5b18298fef | |||
| 7a9bd18ba2 | |||
| 03af44e545 | |||
| 5776257663 | |||
| d3997c9fc6 | |||
| 0676ea9668 | |||
| 7a4721e122 | |||
| a5aed0d139 | |||
| f1480595d5 | |||
| 328947dbfd | |||
| 279717b092 |
@@ -4,7 +4,8 @@
|
||||
"commit": false,
|
||||
"fixed": [
|
||||
[
|
||||
"@trigger.dev/*"
|
||||
"@trigger.dev/*",
|
||||
"trigger.dev"
|
||||
]
|
||||
],
|
||||
"linked": [],
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Don’t swallow some error messages when deploying
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
v3: fix digest extraction
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Use the dashboard url instead of the API url for the View logs link
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@trigger.dev/otlp-importer": patch
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Fix package builds and CLI commands on Windows
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Stop swallowing deployment errors and display them better
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Init command was failing on Windows because of bad template paths
|
||||
Vendored
+1
-1
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
[](https://twitter.com/triggerdotdev)
|
||||
[](https://github.com/triggerdotdev/trigger.dev)
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+2
-19
@@ -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>
|
||||
|
||||
+95
-52
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
+6
-4
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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" 
|
||||
</Tab>
|
||||
<Tab title="Runs list">
|
||||
<Steps>
|
||||
<Step title="Click the action button on a run">
|
||||

|
||||
</Step>
|
||||
<Step title="Click replay"></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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: Don’t 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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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/*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,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", {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);",
|
||||
},
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user