Compare commits

...

24 Commits

Author SHA1 Message Date
Eric Allam ba4f04db37 Fix pnpm lock file 2024-04-24 12:49:06 +01:00
github-actions[bot] d7911892ee chore: Update version for release (beta) (#1058)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-24 12:48:00 +01:00
Eric Allam f888f85a09 Make the update trigger packages not print anything in dev 2024-04-24 12:16:49 +01:00
Eric Allam e9a63a4868 Lock SDK and CLI deps on exact core version 2024-04-24 12:12:24 +01:00
Eric Allam 93ecbc5e05 Fix pnpm lock file 2024-04-24 11:52:12 +01:00
Eric Allam 60a8a5777b Merge pull request #1057 from triggerdotdev/changeset-release/main
chore: Update version for release (beta)
2024-04-24 11:50:13 +01:00
github-actions[bot] ed1cd16753 chore: Update version for release (beta) 2024-04-24 10:43:28 +00:00
Eric Allam 6bb4dd7b6f Fixed messaging when trying to login with an existing invalid access token 2024-04-24 11:41:38 +01:00
nicktrn 0b5a0be807 update CI deploy error 2024-04-24 11:23:20 +01:00
nicktrn 74d1e61e42 v3: fix for fresh CLI logins after CLI token revoke (#1056)
* prettier login command output

* fix for trying to login again after revoking cli token

* changeset

* improve errors when logging in with revoked or invalid token

* fix builds.. again
2024-04-24 10:44:34 +01:00
Eric Allam 12c83a56af Skip e2e github workflows for now because playwright is broken 2024-04-24 10:33:11 +01:00
nicktrn 75a54540a4 use full image refs for init containers 2024-04-24 10:13:28 +01:00
Eric Allam b68012f81c v3: Various fixes for Next.js projects and projects that use v2 and v3 together (#1051)
* Fixes an issue that was treating v2 trigger directories as v3

* Make msw a normal dependency (for now) to fix Module Not Found error in Next.js.

* Extract out all the zod* stuff from core so the SDK does not import it

* Add a changeset

* Fixing typecheck errors in the webapp

* Export the Task and TaskOptions types

* Extract additional exports from core/v3 that aren’t used in the SDK

* Move to our global system from AsyncLocalStorage for the current task context storage

* Update the esbuild core bundling plugin for the new core v3 exports

* Fix v3 CLI telemetry

* Add support for tasks located in subdirectories inside trigger dirs

* Remove the env var check during deploy (too many false negatives)
2024-04-24 10:09:36 +01:00
nicktrn fb83d58703 fix builds 2024-04-24 08:48:32 +01:00
nicktrn 52b2a8289c fix private package installs (#1055) 2024-04-24 07:58:14 +01:00
nicktrn 1e93ec4216 Revert "change action runner location" (resolved)
This reverts commit cb27b7278a.
2024-04-24 07:45:08 +01:00
nicktrn cb27b7278a change action runner location ⬆️⬆️⬇️⬇️⬅️➡️⬅️➡️🅱️🅰️ 2024-04-23 18:13:04 +01:00
Eric Allam e85fc501a6 Fix pnpm lock file 2024-04-23 17:03:53 +01:00
github-actions[bot] 0bfeb0816f chore: Update version for release (beta) (#1054)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-23 16:58:48 +01:00
nicktrn b207601732 v3: semi-automatic package update command (#1052)
* fix package manager detection

* fix cli update check while in beta

* add update command and warn on mismatches

* update fixes and better integration with dev and deploy

* fix for ci

* update docs

* changeset

* be more specific about prompts before updates

* fail on version mismatch during ci

* update github actions docs

* fix package manager copypasta

* remove ncu

* ignore companyicons

* improve warnings and errors
2024-04-23 16:08:46 +01:00
Matt Aitken 3913e57ef4 Trace view fixes and improvements (#1046)
* Query param for span using history.replaceState is working

* When clicking again on a node, don’t collapse it

* Close the span view using the same replacing of the search param

* Conditional rendering of the resize panels was causing the tree view re-rendering and collapsing…

* Live reloading moved to where the parent label is

* Span action bar is now deeper

* WIP on trace view navigation changes with shortcuts

* Shortcuts for expanding and collapsing en masse

* Number keys expand/collapse levels

* Changed duration toggle to a shortcut key

* Option + click expands/collapse at that level

* Option/alt left/right expands/collapse at that level

* Removed unused imports

* Link from the runs table to the specific span

* Latest lockfile

* Sorted imports

* When doing a test link directly to a span

* Replay links to the span

* CLI log links go directly to a span

* Keyboard shortcuts are in a popover if the width is narrow

* If holding alt only collapse level

* Don’t expand the individual node if you’re holding alt
2024-04-22 13:46:08 +01:00
James Ritchie 26f310397a Fixed the grid layout for v2 runs page 2024-04-22 13:06:56 +01:00
github-actions[bot] 0a845767a0 chore: Update version for release (beta) (#1049)
* chore: Update version for release (beta)

* update lockfile

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2024-04-21 15:07:11 +01:00
nicktrn ed2a26c865 v3: fix additionalFiles, uncaught exceptions, and improve errors (#1048)
* fix additionalFiles for paths up the tree

* warn if additionalFiles pattern produced no matches

* improve deploy warnings and errors, fail early

* changeset
2024-04-21 15:03:00 +01:00
181 changed files with 3532 additions and 1386 deletions
+9
View File
@@ -0,0 +1,9 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
- Fix additionalFiles that aren't decendants
- Stop swallowing uncaught exceptions in prod
- Improve warnings and errors, fail early on critical warnings
- New arg to --save-logs even for successful builds
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
v3 CLI update command and package manager detection fix
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Remove the env var check during deploy (too many false negatives)
+9
View File
@@ -0,0 +1,9 @@
---
"@trigger.dev/sdk": patch
---
Make msw a normal dependency (for now) to fix Module Not Found error in Next.js.
It turns out that webpack will "hoist" dynamically imported modules and attempt to resolve them at build time, even though it's an optional peer dep:
https://x.com/maverickdotdev/status/1782465214308319404
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fixes an issue that was treating v2 trigger directories as v3
+12 -1
View File
@@ -44,6 +44,8 @@
"@trigger.dev/yalt": "2.3.18"
},
"changesets": [
"angry-eagles-trade",
"beige-pens-dance",
"breezy-gorillas-mate",
"chilled-hornets-move",
"clean-pianos-listen",
@@ -51,7 +53,9 @@
"cuddly-feet-approve",
"dry-walls-check",
"eight-pumas-float",
"eleven-paws-join",
"few-students-share",
"funny-swans-destroy",
"green-bags-wink",
"khaki-apricots-design",
"khaki-poems-lay",
@@ -63,6 +67,7 @@
"loud-actors-remember",
"many-ligers-pump",
"mighty-camels-joke",
"nasty-jars-pump",
"new-rivers-tell",
"ninety-pets-travel",
"odd-poets-own",
@@ -70,12 +75,16 @@
"poor-flowers-cross",
"rare-roses-float",
"real-planets-stare",
"rich-kangaroos-unite",
"rotten-beers-refuse",
"rotten-dryers-exercise",
"selfish-ducks-sort",
"shaggy-spoons-taste",
"sharp-emus-compare",
"sharp-zebras-serve",
"shiny-coats-cry",
"silly-suits-switch",
"slow-buses-own",
"smart-needles-move",
"smart-olives-eat",
"spicy-lamps-smoke",
@@ -91,6 +100,8 @@
"tidy-dryers-sleep",
"tiny-doors-type",
"tiny-elephants-scream",
"tricky-bulldogs-heal"
"tricky-bulldogs-heal",
"tricky-ladybugs-unite",
"two-pumas-wait"
]
}
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fix a bug where revoking the CLI token would prevent you from ever logging in again with the CLI.
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Add git to prod worker image which fixes private package installs
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
---
Lock SDK and CLI deps on exact core version
+8
View File
@@ -0,0 +1,8 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
"@trigger.dev/cli": patch
---
Move to our global system from AsyncLocalStorage for the current task context storage
+8
View File
@@ -0,0 +1,8 @@
---
"@trigger.dev/sdk": patch
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
Extracting out all the non-SDK related features from the main @trigger.dev/core/v3 export
+6
View File
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/cli": patch
---
Add support for tasks located in subdirectories inside trigger dirs
+3 -3
View File
@@ -27,6 +27,6 @@ jobs:
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
e2e:
uses: ./.github/workflows/e2e.yml
secrets: inherit
# e2e:
# uses: ./.github/workflows/e2e.yml
# secrets: inherit
+4 -4
View File
@@ -49,11 +49,11 @@ jobs:
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
e2e:
uses: ./.github/workflows/e2e.yml
secrets: inherit
# e2e:
# uses: ./.github/workflows/e2e.yml
# secrets: inherit
publish:
needs: [typecheck, units, e2e]
needs: [typecheck, units]
uses: ./.github/workflows/publish-docker.yml
secrets: inherit
+2 -2
View File
@@ -21,8 +21,8 @@
"execa": "^8.0.1",
"nanoid": "^5.0.6",
"prom-client": "^15.1.0",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4"
"socket.io": "4.7.4",
"socket.io-client": "4.7.4"
},
"devDependencies": {
"@types/node": "^18",
+2 -2
View File
@@ -8,9 +8,9 @@ import {
PlatformToCoordinatorMessages,
ProdWorkerSocketData,
ProdWorkerToCoordinatorMessages,
ZodNamespace,
ZodSocketConnection,
} from "@trigger.dev/core/v3";
import { ZodNamespace } from "@trigger.dev/core/v3/zodNamespace";
import { ZodSocketConnection } from "@trigger.dev/core/v3/zodSocket";
import { HttpReply, getTextBody, SimpleLogger } from "@trigger.dev/core-apps";
import { collectDefaultMetrics, register, Gauge } from "prom-client";
+1 -1
View File
@@ -211,7 +211,7 @@ class KubernetesTaskOperations implements TaskOperations {
},
{
name: "populate-taskinfo",
image: "busybox",
image: "docker.io/library/busybox",
command: ["/bin/sh", "-c"],
args: ["printenv COORDINATOR_HOST | tee /etc/taskinfo/coordinator-host"],
env: [
+2 -2
View File
@@ -408,7 +408,7 @@ export class TaskMonitor {
const createNonZeroExitPod = async (name: string, labels?: Record<string, string>) => {
const container = {
name,
image: "busybox",
image: "docker.io/library/busybox",
command: ["sh"],
args: ["-c", "exit 1"],
} satisfies k8s.V1Container;
@@ -419,7 +419,7 @@ export class TaskMonitor {
const createOoDiskPod = async (name: string, labels?: Record<string, string>) => {
const container = {
name,
image: "busybox",
image: "docker.io/library/busybox",
command: ["sh"],
args: [
"-c",
@@ -2,8 +2,14 @@ import { Fragment } from "react";
import { Modifier, ShortcutDefinition } from "~/hooks/useShortcutKeys";
import { cn } from "~/utils/cn";
import { useOperatingSystem } from "./OperatingSystemProvider";
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronUpIcon,
} from "@heroicons/react/20/solid";
const variants = {
export const variants = {
small:
"text-[0.6rem] font-medium min-w-[17px] rounded-[2px] px-1 ml-1 -mr-0.5 grid place-content-center border border-dimmed/40 text-text-dimmed group-hover:text-text-bright/80 group-hover:border-dimmed/60 transition uppercase",
medium:
@@ -23,7 +29,7 @@ export function ShortcutKey({ shortcut, variant, className }: ShortcutKeyProps)
const isMac = platform === "mac";
let relevantShortcut = "mac" in shortcut ? (isMac ? shortcut.mac : shortcut.windows) : shortcut;
const modifiers = relevantShortcut.modifiers ?? [];
const character = keyString(relevantShortcut.key, isMac);
const character = keyString(relevantShortcut.key, isMac, variant);
return (
<span className={cn(variants[variant], className)}>
@@ -35,10 +41,22 @@ export function ShortcutKey({ shortcut, variant, className }: ShortcutKeyProps)
);
}
function keyString(key: String, isMac: boolean) {
function keyString(key: String, isMac: boolean, size: "small" | "medium") {
key = key.toLowerCase();
const className = size === "small" ? "w-2.5 h-4" : "w-3 h-5";
switch (key) {
case "enter":
return isMac ? "↵" : key;
case "arrowdown":
return <ChevronDownIcon className={className} />;
case "arrowup":
return <ChevronUpIcon className={className} />;
case "arrowleft":
return <ChevronLeftIcon className={className} />;
case "arrowright":
return <ChevronRightIcon className={className} />;
default:
return key;
}
@@ -1,10 +1,10 @@
import { VirtualItem, Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
import { motion } from "framer-motion";
import { MutableRefObject, RefObject, useCallback, useEffect, useReducer, useRef } from "react";
import { UnmountClosed } from "react-collapse";
import { cn } from "~/utils/cn";
import { NodeState, NodesState, reducer } from "./reducer";
import { applyFilterToState, concreteStateFromInput, selectedIdFromState } from "./utils";
import { motion } from "framer-motion";
export type TreeViewProps<TData> = {
tree: FlatTree<TData>;
@@ -165,6 +165,11 @@ export type UseTreeStateOutput = {
expandNode: (id: string, scrollToNode?: boolean) => void;
collapseNode: (id: string) => void;
toggleExpandNode: (id: string, scrollToNode?: boolean) => void;
expandAllBelowDepth: (depth: number) => void;
collapseAllBelowDepth: (depth: number) => void;
expandLevel: (level: number) => void;
collapseLevel: (level: number) => void;
toggleExpandLevel: (level: number) => void;
selectFirstVisibleNode: (scrollToNode?: boolean) => void;
selectLastVisibleNode: (scrollToNode?: boolean) => void;
selectNextVisibleNode: (scrollToNode?: boolean) => void;
@@ -333,6 +338,41 @@ export function useTree<TData>({
[state]
);
const expandAllBelowDepth = useCallback(
(depth: number) => {
dispatch({ type: "EXPAND_ALL_BELOW_DEPTH", payload: { tree, depth } });
},
[state]
);
const collapseAllBelowDepth = useCallback(
(depth: number) => {
dispatch({ type: "COLLAPSE_ALL_BELOW_DEPTH", payload: { tree, depth } });
},
[state]
);
const expandLevel = useCallback(
(level: number) => {
dispatch({ type: "EXPAND_LEVEL", payload: { tree, level } });
},
[state]
);
const collapseLevel = useCallback(
(level: number) => {
dispatch({ type: "COLLAPSE_LEVEL", payload: { tree, level } });
},
[state]
);
const toggleExpandLevel = useCallback(
(level: number) => {
dispatch({ type: "TOGGLE_EXPAND_LEVEL", payload: { tree, level } });
},
[state]
);
const getTreeProps = useCallback(() => {
return {
role: "tree",
@@ -368,25 +408,48 @@ export function useTree<TData>({
}
case "Left":
case "ArrowLeft": {
e.preventDefault();
const selected = selectedIdFromState(state.nodes);
if (selected) {
const treeNode = tree.find((node) => node.id === selected);
if (treeNode && treeNode.hasChildren && state.nodes[selected].expanded) {
if (e.altKey) {
if (treeNode && treeNode.hasChildren) {
collapseLevel(treeNode.level);
}
break;
}
const shouldCollapse =
treeNode && treeNode.hasChildren && state.nodes[selected].expanded;
if (shouldCollapse) {
collapseNode(selected);
} else {
selectParentNode(true);
}
}
e.preventDefault();
break;
}
case "Right":
case "ArrowRight": {
e.preventDefault();
const selected = selectedIdFromState(state.nodes);
if (selected) {
const treeNode = tree.find((node) => node.id === selected);
if (e.altKey) {
if (treeNode && treeNode.hasChildren) {
expandLevel(treeNode.level);
}
break;
}
expandNode(selected, true);
}
e.preventDefault();
break;
}
case "Escape": {
@@ -427,6 +490,11 @@ export function useTree<TData>({
expandNode,
collapseNode,
toggleExpandNode,
expandAllBelowDepth,
collapseAllBelowDepth,
expandLevel,
collapseLevel,
toggleExpandLevel,
selectFirstVisibleNode,
selectLastVisibleNode,
selectNextVisibleNode,
@@ -91,6 +91,46 @@ type ToggleExpandNodeAction = {
} & WithScrollToNode;
};
type ExpandAllBelowDepthAction = {
type: "EXPAND_ALL_BELOW_DEPTH";
payload: {
depth: number;
tree: FlatTree<any>;
};
};
type CollapseAllBelowDepthAction = {
type: "COLLAPSE_ALL_BELOW_DEPTH";
payload: {
depth: number;
tree: FlatTree<any>;
};
};
type ExpandLevelAction = {
type: "EXPAND_LEVEL";
payload: {
level: number;
tree: FlatTree<any>;
};
};
type CollapseLevelAction = {
type: "COLLAPSE_LEVEL";
payload: {
level: number;
tree: FlatTree<any>;
};
};
type ToggleExpandLevelAction = {
type: "TOGGLE_EXPAND_LEVEL";
payload: {
level: number;
tree: FlatTree<any>;
};
};
type SelectFirstVisibleNodeAction = {
type: "SELECT_FIRST_VISIBLE_NODE";
payload: {
@@ -135,6 +175,11 @@ export type Action =
| ExpandNodeAction
| CollapseNodeAction
| ToggleExpandNodeAction
| ExpandAllBelowDepthAction
| CollapseAllBelowDepthAction
| ExpandLevelAction
| CollapseLevelAction
| ToggleExpandLevelAction
| SelectFirstVisibleNodeAction
| SelectLastVisibleNodeAction
| SelectNextVisibleNodeAction
@@ -229,6 +274,109 @@ export function reducer(state: TreeState, action: Action): TreeState {
});
}
}
case "EXPAND_ALL_BELOW_DEPTH": {
const nodesToExpand = action.payload.tree.filter(
(n) => n.level >= action.payload.depth && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToExpand.find((n) => n.id === key) ? true : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "COLLAPSE_ALL_BELOW_DEPTH": {
const nodesToCollapse = action.payload.tree.filter(
(n) => n.level >= action.payload.depth && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToCollapse.find((n) => n.id === key) ? false : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "EXPAND_LEVEL": {
const nodesToExpand = action.payload.tree.filter(
(n) => n.level <= action.payload.level && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToExpand.find((n) => n.id === key) ? true : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "COLLAPSE_LEVEL": {
const nodesToCollapse = action.payload.tree.filter(
(n) => n.level === action.payload.level && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToCollapse.find((n) => n.id === key) ? false : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "TOGGLE_EXPAND_LEVEL": {
//first get the first item at that level in the tree. If it is expanded, collapse all nodes at that level
//if it is collapsed, expand all nodes at that level
const nodesAtLevel = action.payload.tree.filter(
(n) => n.level === action.payload.level && n.hasChildren
);
const firstNode = nodesAtLevel[0];
if (!firstNode) {
return state;
}
const currentlyExpanded = state.nodes[firstNode.id]?.expanded ?? true;
const currentVisible = state.nodes[firstNode.id]?.visible ?? true;
if (currentlyExpanded && currentVisible) {
return reducer(state, {
type: "COLLAPSE_LEVEL",
payload: {
level: action.payload.level,
tree: action.payload.tree,
},
});
} else {
return reducer(state, {
type: "EXPAND_LEVEL",
payload: {
level: action.payload.level,
tree: action.payload.tree,
},
});
}
}
case "SELECT_FIRST_VISIBLE_NODE": {
const node = firstVisibleNode(action.payload.tree, state.nodes);
if (node) {
@@ -1,10 +1,16 @@
import { ArrowPathIcon, StopCircleIcon } from "@heroicons/react/20/solid";
import { StopIcon } from "@heroicons/react/24/outline";
import { BeakerIcon, BookOpenIcon, CheckIcon } from "@heroicons/react/24/solid";
import { useLocation } from "@remix-run/react";
import { formatDuration } from "@trigger.dev/core/v3";
import { User } from "@trigger.dev/database";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { useEnvironments } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { RunListAppliedFilters, RunListItem } from "~/presenters/v3/RunListPresenter.server";
import { docsPath, v3RunPath, v3TestPath } from "~/utils/pathBuilder";
import { docsPath, v3RunSpanPath, v3TestPath } from "~/utils/pathBuilder";
import { EnvironmentLabel } from "../../environments/EnvironmentLabel";
import { DateTime } from "../../primitives/DateTime";
import { Paragraph } from "../../primitives/Paragraph";
@@ -14,21 +20,14 @@ import {
TableBlankRow,
TableBody,
TableCell,
TableCellChevron,
TableCellMenu,
TableHeader,
TableHeaderCell,
TableRow,
} from "../../primitives/Table";
import { formatDuration } from "@trigger.dev/core/v3";
import { TaskRunStatusCombo } from "./TaskRunStatus";
import { useEnvironments } from "~/hooks/useEnvironments";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { ArrowPathIcon, StopCircleIcon } from "@heroicons/react/20/solid";
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { CancelRunDialog } from "./CancelRunDialog";
import { useLocation } from "@remix-run/react";
import { ReplayRunDialog } from "./ReplayRunDialog";
import { TaskRunStatusCombo } from "./TaskRunStatus";
type RunsTableProps = {
total: number;
@@ -78,7 +77,7 @@ export function TaskRunsTable({
<BlankState isLoading={isLoading} filters={filters} />
) : (
runs.map((run) => {
const path = v3RunPath(organization, project, run);
const path = v3RunSpanPath(organization, project, run, { spanId: run.spanId });
const usernameForEnv =
currentUser.id !== run.environment.userId ? run.environment.userName : undefined;
return (
@@ -0,0 +1,32 @@
import { useCallback, useState } from "react";
import { useOptimisticLocation } from "./useOptimisticLocation";
import type { Location } from "@remix-run/react";
export function useReplaceLocation() {
const optimisticLocation = useOptimisticLocation();
const [location, setLocation] = useState(optimisticLocation);
const replaceLocation = useCallback((location: Location<any>) => {
const fullPath = location.pathname + location.search + location.hash;
//replace the URL in the browser
history.replaceState(null, "", fullPath);
//update the state (new object in case the same location ref was modified)
const newLocation = { ...location };
setLocation(newLocation);
}, []);
const replaceSearchParam = useCallback(
(key: string, value?: string) => {
const searchParams = new URLSearchParams(location.search);
if (value) {
searchParams.set(key, value);
} else {
searchParams.delete(key);
}
replaceLocation({ ...optimisticLocation, search: "?" + searchParams.toString() });
},
[optimisticLocation, replaceLocation]
);
return { location, replaceLocation, replaceSearchParam };
}
@@ -106,6 +106,7 @@ export class RunListPresenter {
lockedAt: Date | null;
completedAt: Date | null;
isTest: boolean;
spanId: string;
attempts: BigInt;
}[]
>`
@@ -121,6 +122,7 @@ export class RunListPresenter {
tr."lockedAt" AS "lockedAt",
tra."completedAt" AS "completedAt",
tr."isTest" AS "isTest",
tr."spanId" AS "spanId",
COUNT(tra.id) AS attempts
FROM
${sqlDatabaseSchema}."TaskRun" tr
@@ -225,6 +227,7 @@ export class RunListPresenter {
status: run.status,
version: run.version,
taskIdentifier: run.taskIdentifier,
spanId: run.spanId,
attempts: Number(run.attempts),
isReplayable: true,
isCancellable: CANCELLABLE_STATUSES.includes(run.status),
@@ -60,7 +60,7 @@ export default function Page() {
const user = useUser();
return (
<>
<div className="overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
{job.hasIntegrationsRequiringAction && (
<Callout variant="error" to={organizationIntegrationsPath(organization)} className="mb-2">
{simplur`This Job has ${
@@ -96,6 +96,6 @@ export default function Page() {
</div>
)}
</Help>
</>
</div>
);
}
@@ -1,10 +1,13 @@
import {
ArrowsPointingInIcon,
ArrowsPointingOutIcon,
ChevronDownIcon,
ChevronRightIcon,
MagnifyingGlassMinusIcon,
MagnifyingGlassPlusIcon,
} from "@heroicons/react/20/solid";
import { Outlet, useNavigate, useParams, useRevalidator } from "@remix-run/react";
import type { Location } from "@remix-run/react";
import { useParams, useRevalidator } from "@remix-run/react";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Virtualizer } from "@tanstack/react-virtual";
import {
@@ -23,7 +26,7 @@ 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 { Button, 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";
@@ -33,6 +36,7 @@ import {
ResizablePanel,
ResizablePanelGroup,
} from "~/components/primitives/Resizable";
import { ShortcutKey, variants } from "~/components/primitives/ShortcutKey";
import { Slider } from "~/components/primitives/Slider";
import { Switch } from "~/components/primitives/Switch";
import * as Timeline from "~/components/primitives/Timeline";
@@ -45,8 +49,9 @@ import { useDebounce } from "~/hooks/useDebounce";
import { useEventSource } from "~/hooks/useEventSource";
import { useInitialDimensions } from "~/hooks/useInitialDimensions";
import { useOrganization } from "~/hooks/useOrganizations";
import { usePathName } from "~/hooks/usePathName";
import { useProject } from "~/hooks/useProject";
import { useReplaceLocation } from "~/hooks/useReplaceLocation";
import { Shortcut, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { useUser } from "~/hooks/useUser";
import { RunEvent, RunPresenter } from "~/presenters/v3/RunPresenter.server";
import { getResizableRunSettings, setResizableRunSettings } from "~/services/resizablePanel";
@@ -60,6 +65,11 @@ import {
v3RunStreamingPath,
v3RunsPath,
} from "~/utils/pathBuilder";
import { SpanView } from "../resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route";
import { number } from "zod";
import { useHotkeys } from "react-hotkeys-hook";
import { Popover, PopoverArrowTrigger, PopoverContent } from "~/components/primitives/Popover";
import { Header3 } from "~/components/primitives/Headers";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
@@ -82,19 +92,18 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
});
};
function getSpanId(path: string): string | undefined {
const regex = /spans\/([^\/]*)/;
const match = path.match(regex);
return match ? match[1] : undefined;
function getSpanId(location: Location<any>): string | undefined {
const search = new URLSearchParams(location.search);
return search.get("span") ?? undefined;
}
export default function Page() {
const { run, trace, resizeSettings } = useTypedLoaderData<typeof loader>();
const navigate = useNavigate();
const organization = useOrganization();
const pathName = usePathName();
const project = useProject();
const user = useUser();
const { location, replaceSearchParam } = useReplaceLocation();
const selectedSpanId = getSpanId(location);
const usernameForEnv = user.id !== run.environment.userId ? run.environment.userName : undefined;
@@ -133,10 +142,8 @@ export default function Page() {
const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace;
const selectedSpanId = getSpanId(pathName);
const changeToSpan = useDebounce((selectedSpan: string) => {
navigate(v3RunSpanPath(organization, project, run, { spanId: selectedSpan }));
replaceSearchParam("span", selectedSpan);
}, 250);
const revalidator = useRevalidator();
@@ -166,62 +173,47 @@ export default function Page() {
</NavBar>
<PageBody scrollable={false}>
<div className={cn("grid h-full max-h-full grid-cols-1 overflow-hidden")}>
{selectedSpanId === undefined ? (
<TasksTreeView
selectedId={selectedSpanId}
key={events[0]?.id ?? "-"}
events={events}
parentRunFriendlyId={parentRunFriendlyId}
onSelectedIdChanged={(selectedSpan) => {
//instantly close the panel if no span is selected
if (!selectedSpan) {
navigate(v3RunPath(organization, project, run));
return;
}
<ResizablePanelGroup
direction="horizontal"
className="h-full max-h-full"
onLayout={(layout) => {
if (layout.length !== 2) return;
if (!selectedSpanId) return;
setResizableRunSettings(document, layout);
}}
>
<ResizablePanel order={1} minSize={30} defaultSize={resizeSettings.layout?.[0]}>
<TasksTreeView
selectedId={selectedSpanId}
key={events[0]?.id ?? "-"}
events={events}
parentRunFriendlyId={parentRunFriendlyId}
onSelectedIdChanged={(selectedSpan) => {
//instantly close the panel if no span is selected
if (!selectedSpan) {
replaceSearchParam("span");
return;
}
changeToSpan(selectedSpan);
}}
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
/>
) : (
<ResizablePanelGroup
direction="horizontal"
className="h-full max-h-full"
onLayout={(layout) => {
if (layout.length !== 2) return;
setResizableRunSettings(document, layout);
}}
>
<ResizablePanel order={1} minSize={30} defaultSize={resizeSettings.layout?.[0]}>
<TasksTreeView
selectedId={selectedSpanId}
key={events[0]?.id ?? "-"}
events={events}
parentRunFriendlyId={parentRunFriendlyId}
onSelectedIdChanged={(selectedSpan) => {
//instantly close the panel if no span is selected
if (!selectedSpan) {
navigate(v3RunPath(organization, project, run));
return;
}
changeToSpan(selectedSpan);
}}
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
changeToSpan(selectedSpan);
}}
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
/>
</ResizablePanel>
<ResizableHandle withHandle />
{selectedSpanId && (
<ResizablePanel order={2} minSize={30} defaultSize={resizeSettings.layout?.[1]}>
<SpanView
runParam={run.friendlyId}
spanId={selectedSpanId}
closePanel={() => replaceSearchParam("span")}
/>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel order={2} minSize={30} defaultSize={resizeSettings.layout?.[1]}>
<Outlet key={selectedSpanId} />
</ResizablePanel>
</ResizablePanelGroup>
)}
)}
</ResizablePanelGroup>
</div>
</PageBody>
</>
@@ -263,6 +255,9 @@ function TasksTreeView({
getNodeProps,
toggleNodeSelection,
toggleExpandNode,
expandAllBelowDepth,
toggleExpandLevel,
collapseAllBelowDepth,
selectNode,
scrollToNode,
virtualizer,
@@ -286,7 +281,7 @@ function TasksTreeView({
});
return (
<div className="grid h-full grid-rows-[2.5rem_1fr] overflow-hidden">
<div className="grid h-full grid-rows-[2.5rem_1fr_3.25rem] overflow-hidden">
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
<Input
placeholder="Search log"
@@ -297,30 +292,12 @@ function TasksTreeView({
onChange={(e) => setFilterText(e.target.value)}
/>
<div className="flex items-center gap-2">
<LiveReloadingStatus rootSpanCompleted={rootSpanStatus !== "executing"} />
<Switch
variant="small"
label="Errors only"
checked={errorsOnly}
onCheckedChange={(e) => setErrorsOnly(e.valueOf())}
/>
<Switch
variant="small"
label="Show durations"
checked={showDurations}
onCheckedChange={(e) => setShowDurations(e.valueOf())}
/>
<Slider
variant={"tertiary"}
className="w-20"
LeadingIcon={MagnifyingGlassMinusIcon}
TrailingIcon={MagnifyingGlassPlusIcon}
value={[scale]}
onValueChange={(value) => setScale(value[0])}
min={0}
max={1}
step={0.05}
/>
</div>
</div>
<ResizablePanelGroup
@@ -333,14 +310,15 @@ function TasksTreeView({
{/* Tree list */}
<ResizablePanel order={1} minSize={20} defaultSize={50} className="pl-3">
<div className="grid h-full grid-rows-[2rem_1fr] overflow-hidden">
<div className="flex items-center">
<div className="flex items-center pr-2">
{parentRunFriendlyId ? (
<ShowParentLink runFriendlyId={parentRunFriendlyId} />
) : (
<Paragraph variant="small" className="text-charcoal-500">
<Paragraph variant="small" className="flex-1 text-charcoal-500">
This is the root task
</Paragraph>
)}
<LiveReloadingStatus rootSpanCompleted={rootSpanStatus !== "executing"} />
</div>
<TreeView
parentRef={parentRef}
@@ -355,13 +333,13 @@ function TasksTreeView({
<>
<div
className={cn(
"delay-[25ms] flex h-8 cursor-pointer items-center overflow-hidden rounded-l-sm pr-2 transition-colors",
"flex h-8 cursor-pointer items-center overflow-hidden rounded-l-sm pr-2",
state.selected
? "bg-grid-dimmed hover:bg-grid-bright"
: "bg-transparent hover:bg-grid-dimmed"
)}
onClick={() => {
toggleNodeSelection(node.id);
selectNode(node.id);
}}
>
<div className="flex h-8 items-center">
@@ -379,7 +357,15 @@ function TasksTreeView({
)}
onClick={(e) => {
e.stopPropagation();
toggleExpandNode(node.id);
if (e.altKey) {
if (state.expanded) {
collapseAllBelowDepth(node.level);
} else {
expandAllBelowDepth(node.level);
}
} else {
toggleExpandNode(node.id);
}
scrollToNode(node.id);
}}
>
@@ -445,6 +431,50 @@ function TasksTreeView({
/>
</ResizablePanel>
</ResizablePanelGroup>
<div className="flex items-center justify-between gap-2 border-t border-grid-dimmed px-2">
<div className="grow @container">
<div className="hidden items-center gap-4 @[42rem]:flex">
<KeyboardShortcuts
expandAllBelowDepth={expandAllBelowDepth}
collapseAllBelowDepth={collapseAllBelowDepth}
toggleExpandLevel={toggleExpandLevel}
setShowDurations={setShowDurations}
/>
</div>
<div className="@[42rem]:hidden">
<Popover>
<PopoverArrowTrigger>Shortcuts</PopoverArrowTrigger>
<PopoverContent
className="min-w-[20rem] overflow-y-auto p-2 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
align="start"
>
<Header3 spacing>Keyboard shortcuts</Header3>
<div className="flex flex-col gap-2">
<KeyboardShortcuts
expandAllBelowDepth={expandAllBelowDepth}
collapseAllBelowDepth={collapseAllBelowDepth}
toggleExpandLevel={toggleExpandLevel}
setShowDurations={setShowDurations}
/>
</div>
</PopoverContent>
</Popover>
</div>
</div>
<div className="flex items-center gap-4">
<Slider
variant={"tertiary"}
className="w-20"
LeadingIcon={MagnifyingGlassMinusIcon}
TrailingIcon={MagnifyingGlassPlusIcon}
value={[scale]}
onValueChange={(value) => setScale(value[0])}
min={0}
max={1}
step={0.05}
/>
</div>
</div>
</div>
);
}
@@ -738,6 +768,7 @@ function ShowParentLink({ runFriendlyId }: { runFriendlyId: string }) {
fullWidth
textAlignLeft
shortcut={{ key: "p" }}
className="flex-1"
>
{mouseOver ? (
<ShowParentIconSelected className="h-4 w-4 text-indigo-500" />
@@ -884,3 +915,92 @@ function ConnectedDevWarning() {
</div>
);
}
function KeyboardShortcuts({
expandAllBelowDepth,
collapseAllBelowDepth,
toggleExpandLevel,
setShowDurations,
}: {
expandAllBelowDepth: (depth: number) => void;
collapseAllBelowDepth: (depth: number) => void;
toggleExpandLevel: (depth: number) => void;
setShowDurations: (show: (show: boolean) => boolean) => void;
}) {
return (
<>
<ArrowKeyShortcuts />
<ShortcutWithAction
shortcut={{ key: "e" }}
action={() => expandAllBelowDepth(0)}
title="Expand all"
/>
<ShortcutWithAction
shortcut={{ key: "c" }}
action={() => collapseAllBelowDepth(1)}
title="Collapse all"
/>
<NumberShortcuts toggleLevel={(number) => toggleExpandLevel(number)} />
<ShortcutWithAction
shortcut={{ key: "d" }}
action={() => setShowDurations((d) => !d)}
title="Toggle durations"
/>
</>
);
}
function ArrowKeyShortcuts() {
return (
<div className="flex items-center gap-0.5">
<ShortcutKey shortcut={{ key: "arrowup" }} variant="medium" className="ml-0 mr-0" />
<ShortcutKey shortcut={{ key: "arrowdown" }} variant="medium" className="ml-0 mr-0" />
<ShortcutKey shortcut={{ key: "arrowleft" }} variant="medium" className="ml-0 mr-0" />
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium" className="ml-0 mr-0" />
<Paragraph variant="extra-small" className="ml-1.5 whitespace-nowrap">
Navigate
</Paragraph>
</div>
);
}
function ShortcutWithAction({
shortcut,
title,
action,
}: {
shortcut: Shortcut;
title: string;
action: () => void;
}) {
useShortcutKeys({
shortcut,
action,
});
return (
<div className="flex items-center gap-0.5">
<ShortcutKey shortcut={shortcut} variant="medium" className="ml-0 mr-0" />
<Paragraph variant="extra-small" className="ml-1.5 whitespace-nowrap">
{title}
</Paragraph>
</div>
);
}
function NumberShortcuts({ toggleLevel }: { toggleLevel: (depth: number) => void }) {
useHotkeys(["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], (event, hotkeysEvent) => {
toggleLevel(Number(event.key));
});
return (
<div className="flex items-center gap-0.5">
<span className={cn(variants.medium, "ml-0 mr-0")}>0</span>
<span className="text-[0.75rem] text-text-dimmed"></span>
<span className={cn(variants.medium, "ml-0 mr-0")}>9</span>
<Paragraph variant="extra-small" className="ml-1.5 whitespace-nowrap">
Toggle level
</Paragraph>
</div>
);
}
@@ -6,7 +6,6 @@ import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runt
import { TaskRunStatus } from "@trigger.dev/database";
import { useCallback, useEffect, useRef, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { JSONEditor } from "~/components/code/JSONEditor";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { Button } from "~/components/primitives/Buttons";
@@ -37,7 +36,7 @@ import {
TestTaskPresenter,
} from "~/presenters/v3/TestTaskPresenter.server";
import { requireUserId } from "~/services/session.server";
import { docsPath, v3RunPath, v3TaskParamsSchema } from "~/utils/pathBuilder";
import { docsPath, v3RunSpanPath, v3TaskParamsSchema } from "~/utils/pathBuilder";
import { TestTaskService } from "~/v3/services/testTask.server";
import { TestTaskData } from "~/v3/testTask";
@@ -77,7 +76,12 @@ export const action: ActionFunction = async ({ request, params }) => {
}
return redirectWithSuccessMessage(
v3RunPath({ slug: organizationSlug }, { slug: projectParam }, { friendlyId: run.friendlyId }),
v3RunSpanPath(
{ slug: organizationSlug },
{ slug: projectParam },
{ friendlyId: run.friendlyId },
{ spanId: run.spanId }
),
request,
"Test run created"
);
@@ -1,6 +1,6 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { parseBatchTriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { BatchTriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { MAX_BATCH_TRIGGER_ITEMS } from "~/consts";
import { authenticateApiRequest } from "~/services/apiAuth.server";
@@ -46,7 +46,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
// Now parse the request body
const anyBody = await request.json();
const body = parseBatchTriggerTaskRequestBody(anyBody);
const body = BatchTriggerTaskRequestBody.safeParse(anyBody);
if (!body.success) {
return json({ error: "Invalid request body" }, { status: 400 });
@@ -1,6 +1,6 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { parseTriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { TriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
@@ -52,7 +52,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
// Now parse the request body
const anyBody = await request.json();
const body = parseTriggerTaskRequestBody(anyBody);
const body = TriggerTaskRequestBody.safeParse(anyBody);
if (!body.success) {
return json({ error: "Invalid request body" }, { status: 400 });
@@ -2,6 +2,7 @@ import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { requireUserId } from "~/services/session.server";
import { v3RunSpanPath } from "~/utils/pathBuilder";
const ParamsSchema = z.object({
projectRef: z.string(),
@@ -33,8 +34,20 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
return new Response("Not found", { status: 404 });
}
const run = await prisma.taskRun.findUnique({
where: {
friendlyId: validatedParams.runParam,
},
});
if (!run) {
throw new Response("Not found", { status: 404 });
}
// Redirect to the project's runs page
return redirect(
`/orgs/${project.organization.slug}/projects/v3/${project.slug}/runs/${validatedParams.runParam}`
v3RunSpanPath({ slug: project.organization.slug }, { slug: project.slug }, run, {
spanId: run.spanId,
})
);
}
@@ -4,10 +4,11 @@ import {
QueueListIcon,
StopCircleIcon,
} from "@heroicons/react/20/solid";
import { useParams } from "@remix-run/react";
import { useFetcher, useParams } from "@remix-run/react";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { formatDurationNanoseconds, nanosecondsToMilliseconds } from "@trigger.dev/core/v3";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEffect } from "react";
import { typedjson, useTypedFetcher, useTypedLoaderData } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { CodeBlock } from "~/components/code/CodeBlock";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
@@ -17,6 +18,7 @@ import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { Header2 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Property, PropertyTable } from "~/components/primitives/PropertyTable";
import { Spinner } from "~/components/primitives/Spinner";
import { CancelRunDialog } from "~/components/runs/v3/CancelRunDialog";
import { LiveTimer } from "~/components/runs/v3/LiveTimer";
import { ReplayRunDialog } from "~/components/runs/v3/ReplayRunDialog";
@@ -58,19 +60,57 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
return typedjson({ span });
};
export default function Page() {
const {
span: { event },
} = useTypedLoaderData<typeof loader>();
export function SpanView({
runParam,
spanId,
closePanel,
}: {
runParam: string;
spanId: string | undefined;
closePanel: () => void;
}) {
const organization = useOrganization();
const project = useProject();
const { runParam } = useParams();
const fetcher = useTypedFetcher<typeof loader>();
useEffect(() => {
if (spanId === undefined) return;
fetcher.load(
`/resources/orgs/${organization.slug}/projects/v3/${project.slug}/runs/${runParam}/spans/${spanId}`
);
}, [organization.slug, project.slug, runParam, spanId]);
if (spanId === undefined) {
return null;
}
if (fetcher.state !== "idle" || fetcher.data === undefined) {
return (
<div
className={cn(
"grid h-full max-h-full grid-rows-[2.5rem_1fr] overflow-hidden bg-background-bright"
)}
>
<div className="mx-3 flex items-center gap-2 overflow-x-hidden border-b border-grid-dimmed">
<div className="size-4 bg-grid-dimmed" />
<div className="h-6 w-[60%] bg-grid-dimmed" />
</div>
<div className="flex items-center justify-center">
<Spinner />
</div>
</div>
);
}
const {
span: { event },
} = fetcher.data;
return (
<div
className={cn(
"grid h-full max-h-full overflow-hidden bg-background-bright",
event.showActionBar ? "grid-rows-[2.5rem_1fr_2.5rem]" : "grid-rows-[2.5rem_1fr]"
event.showActionBar ? "grid-rows-[2.5rem_1fr_3.25rem]" : "grid-rows-[2.5rem_1fr]"
)}
>
<div className="mx-3 flex items-center justify-between gap-2 overflow-x-hidden border-b border-grid-dimmed">
@@ -85,8 +125,8 @@ export default function Page() {
</Header2>
</div>
{runParam && (
<LinkButton
to={v3RunPath(organization, project, { friendlyId: runParam })}
<Button
onClick={closePanel}
variant="minimal/medium"
LeadingIcon={ExitIcon}
shortcut={{ key: "esc" }}
@@ -188,7 +228,7 @@ export default function Page() {
{ friendlyId: event.runId },
{ spanId: event.spanId }
)}
variant="minimal/small"
variant="minimal/medium"
LeadingIcon={QueueListIcon}
shortcut={{ key: "f" }}
>
@@ -216,7 +256,7 @@ function RunActionButtons({ span }: { span: Span }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="danger/small" LeadingIcon={StopCircleIcon}>
<Button variant="danger/medium" LeadingIcon={StopCircleIcon}>
Cancel run
</Button>
</DialogTrigger>
@@ -236,7 +276,7 @@ function RunActionButtons({ span }: { span: Span }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="tertiary/small" LeadingIcon={ArrowPathIcon}>
<Button variant="tertiary/medium" LeadingIcon={ArrowPathIcon}>
Replay run
</Button>
</DialogTrigger>
@@ -4,7 +4,7 @@ import { z } from "zod";
import { prisma } from "~/db.server";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { logger } from "~/services/logger.server";
import { v3RunPath } from "~/utils/pathBuilder";
import { v3RunSpanPath } from "~/utils/pathBuilder";
import { ReplayTaskRunService } from "~/v3/services/replayTaskRun.server";
const FormSchema = z.object({
@@ -54,12 +54,13 @@ export const action: ActionFunction = async ({ request, params }) => {
);
}
const runPath = v3RunPath(
const runPath = v3RunSpanPath(
{
slug: taskRun.project.organization.slug,
},
{ slug: taskRun.project.slug },
{ friendlyId: newRun.friendlyId }
{ friendlyId: newRun.friendlyId },
{ spanId: newRun.spanId }
);
return redirectWithSuccessMessage(runPath, request, `Replaying run`);
@@ -134,20 +134,27 @@ export async function authenticatePersonalAccessToken(
const hashedToken = hashToken(token);
const personalAccessToken = await prisma.personalAccessToken.update({
const personalAccessToken = await prisma.personalAccessToken.findFirst({
where: {
hashedToken,
revokedAt: null,
},
});
if (!personalAccessToken) {
// The token may have been revoked or is entirely invalid
return;
}
await prisma.personalAccessToken.update({
where: {
id: personalAccessToken.id,
},
data: {
lastAccessedAt: new Date(),
},
});
if (!personalAccessToken) {
return;
}
const decryptedToken = decryptPersonalAccessToken(personalAccessToken);
if (decryptedToken !== token) {
@@ -210,6 +217,18 @@ export async function createPersonalAccessTokenFromAuthorizationCode(
},
});
if (existingCliPersonalAccessToken.revokedAt) {
// re-activate revoked CLI PAT so we can use it again
await prisma.personalAccessToken.update({
where: {
id: existingCliPersonalAccessToken.id,
},
data: {
revokedAt: null,
},
});
}
//we don't return the decrypted token
return {
id: existingCliPersonalAccessToken.id,
+1 -3
View File
@@ -14,8 +14,6 @@ import { Job } from "~/models/job.server";
import type { Organization } from "~/models/organization.server";
import type { Project } from "~/models/project.server";
import { objectToSearchParams } from "./searchParams";
import { ScheduleListFilters } from "~/components/runs/v3/ScheduleFilters";
import { useLocation } from "@remix-run/react";
export type OrgForPath = Pick<Organization, "slug">;
export type ProjectForPath = Pick<Project, "slug">;
@@ -368,7 +366,7 @@ export function v3RunSpanPath(
run: v3RunForPath,
span: v3SpanForPath
) {
return `${v3RunPath(organization, project, run)}/spans/${span.spanId}`;
return `${v3RunPath(organization, project, run)}?span=${span.spanId}`;
}
export function v3TraceSpanPath(
@@ -1,9 +1,5 @@
import {
ZodMessageHandler,
ZodMessageSender,
clientWebsocketMessages,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import { clientWebsocketMessages, serverWebsocketMessages } from "@trigger.dev/core/v3";
import { ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
import { Evt } from "evt";
import { randomUUID } from "node:crypto";
import type { CloseEvent, ErrorEvent, MessageEvent, WebSocket } from "ws";
+6 -1
View File
@@ -5,8 +5,8 @@ import {
PlatformToProviderMessages,
ProviderToPlatformMessages,
SharedQueueToClientMessages,
ZodNamespace,
} from "@trigger.dev/core/v3";
import { ZodNamespace } from "@trigger.dev/core/v3/zodNamespace";
import { Server } from "socket.io";
import { env } from "~/env.server";
import { singleton } from "~/utils/singleton";
@@ -71,6 +71,7 @@ function initializeSocketIOServerInstance() {
function createCoordinatorNamespace(io: Server) {
const coordinator = new ZodNamespace({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
io,
name: "coordinator",
authToken: env.COORDINATOR_SECRET,
@@ -147,6 +148,7 @@ function createCoordinatorNamespace(io: Server) {
function createProviderNamespace(io: Server) {
const provider = new ZodNamespace({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
io,
name: "provider",
authToken: env.PROVIDER_SECRET,
@@ -181,6 +183,7 @@ function createProviderNamespace(io: Server) {
function createSharedQueueConsumerNamespace(io: Server) {
const sharedQueue = new ZodNamespace({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
io,
name: "shared-queue",
authToken: env.PROVIDER_SECRET,
@@ -188,7 +191,9 @@ function createSharedQueueConsumerNamespace(io: Server) {
serverMessages: SharedQueueToClientMessages,
onConnection: async (socket, handler, sender, logger) => {
const sharedSocketConnection = new SharedSocketConnection({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
namespace: sharedQueue.namespace,
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
socket,
logger,
poolSize: env.SHARED_QUEUE_CONSUMER_POOL_SIZE,
@@ -3,9 +3,9 @@ import {
TaskRunExecution,
TaskRunExecutionPayload,
TaskRunExecutionResult,
ZodMessageSender,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
import { BackgroundWorker, BackgroundWorkerTask } from "@trigger.dev/database";
import { z } from "zod";
import { prisma } from "~/db.server";
@@ -8,9 +8,9 @@ import {
TaskRunExecutionResult,
TaskRunFailedExecutionResult,
TaskRunSuccessfulExecutionResult,
ZodMessageSender,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
import {
BackgroundWorker,
BackgroundWorkerTask,
@@ -26,7 +26,6 @@ import { EnvironmentVariablesRepository } from "../environmentVariables/environm
import { generateFriendlyId } from "../friendlyIdentifiers";
import { socketIo } from "../handleSocketIo.server";
import { findCurrentWorkerDeployment } from "../models/workerDeployment.server";
import { CancelAttemptService } from "../services/cancelAttempt.server";
import { RestoreCheckpointService } from "../services/restoreCheckpoint.server";
import { tracer } from "../tracer.server";
@@ -1,4 +1,5 @@
import { CoordinatorToPlatformMessages, InferSocketMessageSchema } from "@trigger.dev/core/v3";
import { CoordinatorToPlatformMessages } from "@trigger.dev/core/v3";
import type { InferSocketMessageSchema } from "@trigger.dev/core/v3/zodSocket";
import type {
CheckpointRestoreEvent,
TaskRunAttemptStatus,
@@ -1,9 +1,9 @@
import {
CoordinatorToPlatformMessages,
InferSocketMessageSchema,
TaskRunExecution,
TaskRunExecutionResult,
} from "@trigger.dev/core/v3";
import type { InferSocketMessageSchema } from "@trigger.dev/core/v3/zodSocket";
import { $transaction, PrismaClientOrTransaction } from "~/db.server";
import { logger } from "~/services/logger.server";
import { marqs } from "~/v3/marqs/index.server";
+4 -5
View File
@@ -1,11 +1,10 @@
import { clientWebsocketMessages, serverWebsocketMessages } from "@trigger.dev/core/v3";
import {
MessageCatalogToSocketIoEvents,
StructuredLogger,
ZodMessageHandler,
ZodMessageSender,
clientWebsocketMessages,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
MessageCatalogToSocketIoEvents,
} from "@trigger.dev/core/v3/zodMessageHandler";
import type { StructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import { Evt } from "evt";
import { randomUUID } from "node:crypto";
import { logger } from "~/services/logger.server";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Logger } from "@trigger.dev/core-backend";
import { ZodMessageCatalogSchema, ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3";
import { ZodMessageCatalogSchema, ZodMessageHandler } from "@trigger.dev/core/v3/zodMessageHandler";
import { Evt } from "evt";
import Redis, { RedisOptions } from "ioredis";
import { z } from "zod";
+1 -1
View File
@@ -150,7 +150,7 @@
"simple-oauth2": "^5.0.0",
"simplur": "^3.0.1",
"slug": "^6.0.0",
"socket.io": "^4.7.4",
"socket.io": "4.7.4",
"socket.io-adapter": "^2.5.4",
"sonner": "^1.0.3",
"sqs-consumer": "^7.4.0",
+1 -1
View File
@@ -6,4 +6,4 @@
"publishConfig": {
"access": "public"
}
}
}
+2
View File
@@ -2,6 +2,8 @@
The CLI `dev` command runs a server for your tasks. It will watches for changes in your `/trigger` directory and communicates with the Trigger.dev platform to register your tasks, perform runs, and send data back and forth.
It can also update your `@trigger.dev/*` packages to prevent version mismatches and failed deploys. You will always be prompted first.
<CodeGroup>
```bash npm
+8 -5
View File
@@ -21,13 +21,16 @@ yarn dlx trigger.dev@beta deploy
</CodeGroup>
<Warning>Will fail in CI if any version mismatches are detected. Ensure everything runs locally first using the [dev](/v3/cli-dev) command and don't bypass the version checks!</Warning>
It performs a few steps to deploy:
1. Typechecks the code.
2. Compiles and bundles the code.
3. Checks that [environment variables](/v3/deploy-environment-variables) are set.
4. Deploys the code to the cloud.
5. Registers the tasks as a new version in the environment (prod by default).
1. Optionally updates packages when running locally.
2. Typechecks the code.
3. Compiles and bundles the code.
4. Checks that [environment variables](/v3/deploy-environment-variables) are set.
5. Deploys the code to the cloud.
6. Registers the tasks as a new version in the environment (prod by default).
## Options
+2
View File
@@ -21,6 +21,8 @@ yarn dlx trigger.dev@beta dev
</CodeGroup>
It will first perform an update check to prevent version mismatches, failed deploys, and other errors. You will always be prompted first.
You will see in the terminal that the server is running and listening for requests. When you run a task, you will see it in the terminal along with a link to view it in the dashboard.
It is worth noting that each task runs in a separate Node process. This means that if you have a long-running task, it will not block other tasks from running.
+18
View File
@@ -5,6 +5,8 @@ description: "You can easily deploy your tasks with GitHub actions."
This simple GitHub action file will deploy you Trigger.dev tasks when new code is pushed to the `main` branch and the `trigger` directory has changes in it.
<Warning>The deploy step will fail if any version mismatches are detected. Please see the [version pinning](/v3/github-actions#version-pinning) section for more details.</Warning>
```yaml .github/workflows/release-trigger.yml
name: Deploy to Trigger.dev
@@ -42,3 +44,19 @@ If you already have a GitHub action file, you can just add the final step "🚀
You need to add the `TRIGGER_ACCESS_TOKEN` secret to your repository. You can create a new access token by going to your profile page and then clicking on the "Personal Access Tokens" tab.
To set it in GitHub go to your repository, click on "Settings", "Secrets and variables" and then "Actions". Add a new secret with the name `TRIGGER_ACCESS_TOKEN` and use the value of your access token.
## Version pinning
The CLI and `@trigger.dev/*` package versions need to be in sync, otherwise there will be errors and unpredictable behavior. Hence, the `deploy` command will automatically fail during CI on any version mismatches.
To ensure a smooth CI experience you can pin the CLI version in the deploy step, like so:
```yaml .github/workflows/release-trigger.yml
- name: 🚀 Deploy Trigger.dev
env:
TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
run: |
npx trigger.dev@3.0.0-beta.16 deploy
```
You should use the version you run locally during dev and manual deploy. The current version is displayed in the banner, but you can also check it by appending `--version` to any command.
+20 -2
View File
@@ -53,7 +53,7 @@ export const config: TriggerConfig = {
## ESM-only packages
We'll let you know when run the CLI dev command if this is a problem. Some packages are ESM-only so they don't work directly from CJS when using Node.js. In that case you need to add them to the `dependenciesToBundle` array in your `trigger.config.ts` file.
We'll let you know when running the CLI dev command if this is a problem. Some packages are ESM-only so they don't work directly from CJS when using Node.js. In that case you need to add them to the `dependenciesToBundle` array in your `trigger.config.ts` file.
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
@@ -92,7 +92,10 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
<Step title="package.json postinstall `prisma generate`">
```json
<CodeGroup>
```json default path
{
"scripts": {
"postinstall": "prisma generate"
@@ -100,6 +103,16 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
}
```
```json custom path
{
"scripts": {
"postinstall": "prisma generate --schema=./custom/path/to/schema.prisma"
}
}
```
</CodeGroup>
Anything you put in `postinstall` will be run as part of the install step. This is how Next.js recommends you set up Prisma anyway.
</Step>
@@ -111,7 +124,12 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
export const config: TriggerConfig = {
//..other stuff
// using the default path
additionalFiles: ["./prisma/schema.prisma"],
// or a custom path, for example in a monorepo
additionalFiles: ["../../custom/path/to/schema.prisma"],
additionalPackages: ["prisma@5.11.0"],
};
```
+1 -19
View File
@@ -171,25 +171,7 @@ async function yourBackendFunction() {
## Upgrading your project
<Steps>
<Step title="Upgrade the v2 Trigger.dev packages">
You can run this command to upgrade all the packages to the beta:
```bash
npx @trigger.dev/cli@beta update --to beta
```
</Step>
<Step title="Follow the v3 quick start">
Follow the [v3 quick start](/v3/quick-start) to get started with v3.
</Step>
</Steps>
Just follow the [v3 quick start](/v3/quick-start) to get started with v3. Our new CLI will take care of the rest.
## Using v2 together with v3
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/airtable
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/airtable",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"airtable": "^0.12.1",
"zod": "3.22.3"
},
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/github
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"zod": "3.22.3"
},
"engines": {
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/linear
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/linear",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"zod": "3.22.3"
},
"engines": {
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/slack
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/openai",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.15"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19"
},
"engines": {
"node": ">=18.0.0"
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/plain
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/plain",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"@team-plain/typescript-sdk": "^2.7.0"
},
"engines": {
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/replicate
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/replicate",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"replicate": "^0.18.1",
"zod": "3.22.3"
},
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/resend
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"resend": "^2.1.0"
},
"engines": {
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/sendgrid
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sendgrid",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.15"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19"
},
"engines": {
"node": ">=16.8.0"
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/shopify
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/shopify",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"zod": "3.22.3"
},
"engines": {
+28
View File
@@ -1,5 +1,33 @@
# @trigger.dev/slack
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/slack",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"zod": "3.22.3"
},
"engines": {
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/stripe
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/stripe",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"stripe": "^12.14.0",
"zod": "3.22.3"
},
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/supabase
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/supabase",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"supabase-management-js": "^1.0.0",
"zod": "3.22.3"
},
+32
View File
@@ -1,5 +1,37 @@
# @trigger.dev/typeform
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
- @trigger.dev/integration-kit@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/typeform",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.15",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.19",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19",
"@typeform/api-client": "^1.8.0",
"zod": "3.22.3"
},
+2 -1
View File
@@ -68,7 +68,8 @@
"pnpm": {
"patchedDependencies": {
"@changesets/assemble-release-plan@5.2.4": "patches/@changesets__assemble-release-plan@5.2.4.patch",
"tsup@8.0.1": "patches/tsup@8.0.1.patch"
"tsup@8.0.1": "patches/tsup@8.0.1.patch",
"engine.io-parser@5.2.2": "patches/engine.io-parser@5.2.2.patch"
}
}
}
+28
View File
@@ -1,5 +1,33 @@
# @trigger.dev/astro
## 3.0.0-beta.19
### Patch Changes
- Updated dependencies [e9a63a486]
- @trigger.dev/sdk@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/astro",
"description": "An Astro-native integration for Trigger.dev background jobs platform",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"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.15"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.19"
},
"devDependencies": {
"astro": "^3.0.12",
+40
View File
@@ -1,5 +1,45 @@
# trigger.dev
## 3.0.0-beta.19
### Patch Changes
- e9a63a486: Lock SDK and CLI deps on exact core version
- @trigger.dev/core@3.0.0-beta.19
## 3.0.0-beta.18
### Patch Changes
- b68012f81: Remove the env var check during deploy (too many false negatives)
- b68012f81: Fixes an issue that was treating v2 trigger directories as v3
- 74d1e61e4: Fix a bug where revoking the CLI token would prevent you from ever logging in again with the CLI.
- 52b2a8289: Add git to prod worker image which fixes private package installs
- b68012f81: Move to our global system from AsyncLocalStorage for the current task context storage
- b68012f81: Extracting out all the non-SDK related features from the main @trigger.dev/core/v3 export
- b68012f81: Add support for tasks located in subdirectories inside trigger dirs
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/core@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- b20760173: v3 CLI update command and package manager detection fix
- @trigger.dev/core@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- ed2a26c86: - Fix additionalFiles that aren't decendants
- Stop swallowing uncaught exceptions in prod
- Improve warnings and errors, fail early on critical warnings
- New arg to --save-logs even for successful builds
- Updated dependencies [ed2a26c86]
- @trigger.dev/core@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "trigger.dev",
"version": "3.0.0-beta.15",
"version": "3.0.0-beta.19",
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -85,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.15",
"@trigger.dev/core": "workspace:3.0.0-beta.19",
"@types/degit": "^2.8.3",
"chalk": "^5.2.0",
"chokidar": "^3.5.3",
@@ -106,7 +106,6 @@
"mock-fs": "^5.2.0",
"nanoid": "^4.0.2",
"node-fetch": "^3.3.0",
"npm-check-updates": "^16.12.2",
"object-hash": "^3.0.0",
"p-debounce": "^4.0.0",
"p-throttle": "^6.1.0",
+1
View File
@@ -5,6 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
busybox \
ca-certificates \
dumb-init \
git \
openssl \
&& rm -rf /var/lib/apt/lists/*
+2 -1
View File
@@ -1,4 +1,5 @@
import { flattenAttributes, recordSpanException } from "@trigger.dev/core/v3";
import { flattenAttributes } from "@trigger.dev/core/v3";
import { recordSpanException } from "@trigger.dev/core/v3/workers";
import { Command } from "commander";
import { z } from "zod";
import { getTracer, provider } from "../telemetry/tracing";
+2
View File
@@ -8,6 +8,7 @@ import { configureWhoamiCommand } from "../commands/whoami.js";
import { COMMAND_NAME } from "../consts.js";
import { getVersion } from "../utilities/getVersion.js";
import { configureListProfilesCommand } from "../commands/list-profiles.js";
import { configureUpdateCommand } from "../commands/update.js";
export const program = new Command();
@@ -23,3 +24,4 @@ configureDeployCommand(program);
configureWhoamiCommand(program);
configureLogoutCommand(program);
configureListProfilesCommand(program);
configureUpdateCommand(program);
+329 -155
View File
@@ -6,8 +6,8 @@ import {
TaskMetadataFailedToParseData,
detectDependencyVersion,
flattenAttributes,
recordSpanException,
} from "@trigger.dev/core/v3";
import { recordSpanException } from "@trigger.dev/core/v3/workers";
import chalk from "chalk";
import { Command, Option as CommandOption } from "commander";
import { Metafile, build } from "esbuild";
@@ -43,7 +43,7 @@ import { logger } from "../utilities/logger.js";
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
import { login } from "./login";
import { Glob } from "glob";
import { Glob, GlobOptions } from "glob";
import type { SetOptional } from "type-fest";
import { bundleDependenciesPlugin, workerSetupImportConfigPlugin } from "../utilities/build";
import { chalkError, chalkPurple, chalkWarning } from "../utilities/cliOutput";
@@ -57,11 +57,12 @@ import { safeJsonParse } from "../utilities/safeJsonParse";
import { JavascriptProject } from "../utilities/javascriptProject";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { escapeImportPath, spinner } from "../utilities/windows";
import { updateTriggerPackages } from "./update";
import { docs, getInTouch } from "../utilities/links";
const DeployCommandOptions = CommonCommandOptions.extend({
skipTypecheck: z.boolean().default(false),
skipDeploy: z.boolean().default(false),
ignoreEnvVarCheck: z.boolean().default(false),
env: z.enum(["prod", "staging"]),
loadImage: z.boolean().default(false),
buildPlatform: z.enum(["linux/amd64", "linux/arm64"]).default("linux/amd64"),
@@ -72,6 +73,8 @@ const DeployCommandOptions = CommonCommandOptions.extend({
projectRef: z.string().optional(),
outputMetafile: z.string().optional(),
apiUrl: z.string().optional(),
saveLogs: z.boolean().default(false),
skipUpdateCheck: z.boolean().default(false),
});
type DeployCommandOptions = z.infer<typeof DeployCommandOptions>;
@@ -88,10 +91,7 @@ export function configureDeployCommand(program: Command) {
"prod"
)
.option("--skip-typecheck", "Whether to skip the pre-build typecheck")
.option(
"--ignore-env-var-check",
"Detected missing environment variables won't block deployment"
)
.option("--skip-update-check", "Skip checking for @trigger.dev package updates")
.option("-c, --config <config file>", "The name of the config file, found at [path]")
.option(
"-p, --project-ref <project ref>",
@@ -122,6 +122,12 @@ export function configureDeployCommand(program: Command) {
"(Coming soon) Specify the tag to use when pushing the image to the registry"
).hideHelp()
)
.addOption(
new CommandOption(
"--ignore-env-var-check",
"(deprecated) Detected missing environment variables won't block deployment"
).hideHelp()
)
.addOption(new CommandOption("-D, --skip-deploy", "Skip deploying the image").hideHelp())
.addOption(
new CommandOption("--load-image", "Load the built image into your local docker").hideHelp()
@@ -140,6 +146,12 @@ export function configureDeployCommand(program: Command) {
"If provided, will save the esbuild metafile for the build to the specified path"
).hideHelp()
)
.addOption(
new CommandOption(
"--save-logs",
"If provided, will save logs even for successful builds"
).hideHelp()
)
.action(async (path, options) => {
await handleTelemetry(async () => {
await printStandloneInitialBanner(true);
@@ -159,6 +171,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
intro("Deploying project");
if (!options.skipUpdateCheck) {
await updateTriggerPackages(dir, { ...options }, true, true);
}
const authorization = await login({
embedded: true,
defaultApiUrl: options.apiUrl,
@@ -224,16 +240,6 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
logger.debug("Compilation result", { compilation });
if (compilation.envVars.length > 0) {
await checkEnvVars(
compilation.envVars ?? [],
resolvedConfig.config,
options,
environmentClient,
authorization.dashboardUrl
);
}
// Step 2: Initialize a deployment on the server (response will have everything we need to build an image)
const deploymentResponse = await environmentClient.initializeDeployment({
contentHash: compilation.contentHash,
@@ -306,25 +312,42 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
const image = await buildImage();
const warnings = checkLogsForWarnings(image.logs);
if (!warnings.ok) {
await failDeploy(
deploymentResponse.data.shortCode,
warnings.summary,
image.logs,
deploymentSpinner,
warnings.warnings,
warnings.errors
);
throw new SkipLoggingError(`Failed to build project image: ${warnings.summary}`);
}
if (!image.ok) {
deploymentSpinner.stop(`Failed to build project.`);
// If there are logs, let's write it out to a temporary file and include the path in the error message
if (image.logs.trim() !== "") {
const logPath = join(await createTempDir(), `build-${deploymentResponse.data.shortCode}.log`);
await writeFile(logPath, image.logs);
logger.log(
`${chalkError("X Error:")} ${image.error}. Full build logs have been saved to ${logPath})`
);
} else {
logger.log(`${chalkError("X Error:")} ${image.error}.`);
}
await failDeploy(
deploymentResponse.data.shortCode,
image.error,
image.logs,
deploymentSpinner,
warnings.warnings
);
throw new SkipLoggingError(`Failed to build project image: ${image.error}`);
}
const preExitTasks = async () => {
printWarnings(warnings.warnings);
if (options.saveLogs) {
const logPath = await saveLogs(deploymentResponse.data.shortCode, image.logs);
log.info(`Build logs have been saved to ${logPath}`);
}
};
const imageReference = options.selfHosted
? `${selfHostedRegistryHost ? `${selfHostedRegistryHost}/` : ""}${image.image}${
image.digest ? `@${image.digest}` : ""
@@ -340,6 +363,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
`Project image built: ${imageReference}. Skipping deployment as requested`
);
await preExitTasks();
throw new SkipCommandError("Skipping deployment as requested");
}
@@ -359,6 +384,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
if (!startIndexingResponse.success) {
deploymentSpinner.stop(`Failed to start indexing: ${startIndexingResponse.error}`);
await preExitTasks();
throw new SkipLoggingError(`Failed to start indexing: ${startIndexingResponse.error}`);
}
@@ -370,12 +397,16 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
if (!finishedDeployment) {
deploymentSpinner.stop(`Deployment failed to complete`);
await preExitTasks();
throw new SkipLoggingError("Deployment failed to complete: unknown issue");
}
if (typeof finishedDeployment === "string") {
deploymentSpinner.stop(`Deployment failed to complete: ${finishedDeployment}`);
await preExitTasks();
throw new SkipLoggingError(`Deployment failed to complete: ${finishedDeployment}`);
}
@@ -386,7 +417,13 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
switch (finishedDeployment.status) {
case "DEPLOYED": {
deploymentSpinner.stop("Deployment completed");
if (warnings.warnings.length > 0) {
deploymentSpinner.stop("Deployment completed with warnings");
} else {
deploymentSpinner.stop("Deployment completed");
}
await preExitTasks();
const taskCount = finishedDeployment.worker?.tasks.length ?? 0;
@@ -417,6 +454,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
logTaskMetadataParseError(parsedError.data.zodIssues, parsedError.data.tasks);
await preExitTasks();
throw new SkipLoggingError(
`Deployment encountered an error: ${finishedDeployment.errorData.name}`
);
@@ -439,6 +478,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
logESMRequireError(parsedError, resolvedConfig);
}
await preExitTasks();
throw new SkipLoggingError(
`Deployment encountered an error: ${finishedDeployment.errorData.name}`
);
@@ -447,87 +488,187 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
`Deployment failed with an unknown error. Please contact eric@trigger.dev for help. ${deploymentLink}`
);
await preExitTasks();
throw new SkipLoggingError("Deployment failed with an unknown error");
}
}
case "CANCELED": {
deploymentSpinner.stop(`Deployment was canceled. ${deploymentLink}`);
await preExitTasks();
throw new SkipLoggingError("Deployment was canceled");
}
case "TIMED_OUT": {
deploymentSpinner.stop(`Deployment timed out. ${deploymentLink}`);
await preExitTasks();
throw new SkipLoggingError("Deployment timed out");
}
}
}
async function checkEnvVars(
envVars: string[],
config: ResolvedConfig,
options: DeployCommandOptions,
environmentClient: CliApiClient,
apiUrl: string
) {
return await tracer.startActiveSpan("detectEnvVars", async (span) => {
try {
span.setAttribute("envVars.check", envVars);
function printErrors(errors?: string[]) {
for (const error of errors ?? []) {
log.error(`${chalkError("Error:")} ${error}`);
}
}
const environmentVariablesSpinner = spinner();
function printWarnings(warnings?: string[]) {
for (const warning of warnings ?? []) {
log.warn(`${chalkWarning("Warning:")} ${warning}`);
}
}
environmentVariablesSpinner.start("Checking environment variables");
const environmentVariables = await environmentClient.getEnvironmentVariables(config.project);
if (!environmentVariables.success) {
environmentVariablesSpinner.stop(`Failed to fetch environment variables, skipping check`);
} else {
// Check to see if all the environment variables in the compilation exist
const missingEnvironmentVariables = envVars.filter(
(envVar) => environmentVariables.data.variables[envVar] === undefined
);
if (missingEnvironmentVariables.length > 0) {
environmentVariablesSpinner.stop(
`Found missing env vars in ${options.env}: ${arrayToSentence(
missingEnvironmentVariables
)}. ${
options.ignoreEnvVarCheck
? "Continuing deployment because of --ignore-env-var-check. "
: "Aborting deployment. "
}${chalk.bgBlueBright(
terminalLink(
"Manage env vars",
`${apiUrl}/projects/v3/${config.project}/environment-variables`
)
)}`
);
span.setAttributes({
"envVars.missing": missingEnvironmentVariables,
});
if (!options.ignoreEnvVarCheck) {
throw new SkipLoggingError("Found missing environment variables");
} else {
span.end();
return;
}
}
environmentVariablesSpinner.stop(`Environment variable check passed`);
}
span.end();
} catch (e) {
recordSpanException(span, e);
span.end();
throw e;
type WarningsCheckReturn =
| {
ok: true;
warnings: string[];
}
});
| {
ok: false;
summary: string;
errors: string[];
warnings: string[];
};
type LogParserOptions = Array<{
regex: RegExp;
message: string;
shouldFail?: boolean;
}>;
// Try to extract useful warnings from logs. Sometimes we may even want to fail the build. This won't work if the step is cached.
function checkLogsForWarnings(logs: string): WarningsCheckReturn {
const warnings: LogParserOptions = [
{
regex: /prisma:warn We could not find your Prisma schema/,
message: `Prisma generate failed to find the default schema. Did you include it in config.additionalFiles? ${terminalLink(
"Config docs",
docs.config.prisma
)}\nCustom schema paths require a postinstall script like this: \`prisma generate --schema=./custom/path/to/schema.prisma\``,
shouldFail: true,
},
];
const errorMessages: string[] = [];
const warningMessages: string[] = [];
let shouldFail = false;
for (const warning of warnings) {
const matches = logs.match(warning.regex);
if (!matches) {
continue;
}
const message = getMessageFromTemplate(warning.message, matches.groups);
if (warning.shouldFail) {
shouldFail = true;
errorMessages.push(message);
} else {
warningMessages.push(message);
}
}
if (shouldFail) {
return {
ok: false,
summary: "Build succeeded with critical warnings. Will not proceed",
warnings: warningMessages,
errors: errorMessages,
};
}
return {
ok: true,
warnings: warningMessages,
};
}
// Try to extract useful error messages from the logs
function checkLogsForErrors(logs: string) {
const errors: LogParserOptions = [
{
regex: /Error: Provided --schema at (?<schema>.*) doesn't exist/,
message: `Prisma generate failed to find the specified schema at "$schema".\nDid you include it in config.additionalFiles? ${terminalLink(
"Config docs",
docs.config.prisma
)}`,
},
{
regex: /sh: 1: (?<packageOrBinary>.*): not found/,
message: `$packageOrBinary not found\n\nIf it's a package: Include it in ${terminalLink(
"config.additionalPackages",
docs.config.prisma
)}\nIf it's a binary: Please ${terminalLink(
"get in touch",
getInTouch
)} and we'll see what we can do!`,
},
];
for (const error of errors) {
const matches = logs.match(error.regex);
if (!matches) {
continue;
}
const message = getMessageFromTemplate(error.message, matches.groups);
log.error(`${chalkError("Error:")} ${message}`);
break;
}
}
function getMessageFromTemplate(template: string, replacer: RegExpMatchArray["groups"]) {
let message = template;
if (replacer) {
for (const [key, value] of Object.entries(replacer)) {
message = message.replaceAll(`$${key}`, value);
}
}
return message;
}
async function saveLogs(shortCode: string, logs: string) {
const logPath = join(await createTempDir(), `build-${shortCode}.log`);
await writeFile(logPath, logs);
return logPath;
}
async function failDeploy(
shortCode: string,
errorSummary: string,
logs: string,
deploymentSpinner: ReturnType<typeof spinner>,
warnings?: string[],
errors?: string[]
) {
deploymentSpinner.stop(`Failed to deploy project`);
// If there are logs, let's write it out to a temporary file and include the path in the error message
if (logs.trim() !== "") {
const logPath = await saveLogs(shortCode, logs);
printWarnings(warnings);
printErrors(errors);
checkLogsForErrors(logs);
outro(`${chalkError("Error:")} ${errorSummary}. Full build logs have been saved to ${logPath}`);
} else {
outro(`${chalkError("Error:")} ${errorSummary}.`);
}
// TODO: Let platform know so it can fail the deploy with an appropriate error
}
// Poll every 1 second for the deployment to finish
@@ -944,7 +1085,7 @@ async function compileProject(
"utf-8"
);
const workerSetupPath = join(cliRootPath(), "workers", "dev", "worker-setup.js");
const workerSetupPath = join(cliRootPath(), "workers", "prod", "worker-setup.js");
let workerContents = workerFacade
.replace("__TASKS__", createTaskFileImports(taskFiles))
@@ -1139,9 +1280,22 @@ async function compileProject(
await writeJSONFile(join(tempDir, "package.json"), packageJsonContents);
await copyAdditionalFiles(config, tempDir);
const copyResult = await copyAdditionalFiles(config, tempDir);
compileSpinner.stop("Project built successfully");
if (!copyResult.ok) {
compileSpinner.stop("Project built with warnings");
log.warn(
`No additionalFiles matches for:\n\n${copyResult.noMatches
.map((glob) => `- "${glob}"`)
.join("\n")}\n\nIf this is unexpected you should check your ${terminalLink(
"glob patterns",
"https://github.com/isaacs/node-glob?tab=readme-ov-file#glob-primer"
)} are valid.`
);
} else {
compileSpinner.stop("Project built successfully");
}
const resolvingDependenciesResult = await resolveDependencies(
tempDir,
@@ -1166,25 +1320,13 @@ async function compileProject(
const contentHash = contentHasher.digest("hex");
const workerSetupEnvVars = await findAllEnvironmentVariableReferencesInFile(workerSetupPath);
const workerFacadeEnvVars = findAllEnvironmentVariableReferences(workerContents);
const envVars = findAllEnvironmentVariableReferences(workerOutputFile.text);
// Remove workerFacadeEnvVars and workerSetupEnvVars from envVars
const finalEnvVars = envVars.filter(
(envVar) => !workerFacadeEnvVars.includes(envVar) && !workerSetupEnvVars.includes(envVar)
);
span.setAttributes({
contentHash: contentHash,
envVars: finalEnvVars,
});
span.end();
return { path: tempDir, contentHash, envVars: finalEnvVars };
return { path: tempDir, contentHash };
} catch (e) {
recordSpanException(span, e);
@@ -1443,11 +1585,24 @@ async function gatherRequiredDependencies(
return Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)));
}
async function copyAdditionalFiles(config: ResolvedConfig, tempDir: string) {
type AdditionalFilesReturn =
| {
ok: true;
}
| {
ok: false;
noMatches: string[];
};
async function copyAdditionalFiles(
config: ResolvedConfig,
tempDir: string
): Promise<AdditionalFilesReturn> {
const additionalFiles = config.additionalFiles ?? [];
const noMatches: string[] = [];
if (additionalFiles.length === 0) {
return;
return { ok: true };
}
return await tracer.startActiveSpan(
@@ -1463,25 +1618,77 @@ async function copyAdditionalFiles(config: ResolvedConfig, tempDir: string) {
additionalFiles,
});
const glob = new Glob(additionalFiles, {
const globOptions = {
withFileTypes: true,
ignore: ["node_modules"],
cwd: config.projectDir,
nodir: true,
});
} satisfies GlobOptions;
for await (const file of glob) {
const relativeDestinationPath = join(
tempDir,
relative(config.projectDir, file.fullpath())
);
const globs: Array<GlobOptions> = [];
let i = 0;
logger.debug(`Copying file ${file.fullpath()} to ${relativeDestinationPath}`);
await mkdir(dirname(relativeDestinationPath), { recursive: true });
await copyFile(file.fullpath(), relativeDestinationPath);
for (const additionalFile of additionalFiles) {
let glob: GlobOptions | Glob<typeof globOptions>;
if (i === 0) {
glob = new Glob(additionalFile, globOptions);
} else {
const previousGlob = globs[i - 1];
if (!previousGlob) {
logger.error("No previous glob, this shouldn't happen", { i, additionalFiles });
continue;
}
// Use the previous glob's options and cache
glob = new Glob(additionalFile, previousGlob);
}
if (!(Symbol.asyncIterator in glob)) {
logger.error("Glob should be an async iterator", { glob });
throw new Error("Unrecoverable error while copying additional files");
}
let matches = 0;
for await (const file of glob) {
matches++;
// Any additional files that aren't a child of projectDir will be moved inside tempDir, so they can be part of the build context
// The file "../foo/bar" will be written to "tempDir/foo/bar"
// The file "../../bar/baz" will be written to "tempDir/bar/baz"
const pathInsideTempDir = relative(config.projectDir, file.fullpath())
.split(posix.sep)
.filter((p) => p !== "..")
.join(posix.sep);
const relativeDestinationPath = join(tempDir, pathInsideTempDir);
logger.debug(`Copying file ${file.fullpath()} to ${relativeDestinationPath}`);
await mkdir(dirname(relativeDestinationPath), { recursive: true });
await copyFile(file.fullpath(), relativeDestinationPath);
}
if (matches === 0) {
noMatches.push(additionalFile);
}
globs[i] = glob;
i++;
}
span.end();
if (noMatches.length > 0) {
return {
ok: false,
noMatches,
} as const;
}
return {
ok: true,
} as const;
} catch (error) {
recordSpanException(span, error);
@@ -1513,36 +1720,3 @@ async function ensureLoggedIntoDockerRegistry(
return tmpDir;
}
async function findAllEnvironmentVariableReferencesInFile(filePath: string) {
const fileContents = await readFile(filePath, "utf-8");
return findAllEnvironmentVariableReferences(fileContents);
}
const IGNORED_ENV_VARS = ["NODE_ENV", "SHELL", "HOME", "PWD", "LOGNAME", "USER", "PATH", "DEBUG"];
function findAllEnvironmentVariableReferences(code: string): string[] {
const regex = /\bprocess\.env\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
const matches = code.matchAll(regex);
const matchesArray = Array.from(matches, (match) => match[1]).filter(Boolean) as string[];
const filteredMatches = matchesArray.filter((match) => !IGNORED_ENV_VARS.includes(match));
// Make sure and remove duplicates
return Array.from(new Set(filteredMatches));
}
function arrayToSentence(items: string[]): string {
if (items.length === 1 && typeof items[0] === "string") {
return items[0];
}
if (items.length === 2) {
return `${items[0]} and ${items[1]}`;
}
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
}
+25 -10
View File
@@ -2,12 +2,11 @@ import {
CreateBackgroundWorkerRequestBody,
ResolvedConfig,
TaskResource,
ZodMessageHandler,
ZodMessageSender,
clientWebsocketMessages,
detectDependencyVersion,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import { ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
import { watch } from "chokidar";
import { Command } from "commander";
import { BuildContext, Metafile, context } from "esbuild";
@@ -53,6 +52,7 @@ import {
import { findUp, pathExists } from "find-up";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { escapeImportPath } from "../utilities/windows";
import { updateTriggerPackages } from "./update";
let apiClient: CliApiClient | undefined;
@@ -61,6 +61,7 @@ const DevCommandOptions = CommonCommandOptions.extend({
debugOtel: z.boolean().default(false),
config: z.string().optional(),
projectRef: z.string().optional(),
skipUpdateCheck: z.boolean().default(false),
});
type DevCommandOptions = z.infer<typeof DevCommandOptions>;
@@ -78,6 +79,7 @@ export function configureDevCommand(program: Command) {
)
.option("--debugger", "Enable the debugger")
.option("--debug-otel", "Enable OpenTelemetry debugging")
.option("--skip-update-check", "Skip checking for @trigger.dev package updates")
).action(async (path, options) => {
wrapCommandAction("dev", DevCommandOptions, options, async (opts) => {
await devCommand(path, opts);
@@ -132,7 +134,13 @@ async function startDev(
}
await printStandloneInitialBanner(true);
printDevBanner();
if (!options.skipUpdateCheck) {
console.log(); // spacing
await updateTriggerPackages(dir, { ...options }, true, true);
}
printDevBanner(!options.skipUpdateCheck);
logger.debug("Starting dev session", { dir, options, authorization });
@@ -416,10 +424,6 @@ function useDev({
const metaOutputKey = join("out", `stdin.js`).replace(/\\/g, "/");
logger.debug("Metafile", {
metafileOutputs: JSON.stringify(result.metafile?.outputs),
});
const metaOutput = result.metafile!.outputs[metaOutputKey];
if (!metaOutput) {
@@ -500,8 +504,15 @@ function useDev({
const taskResources: Array<TaskResource> = [];
if (!backgroundWorker.tasks) {
throw new Error(`Background Worker started without tasks`);
if (!backgroundWorker.tasks || backgroundWorker.tasks.length === 0) {
logger.log(
`${chalkError(
"X Error:"
)} Worker failed to build: no tasks found. Searched in ${config.triggerDirectories.join(
", "
)}`
);
return;
}
for (const task of backgroundWorker.tasks) {
@@ -527,6 +538,10 @@ function useDev({
return;
}
logger.debug("Creating background worker with tasks", {
tasks: taskResources,
});
const backgroundWorkerBody: CreateBackgroundWorkerRequestBody = {
localOnly: true,
metadata: {
@@ -629,7 +644,7 @@ function useDev({
const throttledRebuild = pDebounce(runBuild, 250, { before: true });
const taskFileWatcher = watch(
config.triggerDirectories.map((triggerDir) => `${triggerDir}/*.ts`),
config.triggerDirectories.map((triggerDir) => `${triggerDir}/**/*.ts`),
{
ignoreInitial: true,
}
+2 -5
View File
@@ -1,10 +1,7 @@
import { intro, isCancel, log, outro, select, text } from "@clack/prompts";
import { context, trace } from "@opentelemetry/api";
import {
GetProjectResponseBody,
flattenAttributes,
recordSpanException,
} from "@trigger.dev/core/v3";
import { GetProjectResponseBody, flattenAttributes } from "@trigger.dev/core/v3";
import { recordSpanException } from "@trigger.dev/core/v3/workers";
import chalk from "chalk";
import { Command } from "commander";
import { execa } from "execa";
+16 -4
View File
@@ -1,5 +1,5 @@
import { intro, log, outro, select } from "@clack/prompts";
import { recordSpanException } from "@trigger.dev/core/v3";
import { recordSpanException } from "@trigger.dev/core/v3/workers";
import { Command } from "commander";
import open from "open";
import pRetry, { AbortError } from "p-retry";
@@ -13,7 +13,7 @@ import {
tracer,
wrapCommandAction,
} from "../cli/common.js";
import { chalkLink } from "../utilities/cliOutput.js";
import { chalkLink, prettyError } from "../utilities/cliOutput.js";
import { readAuthConfigProfile, writeAuthConfigProfile } from "../utilities/configFiles.js";
import { getVersion } from "../utilities/getVersion.js";
import { printInitialBanner } from "../utilities/initialBanner.js";
@@ -109,11 +109,23 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
skipTelemetry: !span.isRecording(),
logLevel: logger.loggerLevel,
},
opts.embedded
true
);
if (!whoAmIResult.success) {
throw new Error(whoAmIResult.error);
prettyError("Unable to validate existing personal access token", whoAmIResult.error);
if (!opts.embedded) {
outro(
`Login failed using stored token. To fix, first logout using \`trigger.dev logout${
options?.profile ? ` --profile ${options.profile}` : ""
}\` and then try again.`
);
throw new SkipLoggingError(whoAmIResult.error);
} else {
throw new Error(whoAmIResult.error);
}
} else {
if (!opts.embedded) {
const continueOption = await select({
+285 -133
View File
@@ -1,148 +1,300 @@
import { confirm } from "@clack/prompts";
import { RunOptions, run } from "npm-check-updates";
import path from "path";
import { confirm, intro, isCancel, log, outro } from "@clack/prompts";
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 { readJSONFile, removeFile, writeJSONFile } from "../utilities/fileSystem.js";
import { spinner } from "../utilities/windows.js";
import { CommonCommandOptions, OutroCommandError, wrapCommandAction } from "../cli/common.js";
import { Command } from "commander";
import { logger } from "../utilities/logger.js";
import { PackageJson } from "type-fest";
import { printStandloneInitialBanner, updateCheck } from "../utilities/initialBanner.js";
import { join, resolve } from "path";
import { JavascriptProject } from "../utilities/javascriptProject.js";
import { PackageManager } from "../utilities/getUserPackageManager.js";
import { getVersion } from "../utilities/getVersion.js";
import { chalkError, prettyWarning } from "../utilities/cliOutput.js";
export const UpdateCommandOptionsSchema = z.object({
to: z.string().optional(),
export const UpdateCommandOptions = CommonCommandOptions.pick({
logLevel: true,
skipTelemetry: true,
});
export type UpdateCommandOptions = z.infer<typeof UpdateCommandOptionsSchema>;
export type UpdateCommandOptions = z.infer<typeof UpdateCommandOptions>;
type NcuRunOptionTarget = "latest" | `@${string}`;
export async function updateCommand(projectPath: string, anyOptions: any) {
const loadingSpinner = spinner();
loadingSpinner.start("Checking settings");
const parseRes = UpdateCommandOptionsSchema.safeParse(anyOptions);
if (!parseRes.success) {
loadingSpinner.stop(chalkError(parseRes.error.message));
return;
}
const options = parseRes.data;
const triggerDevPackage = "@trigger.dev";
const packageJSONPath = path.join(projectPath, "package.json");
const packageData = readJSONFileSync(packageJSONPath);
if (!packageData) {
loadingSpinner.stop(chalkError("Couldn't load package.json"));
return;
}
loadingSpinner.message("Checking for updates");
const packageMaps: { [k: string]: { type: string; version: string } } = {};
const packageDependencies = packageData.dependencies || {};
const packageDevDependencies = packageData.devDependencies || {};
Object.keys(packageDependencies).forEach((i) => {
packageMaps[i] = { type: "dependencies", version: packageDependencies[i] };
});
Object.keys(packageDevDependencies).forEach((i) => {
packageMaps[i] = {
type: "devDependencies",
version: packageDevDependencies[i],
};
});
const targetVersion = getTargetVersion(options.to);
// Use npm-check-updates to get updated dependency versions
const ncuOptions: RunOptions = {
packageData,
upgrade: true,
jsonUpgraded: true,
target: targetVersion,
};
// Can either give a json like package.json or just with deps and their new versions
const updatedDependencies: { [k: string]: any } | void = await run(ncuOptions);
if (!updatedDependencies) {
loadingSpinner.stop(chalkError("Couldn't update dependencies"));
return;
}
const ifUpdatedDependenciesIsPackageJSON =
updatedDependencies.hasOwnProperty("dependencies") ||
updatedDependencies.hasOwnProperty("devDependencies");
const dependencies = updatedDependencies.dependencies || {};
const devDependencies = updatedDependencies.devDependencies || {};
const allDependencies = ifUpdatedDependenciesIsPackageJSON
? Object.keys({ ...dependencies, ...devDependencies })
: Object.keys(updatedDependencies);
const triggerPackages = allDependencies.filter((pkg) => pkg.startsWith(triggerDevPackage));
// If there are no @trigger.dev packages
if (triggerPackages.length === 0) {
loadingSpinner.stop(chalkSuccess(`All @trigger.dev/* packages are already up to date.`));
return;
}
// Filter the packages with null and what don't match what
// they are installed with so that they can be updated
const packagesToUpdate = triggerPackages.filter((pkg: string) => updatedDependencies[pkg]);
// If no packages require any updation
if (packagesToUpdate.length === 0) {
loadingSpinner.stop(chalkSuccess(`All @trigger.dev/* packages are already up to date.`));
return;
}
let applyUpdates = targetVersion !== "latest";
if (targetVersion === "latest") {
applyUpdates = await hasUserConfirmed(packagesToUpdate, packageMaps, updatedDependencies);
}
if (applyUpdates) {
const newPackageJSON = packageData;
packagesToUpdate.forEach((packageName) => {
const tmp = packageMaps[packageName];
if (tmp) {
newPackageJSON[tmp.type][packageName] = updatedDependencies[packageName];
}
export function configureUpdateCommand(program: Command) {
return program
.command("update")
.description("Updates all @trigger.dev/* packages to match the CLI version")
.argument("[path]", "The path to the directory that contains the package.json file", ".")
.option(
"-l, --log-level <level>",
"The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
"log"
)
.option("--skip-telemetry", "Opt-out of sending telemetry")
.action(async (path, options) => {
wrapCommandAction("dev", UpdateCommandOptions, options, async (opts) => {
await printStandloneInitialBanner(true);
await updateCommand(path, opts);
});
});
await writeJSONFile(packageJSONPath, newPackageJSON);
await installDependencies(projectPath);
}
}
// expects a version number, or latest.
// if version number is specified, prepend it with '@' for ncu.
function getTargetVersion(toVersion?: string): NcuRunOptionTarget {
if (!toVersion) {
return "latest";
}
return toVersion === "latest" ? "latest" : `@${toVersion}`;
const triggerPackageFilter = /^@trigger\.dev/;
export async function updateCommand(dir: string, options: UpdateCommandOptions) {
await updateTriggerPackages(dir, options);
}
async function hasUserConfirmed(
packagesToUpdate: string[],
packageMaps: { [x: string]: { type: string; version: string } },
updatedDependencies: { [x: string]: any }
): Promise<boolean> {
// Inform the user of the dependencies that can be updated
console.log("\nNewer versions found for the following packages:");
console.table(
packagesToUpdate.map((i) => ({
name: i,
old: packageMaps[i]?.version,
new: updatedDependencies[i],
}))
export async function updateTriggerPackages(
dir: string,
options: UpdateCommandOptions,
embedded?: boolean,
requireUpdate?: boolean
) {
if (!embedded) {
intro("Updating packages");
}
const projectPath = resolve(process.cwd(), dir);
const { packageJson, readonlyPackageJson, packageJsonPath } = await getPackageJson(projectPath);
if (!packageJson) {
log.error("Failed to load package.json. Try to re-run with `-l debug` to see what's going on.");
return;
}
const cliVersion = getVersion();
const newCliVersion = await updateCheck();
if (newCliVersion) {
prettyWarning(
"You're not running the latest CLI version, please consider updating ASAP",
`Current: ${cliVersion}\nLatest: ${newCliVersion}`,
"Run latest: npx trigger.dev@beta"
);
}
const triggerDependencies = getTriggerDependencies(packageJson);
function getVersionMismatches(deps: Dependency[], targetVersion: string): Dependency[] {
const mismatches: Dependency[] = [];
for (const dep of deps) {
if (dep.version === targetVersion) {
continue;
}
mismatches.push(dep);
}
return mismatches;
}
const versionMismatches = getVersionMismatches(triggerDependencies, cliVersion);
if (versionMismatches.length === 0) {
if (!embedded) {
outro(`Nothing to do${newCliVersion ? " ..but you should really update your CLI!" : ""}`);
}
return;
}
prettyWarning(
"Mismatch between your CLI version and installed packages",
"We recommend pinned versions for guaranteed compatibility"
);
// Ask the user if they want to update the dependencies
const shouldContinue = await confirm({
message: "Do you want to update these packages in package.json and re-install dependencies?",
});
if (!process.stdout.isTTY) {
// Running in CI with version mismatch detected
outro("Deploy failed");
return shouldContinue as boolean;
console.log(
`ERROR: Version mismatch detected while running in CI. This won't end well. Aborting.
Please run the dev command locally and check that your CLI version matches the one printed below. Additionally, all \`@trigger.dev/*\` packages also need to match this version.
If your local CLI version doesn't match the one below, you may want to pin the CLI version in this CI step. To do that, just replace \`trigger.dev@beta\` with \`trigger.dev@<FULL_VERSION>\`, for example: \`npx trigger.dev@3.0.0-beta.17 deploy\`
CLI version: ${cliVersion}
Current package versions that don't match the CLI:
${versionMismatches.map((dep) => `- ${dep.name}@${dep.version}`).join("\n")}\n`
);
process.exit(1);
}
log.message(""); // spacing
// Always require user confirmation
const userWantsToUpdate = await updateConfirmation(versionMismatches, cliVersion);
if (isCancel(userWantsToUpdate)) {
throw new OutroCommandError();
}
if (!userWantsToUpdate) {
if (requireUpdate) {
outro("You shall not pass!");
logger.log(
`${chalkError(
"X Error:"
)} Update required: Version mismatches are a common source of bugs and errors. Please update or use \`--skip-update-check\` at your own risk.\n`
);
process.exit(1);
}
if (!embedded) {
outro("You've been warned!");
}
return;
}
const installSpinner = spinner();
installSpinner.start("Writing new package.json file");
// Backup package.json
const packageJsonBackupPath = `${packageJsonPath}.bak`;
await writeJSONFile(packageJsonBackupPath, readonlyPackageJson, true);
const exitHandler = async (sig: any) => {
log.warn(
`You may have to manually roll back any package.json changes. Backup written to ${packageJsonBackupPath}`
);
};
// Add exit handler to warn about manual rollback of package.json
// Automatically rolling back can end up overwriting with an empty file instead
process.prependOnceListener("exit", exitHandler);
// Update package.json
mutatePackageJsonWithUpdatedPackages(packageJson, versionMismatches, cliVersion);
await writeJSONFile(packageJsonPath, packageJson, true);
async function revertPackageJsonChanges() {
await writeJSONFile(packageJsonPath, readonlyPackageJson, true);
await removeFile(packageJsonBackupPath);
}
installSpinner.message("Installing new package versions");
const jsProject = new JavascriptProject(projectPath);
let packageManager: PackageManager | undefined;
try {
packageManager = await jsProject.getPackageManager();
installSpinner.message(`Installing new package versions with ${packageManager}`);
await jsProject.install();
} catch (error) {
installSpinner.stop(
`Failed to install new package versions${packageManager ? ` with ${packageManager}` : ""}`
);
// Remove exit handler in case of failure
process.removeListener("exit", exitHandler);
await revertPackageJsonChanges();
throw error;
}
installSpinner.stop("Installed new package versions");
// Remove exit handler once packages have been updated, also delete backup file
process.removeListener("exit", exitHandler);
await removeFile(packageJsonBackupPath);
if (!embedded) {
outro(
`Packages updated${newCliVersion ? " ..but you should really update your CLI too!" : ""}`
);
}
}
type Dependency = {
type: "dependencies" | "devDependencies";
name: string;
version: string;
};
function getTriggerDependencies(packageJson: PackageJson): Dependency[] {
const deps: Dependency[] = [];
for (const type of ["dependencies", "devDependencies"] as const) {
for (const [name, version] of Object.entries(packageJson[type] ?? {})) {
if (!version) {
continue;
}
if (version.startsWith("workspace")) {
continue;
}
if (!triggerPackageFilter.test(name)) {
continue;
}
const ignoredPackages = ["@trigger.dev/companyicons"];
if (ignoredPackages.includes(name)) {
continue;
}
deps.push({ type, name, version });
}
}
return deps;
}
function mutatePackageJsonWithUpdatedPackages(
packageJson: PackageJson,
depsToUpdate: Dependency[],
targetVersion: string
) {
for (const { type, name, version } of depsToUpdate) {
if (!packageJson[type]) {
throw new Error(
`No ${type} entry found in package.json. Please try to upgrade manually instead.`
);
}
packageJson[type]![name] = targetVersion;
}
}
function printUpdateTable(depsToUpdate: Dependency[], targetVersion: string): void {
log.message("Suggested updates");
const tableData = depsToUpdate.map((dep) => ({
package: dep.name,
old: dep.version,
new: targetVersion,
}));
logger.table(tableData);
}
async function updateConfirmation(depsToUpdate: Dependency[], targetVersion: string) {
printUpdateTable(depsToUpdate, targetVersion);
let confirmMessage = "Would you like to apply those updates?";
return await confirm({
message: confirmMessage,
});
}
export async function getPackageJson(absoluteProjectPath: string) {
const packageJsonPath = join(absoluteProjectPath, "package.json");
const readonlyPackageJson = Object.freeze((await readJSONFile(packageJsonPath)) as PackageJson);
const packageJson = structuredClone(readonlyPackageJson);
return { packageJson, readonlyPackageJson, packageJsonPath };
}
+15 -6
View File
@@ -1,4 +1,4 @@
import { intro, note } from "@clack/prompts";
import { intro, note, outro } from "@clack/prompts";
import { chalkLink } from "../utilities/cliOutput.js";
import { logger } from "../utilities/logger.js";
import { isLoggedIn } from "../utilities/session.js";
@@ -66,11 +66,20 @@ export async function whoAmI(
if (authentication.error === "fetch failed") {
loadingSpinner.stop("Fetch failed. Platform down?");
} else {
loadingSpinner.stop(
`You must login first. Use \`trigger.dev login --profile ${
options?.profile ?? "default"
}\` to login.`
);
if (embedded) {
loadingSpinner.stop(
`Failed to check account details. You may want to run \`trigger.dev logout --profile ${
options?.profile ?? "default"
}\` and try again.`
);
} else {
loadingSpinner.stop(
`You must login first. Use \`trigger.dev login --profile ${
options?.profile ?? "default"
}\` to login.`
);
outro("Whoami failed");
}
}
return {
+9 -4
View File
@@ -5,6 +5,10 @@ import { NodeTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trac
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
import { DiagConsoleLogger, DiagLogLevel, diag, trace } from "@opentelemetry/api";
import * as packageJson from "../../package.json";
import {
SEMRESATTRS_SERVICE_NAME,
SEMRESATTRS_SERVICE_VERSION,
} from "@opentelemetry/semantic-conventions";
function initializeTracing(): NodeTracerProvider | undefined {
if (process.argv.includes("--skip-telemetry") || process.env.TRIGGER_DEV_SKIP_TELEMETRY) {
@@ -19,7 +23,8 @@ function initializeTracing(): NodeTracerProvider | undefined {
detectors: [processDetectorSync],
}).merge(
new Resource({
service: "trigger.dev cli v3",
[SEMRESATTRS_SERVICE_NAME]: "trigger.dev cli v3",
[SEMRESATTRS_SERVICE_VERSION]: packageJson.version,
})
);
@@ -38,9 +43,9 @@ function initializeTracing(): NodeTracerProvider | undefined {
const spanExporter = new OTLPTraceExporter({
url: "https://otel.baselime.io/v1",
timeoutMillis: 500,
timeoutMillis: 5000,
headers: {
"x-api-key": "e9f963244f8b092850d42e34a5339b2d5e68070b".split("").reverse().join(""), // this is a joke
"x-api-key": "b6e0fbbaf8dc2524773d2152ae2e9eb5c7fbaa52",
},
});
@@ -59,5 +64,5 @@ function initializeTracing(): NodeTracerProvider | undefined {
export const provider = initializeTracing();
export function getTracer() {
return trace.getTracer("trigger.dev cli", packageJson.version);
return trace.getTracer("trigger.dev cli v3", packageJson.version);
}
@@ -0,0 +1,3 @@
export function assertExhaustive(x: never): never {
throw new Error("Unexpected object: " + x);
}
+7 -10
View File
@@ -11,7 +11,7 @@ export function bundleTriggerDevCore(buildIdentifier: string, tsconfigPath?: str
name: "trigger-bundle-core",
setup(build) {
build.onResolve({ filter: /.*/ }, (args) => {
if (args.path !== "@trigger.dev/core/v3") {
if (!args.path.startsWith("@trigger.dev/core/v3")) {
return undefined;
}
@@ -22,18 +22,15 @@ export function bundleTriggerDevCore(buildIdentifier: string, tsconfigPath?: str
triggerSdkPath,
});
const resolvedPath = require.resolve("@trigger.dev/core/v3", {
const resolvedPath = require.resolve(args.path, {
paths: [triggerSdkPath],
});
logger.debug(
`[${buildIdentifier}][trigger-bundle-core] Externalizing @trigger.dev/core/v3`,
{
...args,
triggerSdkPath,
resolvedPath,
}
);
logger.debug(`[${buildIdentifier}][trigger-bundle-core] Externalizing ${args.path}`, {
...args,
triggerSdkPath,
resolvedPath,
});
return {
path: resolvedPath,
@@ -1,3 +1,4 @@
import { log } from "@clack/prompts";
import chalk from "chalk";
export const green = "#4FFF54";
@@ -63,3 +64,49 @@ export function prettyPrintDate(date: Date = new Date()) {
return formattedDate;
}
export function prettyError(header: string, body?: string, footer?: string) {
const prefix = "Error: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkError(prefix);
const withIndents = (text?: string) =>
text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body);
const prettyFooter = withIndents(footer);
log.error(
`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${
prettyFooter ? `${spacing}${prettyFooter}` : ""
}`
);
}
export function prettyWarning(header: string, body?: string, footer?: string) {
const prefix = "Warning: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkWarning(prefix);
const withIndents = (text?: string) =>
text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body);
const prettyFooter = withIndents(footer);
log.warn(
`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${
prettyFooter ? `${spacing}${prettyFooter}` : ""
}`
);
}
+15 -1
View File
@@ -99,6 +99,18 @@ async function getConfigPath(dir: string, fileName?: string): Promise<string | u
return await findUp(fileName ? [fileName] : CONFIG_FILES, { cwd: dir });
}
async function findFilePath(dir: string, fileName: string): Promise<string | undefined> {
const result = await findUp([fileName], { cwd: dir });
logger.debug("Searched for the file", {
dir,
fileName,
result,
});
return result;
}
export type ReadConfigOptions = {
projectRef?: string;
configFile?: string;
@@ -187,6 +199,8 @@ export async function resolveConfig(path: string, config: Config): Promise<Resol
config.triggerDirectories = resolveTriggerDirectories(config.triggerDirectories);
logger.debug("Resolved trigger directories", { triggerDirectories: config.triggerDirectories });
if (!config.triggerUrl) {
config.triggerUrl = CLOUD_API_URL;
}
@@ -196,7 +210,7 @@ export async function resolveConfig(path: string, config: Config): Promise<Resol
}
if (!config.tsconfigPath) {
config.tsconfigPath = await getConfigPath(path, "tsconfig.json");
config.tsconfigPath = await findFilePath(path, "tsconfig.json");
}
return config as ResolvedConfig;
@@ -3,9 +3,10 @@ import { relative } from "node:path";
import { chalkError, chalkPurple, chalkGrey, chalkGreen, chalkWarning } from "./cliOutput";
import { logger } from "./logger";
import { ReadConfigResult } from "./configFiles";
import { TaskMetadataParseError } from "../workers/common/errors";
import { z } from "zod";
import { groupTaskMetadataIssuesByTask } from "@trigger.dev/core/v3";
import terminalLink from "terminal-link";
import { docs } from "./links";
export type ESMRequireError = {
type: "esm-require-error";
@@ -86,6 +87,10 @@ export function logESMRequireError(parsedError: ESMRequireError, resolvedConfig:
)}. This will bundle the module with your code.\n`
);
}
logger.log(
`${chalkGrey("○")} For more info see the ${terminalLink("relevant docs", docs.config.esm)}.\n`
);
}
export type PackageNotFoundError = {
+2 -2
View File
@@ -67,8 +67,8 @@ export async function safeFeadJSONFile(path: string) {
}
}
export async function writeJSONFile(path: string, json: any) {
await writeFile(path, JSON.stringify(json), "utf8");
export async function writeJSONFile(path: string, json: any, pretty = false) {
await writeFile(path, JSON.stringify(json, undefined, pretty ? 2 : undefined), "utf8");
}
export function readJSONFileSync(path: string) {
@@ -1,8 +1,16 @@
import { findUp } from "find-up";
import { basename } from "path";
import { logger } from "./logger";
export type PackageManager = "npm" | "pnpm" | "yarn";
export async function getUserPackageManager(path: string): Promise<PackageManager> {
const packageManager = await detectPackageManager(path);
logger.debug("Detected package manager", { packageManager });
return packageManager;
}
async function detectPackageManager(path: string): Promise<PackageManager> {
try {
return await detectPackageManagerFromArtifacts(path);
} catch (error) {
@@ -29,19 +37,30 @@ function detectPackageManagerFromCurrentCommand(): PackageManager {
}
async function detectPackageManagerFromArtifacts(path: string): Promise<PackageManager> {
const packageFiles = [
{ name: "yarn.lock", pm: "yarn" } as const,
{ name: "pnpm-lock.yaml", pm: "pnpm" } as const,
{ name: "package-lock.json", pm: "npm" } as const,
{ name: "npm-shrinkwrap.json", pm: "npm" } as const,
];
const artifacts = {
yarn: "yarn.lock",
pnpm: "pnpm-lock.yaml",
npm: "package-lock.json",
npmShrinkwrap: "npm-shrinkwrap.json",
};
for (const { name, pm } of packageFiles) {
const foundPath = await findUp(name, { cwd: path });
if (typeof foundPath === "string") {
return pm;
}
const foundPath = await findUp(Object.values(artifacts), { cwd: path });
if (!foundPath) {
throw new Error("Could not detect package manager from artifacts");
}
throw new Error("Could not detect package manager from artifacts");
logger.debug("Found path from package manager artifacts", { foundPath });
switch (basename(foundPath)) {
case artifacts.yarn:
return "yarn";
case artifacts.pnpm:
return "pnpm";
case artifacts.npm:
case artifacts.npmShrinkwrap:
return "npm";
default:
throw new Error(`Unhandled package manager detection path: ${foundPath}`);
}
}
+11 -7
View File
@@ -8,8 +8,8 @@ import { logger } from "./logger.js";
import { spinner } from "./windows";
export async function printInitialBanner(performUpdateCheck = true) {
const packageVersion = getVersion();
const text = `\n${logo()} ${chalkGrey(`(${packageVersion})`)}\n`;
const cliVersion = getVersion();
const text = `\n${logo()} ${chalkGrey(`(${cliVersion})`)}\n`;
logger.info(text);
@@ -22,7 +22,7 @@ export async function printInitialBanner(performUpdateCheck = true) {
// Log a slightly more noticeable message if this is a major bump
if (maybeNewVersion !== undefined) {
loadingSpinner.stop(`Update available ${chalk.green(maybeNewVersion)}`);
const currentMajor = parseInt(packageVersion.split(".")[0]!);
const currentMajor = parseInt(cliVersion.split(".")[0]!);
const newMajor = parseInt(maybeNewVersion.split(".")[0]!);
if (newMajor > currentMajor) {
logger.warn(
@@ -38,9 +38,9 @@ After installation, run Trigger.dev with \`npx trigger.dev\`.`
}
export async function printStandloneInitialBanner(performUpdateCheck = true) {
const packageVersion = getVersion();
const cliVersion = getVersion();
logger.log(`\n${logo()} ${chalkGrey("(v3 Developer Preview)")}`);
logger.log(`\n${logo()} ${chalkGrey(`(${cliVersion})`)}\n`);
if (performUpdateCheck) {
const maybeNewVersion = await updateCheck();
@@ -54,7 +54,11 @@ export async function printStandloneInitialBanner(performUpdateCheck = true) {
logger.log(`${chalkGrey("-".repeat(54))}`);
}
export function printDevBanner() {
export function printDevBanner(printTopBorder = true) {
if (printTopBorder) {
logger.log(chalkGrey("-".repeat(54)));
}
logger.log(
`${chalkGrey("Key:")} ${chalkWorker("Version")} ${chalkGrey("|")} ${chalkTask(
"Task"
@@ -68,7 +72,7 @@ async function doUpdateCheck(): Promise<string | undefined> {
try {
// default cache for update check is 1 day
update = await checkForUpdate(pkg, {
distTag: pkg.version.startsWith("0.0.0") ? "beta" : "latest",
distTag: pkg.version.startsWith("3.0.0-beta") ? "beta" : "latest",
});
} catch (err) {
// ignore error
@@ -1,74 +0,0 @@
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...");
const pkgManager = await getUserPackageManager(projectDir);
const installSpinner = await runInstallCommand(pkgManager, projectDir);
// If the spinner was used to show the progress, use succeed method on it
// If not, use the succeed on a new spinner
(installSpinner || spinner()).stop(chalk.green("Successfully installed dependencies!\n"));
}
async function runInstallCommand(
pkgManager: PackageManager,
projectDir: string
): Promise<ReturnType<typeof spinner> | null> {
switch (pkgManager) {
// When using npm, inherit the stderr stream so that the progress bar is shown
case "npm":
await execa(pkgManager, ["install"], {
cwd: projectDir,
stderr: "inherit",
});
return null;
// When using yarn or pnpm, use the stdout stream and ora spinner to show the progress
case "pnpm": {
const loadingSpinner = spinner();
loadingSpinner.start("Running pnpm install...");
const pnpmSubprocess = execa(pkgManager, ["install"], {
cwd: projectDir,
stdout: "pipe",
});
await new Promise<void>((res, rej) => {
pnpmSubprocess.stdout?.on("data", (data: Buffer) => {
const text = data.toString();
if (text.includes("Progress")) {
loadingSpinner.message(text.includes("|") ? text.split(" | ")[1] ?? "" : text);
}
});
pnpmSubprocess.on("error", (e) => rej(e));
pnpmSubprocess.on("close", () => res());
});
return loadingSpinner;
}
case "yarn": {
const loadingSpinner = spinner();
loadingSpinner.start("Running yarn...");
const yarnSubprocess = execa(pkgManager, [], {
cwd: projectDir,
stdout: "pipe",
});
await new Promise<void>((res, rej) => {
yarnSubprocess.stdout?.on("data", (data: Buffer) => {
loadingSpinner.message(data.toString());
});
yarnSubprocess.on("error", (e) => rej(e));
yarnSubprocess.on("close", () => res());
});
return loadingSpinner;
}
}
}

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