diff --git a/.changeset/beige-horses-juggle.md b/.changeset/beige-horses-juggle.md deleted file mode 100644 index 9c2f5c40e..000000000 --- a/.changeset/beige-horses-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Added experimental_devProcessCwdInBuildDir config option to opt-in to new process.cwd behavior when executing tasks in the dev CLI. Currently process.cwd maps to the "root" of your trigger.dev project (the directory that contains your trigger.config.ts file). Setting experimental_devProcessCwdInBuildDir to true changes process.cwd to instead be the temporary build directory inside of the .trigger directory. diff --git a/.changeset/big-carrots-fail.md b/.changeset/big-carrots-fail.md deleted file mode 100644 index 0dc095499..000000000 --- a/.changeset/big-carrots-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix dev runs diff --git a/.changeset/big-garlics-own.md b/.changeset/big-garlics-own.md deleted file mode 100644 index 3df6d66f7..000000000 --- a/.changeset/big-garlics-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -fix: importing from runEngine/index.js breaks non-node runtimes diff --git a/.changeset/blue-eyes-tickle.md b/.changeset/blue-eyes-tickle.md deleted file mode 100644 index ab4ca8b92..000000000 --- a/.changeset/blue-eyes-tickle.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -The dev command will now use the platform-provided engine URL diff --git a/.changeset/breezy-turtles-talk.md b/.changeset/breezy-turtles-talk.md deleted file mode 100644 index da208cb2c..000000000 --- a/.changeset/breezy-turtles-talk.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@trigger.dev/react-hooks": patch -"@trigger.dev/sdk": patch -"trigger.dev": patch -"@trigger.dev/build": patch -"@trigger.dev/core": patch -"@trigger.dev/rsc": patch ---- - -Run Engine 2.0 (alpha) diff --git a/.changeset/chatty-snakes-hope.md b/.changeset/chatty-snakes-hope.md deleted file mode 100644 index e0d2083a5..000000000 --- a/.changeset/chatty-snakes-hope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -fix: Logging large objects is now much more performant and uses less memory diff --git a/.changeset/chilled-weeks-switch.md b/.changeset/chilled-weeks-switch.md deleted file mode 100644 index b49f9c57f..000000000 --- a/.changeset/chilled-weeks-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix update command version mismatch detection diff --git a/.changeset/clean-beans-compete.md b/.changeset/clean-beans-compete.md deleted file mode 100644 index 520ca16c8..000000000 --- a/.changeset/clean-beans-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -New internal idempotency implementation for trigger and batch trigger to prevent request retries from duplicating work diff --git a/.changeset/cuddly-boats-press.md b/.changeset/cuddly-boats-press.md deleted file mode 100644 index 7d4426393..000000000 --- a/.changeset/cuddly-boats-press.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Add external log exporters and fix missing external trace exporters in deployed tasks diff --git a/.changeset/curvy-dogs-share.md b/.changeset/curvy-dogs-share.md deleted file mode 100644 index a0071042a..000000000 --- a/.changeset/curvy-dogs-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -When you create a Waitpoint token using `wait.createToken()` you get a URL back that can be used to complete it by making an HTTP POST request. diff --git a/.changeset/cyan-news-design.md b/.changeset/cyan-news-design.md deleted file mode 100644 index c7db9189f..000000000 --- a/.changeset/cyan-news-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Allow any runs to finish after SIGTERM but disable warm starts diff --git a/.changeset/early-points-jam.md b/.changeset/early-points-jam.md deleted file mode 100644 index 645a50002..000000000 --- a/.changeset/early-points-jam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Gracefully shutdown task run processes using SIGTERM followed by SIGKILL after a 1s timeout. This also prevents cancelled or completed runs from leaving orphaned Ttask run processes behind diff --git a/.changeset/eight-ligers-help.md b/.changeset/eight-ligers-help.md deleted file mode 100644 index 57c832b0b..000000000 --- a/.changeset/eight-ligers-help.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Enhance deploy command output to better distinguish between local and remote builds diff --git a/.changeset/eighty-rings-divide.md b/.changeset/eighty-rings-divide.md deleted file mode 100644 index 193b46ca7..000000000 --- a/.changeset/eighty-rings-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Configurable queue consumer count in supervisor session diff --git a/.changeset/empty-dolls-judge.md b/.changeset/empty-dolls-judge.md deleted file mode 100644 index 477cc9ef4..000000000 --- a/.changeset/empty-dolls-judge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Switch to profile after successful login diff --git a/.changeset/fifty-beers-bake.md b/.changeset/fifty-beers-bake.md deleted file mode 100644 index 63c431c48..000000000 --- a/.changeset/fifty-beers-bake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fixes a bug that would allow processes that had OOM errors to be incorrectly reused when experimental_processKeepAlive was enabled diff --git a/.changeset/flat-pianos-live.md b/.changeset/flat-pianos-live.md deleted file mode 100644 index c1f915195..000000000 --- a/.changeset/flat-pianos-live.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Runtime agnostic SDK config via env vars diff --git a/.changeset/four-needles-add.md b/.changeset/four-needles-add.md deleted file mode 100644 index 7dd18092d..000000000 --- a/.changeset/four-needles-add.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@trigger.dev/redis-worker": major -"@trigger.dev/react-hooks": major -"@trigger.dev/sdk": major -"trigger.dev": major -"@trigger.dev/python": major -"@trigger.dev/build": major -"@trigger.dev/core": major -"@trigger.dev/rsc": major ---- - -Trigger.dev v4 release. Please see our upgrade to v4 docs to view the full changelog: https://trigger.dev/docs/upgrade-to-v4 diff --git a/.changeset/fuzzy-snakes-beg.md b/.changeset/fuzzy-snakes-beg.md deleted file mode 100644 index f5bd55cde..000000000 --- a/.changeset/fuzzy-snakes-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Add supervisor http client option to disable debug logs diff --git a/.changeset/gentle-waves-suffer.md b/.changeset/gentle-waves-suffer.md deleted file mode 100644 index d96452b77..000000000 --- a/.changeset/gentle-waves-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Fixed an issue with realtime streams that timeout and resume streaming dropping chunks diff --git a/.changeset/giant-plums-smash.md b/.changeset/giant-plums-smash.md deleted file mode 100644 index 7b23fb870..000000000 --- a/.changeset/giant-plums-smash.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Added and cleaned up the run ctx param: - -- New optional properties `ctx.run.parentTaskRunId` and `ctx.run.rootTaskRunId` reference the current run's root/parent ID. -- Removed deprecated properties from `ctx` -- Added a new `ctx.deployment` object that contains information about the deployment associated with the run. - -We also update `metadata.root` and `metadata.parent` to work even when the run is a "root" run (meaning it doesn't have a parent or a root associated run). This now works: - -```ts -metadata.root.set("foo", "bar"); -metadata.parent.set("baz", 1); -metadata.current().foo // "bar" -metadata.current().baz // 1 -``` diff --git a/.changeset/gold-insects-invite.md b/.changeset/gold-insects-invite.md deleted file mode 100644 index 7b260ed60..000000000 --- a/.changeset/gold-insects-invite.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Expose esbuild `keepNames` option (experimental) diff --git a/.changeset/green-lions-relate.md b/.changeset/green-lions-relate.md deleted file mode 100644 index da2a96b29..000000000 --- a/.changeset/green-lions-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -The envvars.list() and retrieve() functions receive isSecret for each value. Secret values are always redacted. diff --git a/.changeset/grumpy-wasps-fold.md b/.changeset/grumpy-wasps-fold.md deleted file mode 100644 index 78b74f73b..000000000 --- a/.changeset/grumpy-wasps-fold.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Add `experimental_autoDetectExternal` trigger config option diff --git a/.changeset/healthy-apricots-drop.md b/.changeset/healthy-apricots-drop.md deleted file mode 100644 index aae2b175e..000000000 --- a/.changeset/healthy-apricots-drop.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Add project details to the whoami command diff --git a/.changeset/hip-cups-wave.md b/.changeset/hip-cups-wave.md deleted file mode 100644 index c21b94e37..000000000 --- a/.changeset/hip-cups-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Fix issue where realtime streams would cut off after 5 minutes diff --git a/.changeset/honest-files-decide.md b/.changeset/honest-files-decide.md deleted file mode 100644 index 6bc65f34c..000000000 --- a/.changeset/honest-files-decide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Deprecate toolTask and replace with `ai.tool(mySchemaTask)` diff --git a/.changeset/itchy-frogs-care.md b/.changeset/itchy-frogs-care.md deleted file mode 100644 index 72cb57c86..000000000 --- a/.changeset/itchy-frogs-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Log images sizes for self-hosted deploys diff --git a/.changeset/itchy-games-sort.md b/.changeset/itchy-games-sort.md deleted file mode 100644 index 3f04f6822..000000000 --- a/.changeset/itchy-games-sort.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"trigger.dev": patch ---- - -Display clickable links in Cursor terminal diff --git a/.changeset/late-chairs-ring.md b/.changeset/late-chairs-ring.md deleted file mode 100644 index cd7c9f362..000000000 --- a/.changeset/late-chairs-ring.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix init.ts in custom trigger dirs diff --git a/.changeset/late-dancers-smile.md b/.changeset/late-dancers-smile.md deleted file mode 100644 index 58026740d..000000000 --- a/.changeset/late-dancers-smile.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Add import timings and bundle size analysis, the dev command will now warn about slow imports diff --git a/.changeset/lazy-panthers-shop.md b/.changeset/lazy-panthers-shop.md deleted file mode 100644 index fa622e087..000000000 --- a/.changeset/lazy-panthers-shop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Improve structured logs diff --git a/.changeset/lazy-plums-fetch.md b/.changeset/lazy-plums-fetch.md deleted file mode 100644 index 515d6f85d..000000000 --- a/.changeset/lazy-plums-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/build": patch ---- - -syncVercelEnvVars() fix for syncing the wrong preview branch env vars diff --git a/.changeset/light-peas-melt.md b/.changeset/light-peas-melt.md deleted file mode 100644 index 52d184b6c..000000000 --- a/.changeset/light-peas-melt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix update command version range handling diff --git a/.changeset/little-birds-appear.md b/.changeset/little-birds-appear.md deleted file mode 100644 index 870f253b8..000000000 --- a/.changeset/little-birds-appear.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Removes the `releaseConcurrencyOnWaitpoint` option on queues and the `releaseConcurrency` option on various wait functions. Replaced with the following default behavior: - -- Concurrency is never released when a run is first blocked via a waitpoint, at either the env or queue level. -- Concurrency is always released when a run is checkpointed and shutdown, at both the env and queue level. - -Additionally, environment concurrency limits now have a new "Burst Factor", defaulting to 2.0x. The "Burst Factor" allows the environment-wide concurrency limit to be higher than any individual queue's concurrency limit. For example, if you have an environment concurrency limit of 100, and a Burst Factor of 2.0x, then you can execute up to 200 runs concurrently, but any one task/queue can still only execute 100 runs concurrently. - -We've done some work cleaning up the run statuses. The new statuses are: - -- `PENDING_VERSION`: Task is waiting for a version update because it cannot execute without additional information (task, queue, etc.) -- `QUEUED`: Task is waiting to be executed by a worker -- `DEQUEUED`: Task has been dequeued and is being sent to a worker to start executing. -- `EXECUTING`: Task is currently being executed by a worker -- `WAITING`: Task has been paused by the system, and will be resumed by the system -- `COMPLETED`: Task has been completed successfully -- `CANCELED`: Task has been canceled by the user -- `FAILED`: Task has failed to complete, due to an error in the system -- `CRASHED`: Task has crashed and won't be retried, most likely the worker ran out of resources, e.g. memory or storage -- `SYSTEM_FAILURE`: Task has failed to complete, due to an error in the system -- `DELAYED`: Task has been scheduled to run at a specific time -- `EXPIRED`: Task has expired and won't be executed -- `TIMED_OUT`: Task has reached it's maxDuration and has been stopped - -We've removed the following statuses: - -- `WAITING_FOR_DEPLOY`: This is no longer used, and is replaced by `PENDING_VERSION` -- `FROZEN`: This is no longer used, and is replaced by `WAITING` -- `INTERRUPTED`: This is no longer used -- `REATTEMPTING`: This is no longer used, and is replaced by `EXECUTING` - -We've also added "boolean" helpers to runs returned via the API and from Realtime: - -- `isQueued`: Returns true when the status is `QUEUED`, `PENDING_VERSION`, or `DELAYED` -- `isExecuting`: Returns true when the status is `EXECUTING`, `DEQUEUED`. These count against your concurrency limits. -- `isWaiting`: Returns true when the status is `WAITING`. These do not count against your concurrency limits. -- `isCompleted`: Returns true when the status is any of the completed statuses. -- `isCanceled`: Returns true when the status is `CANCELED` -- `isFailed`: Returns true when the status is any of the failed statuses. -- `isSuccess`: Returns true when the status is `COMPLETED` - -This change adds the ability to easily detect which runs are being counted against your concurrency limit by filtering for both `EXECUTING` or `DEQUEUED`. diff --git a/.changeset/moody-squids-count.md b/.changeset/moody-squids-count.md deleted file mode 100644 index e47508810..000000000 --- a/.changeset/moody-squids-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Init command will now correctly install v4-beta packages diff --git a/.changeset/nasty-cobras-wonder.md b/.changeset/nasty-cobras-wonder.md deleted file mode 100644 index 0cd7c417d..000000000 --- a/.changeset/nasty-cobras-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix metadata collapsing correctness diff --git a/.changeset/nice-colts-boil.md b/.changeset/nice-colts-boil.md deleted file mode 100644 index bd395ae9d..000000000 --- a/.changeset/nice-colts-boil.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Improve warm start times by eagerly creating the child TaskRunProcess when a previous run as completed diff --git a/.changeset/ninety-games-grow.md b/.changeset/ninety-games-grow.md deleted file mode 100644 index df22eff4e..000000000 --- a/.changeset/ninety-games-grow.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -- Resolve issue where CLI could get stuck during deploy finalization -- Unify local and remote build logic, with multi-platform build support -- Improve switch command; now accepts profile name as an argument -- Registry configuration is now fully managed by the webapp -- The deploy `--self-hosted` flag is no longer required -- Enhance deployment error reporting and image digest retrieval diff --git a/.changeset/orange-pens-smile.md b/.changeset/orange-pens-smile.md deleted file mode 100644 index 9a4948cda..000000000 --- a/.changeset/orange-pens-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/redis-worker": patch ---- - -Now each worker gets it's own pLimit concurrency limiter, and we will only ever dequeue items where there is concurrency capacity, preventing incorrectly retried jobs due to visibility timeout expiry diff --git a/.changeset/orange-rocks-grow.md b/.changeset/orange-rocks-grow.md deleted file mode 100644 index c8a6bdaa2..000000000 --- a/.changeset/orange-rocks-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix init.ts detection when using the sentry esbuild plugin diff --git a/.changeset/plenty-dolphins-act.md b/.changeset/plenty-dolphins-act.md deleted file mode 100644 index 59d2c7fc4..000000000 --- a/.changeset/plenty-dolphins-act.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -- Correctly resolve waitpoints that come in early -- Ensure correct state before requesting suspension -- Fix race conditions in snapshot processing diff --git a/.changeset/polite-badgers-suffer.md b/.changeset/polite-badgers-suffer.md deleted file mode 100644 index bba234e21..000000000 --- a/.changeset/polite-badgers-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -experimental processKeepAlive diff --git a/.changeset/polite-impalas-care.md b/.changeset/polite-impalas-care.md deleted file mode 100644 index 134ff3dd4..000000000 --- a/.changeset/polite-impalas-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fixes runLimiter check on #dequeueRuns diff --git a/.changeset/polite-lies-fix.md b/.changeset/polite-lies-fix.md deleted file mode 100644 index 6e60a7760..000000000 --- a/.changeset/polite-lies-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Update nypm package to support test-based bun.lock files diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index f6ffff5fc..000000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "mode": "pre", - "tag": "v4-beta", - "initialVersions": { - "coordinator": "0.0.1", - "docker-provider": "0.0.1", - "kubernetes-provider": "0.0.1", - "supervisor": "0.0.1", - "webapp": "1.0.0", - "@trigger.dev/build": "3.3.17", - "trigger.dev": "3.3.17", - "@trigger.dev/core": "3.3.17", - "@trigger.dev/python": "3.3.17", - "@trigger.dev/react-hooks": "3.3.17", - "@trigger.dev/redis-worker": "3.3.17", - "@trigger.dev/rsc": "3.3.17", - "@trigger.dev/sdk": "3.3.17" - }, - "changesets": [ - "beige-horses-juggle", - "big-carrots-fail", - "big-garlics-own", - "blue-eyes-tickle", - "breezy-turtles-talk", - "chatty-snakes-hope", - "chilled-weeks-switch", - "clean-beans-compete", - "cuddly-boats-press", - "curvy-dogs-share", - "cyan-news-design", - "early-points-jam", - "eight-ligers-help", - "eighty-rings-divide", - "empty-dolls-judge", - "fifty-beers-bake", - "flat-pianos-live", - "four-needles-add", - "fuzzy-snakes-beg", - "gentle-waves-suffer", - "giant-plums-smash", - "gold-insects-invite", - "green-lions-relate", - "grumpy-wasps-fold", - "healthy-apricots-drop", - "hip-cups-wave", - "honest-files-decide", - "itchy-frogs-care", - "itchy-games-sort", - "late-chairs-ring", - "late-dancers-smile", - "lazy-panthers-shop", - "lazy-plums-fetch", - "light-peas-melt", - "little-birds-appear", - "moody-squids-count", - "nasty-cobras-wonder", - "nice-colts-boil", - "ninety-games-grow", - "orange-pens-smile", - "orange-rocks-grow", - "plenty-dolphins-act", - "polite-badgers-suffer", - "polite-impalas-care", - "polite-lies-fix", - "rare-beds-accept", - "rare-mails-fail", - "real-rats-drop", - "red-chairs-begin", - "red-rings-marry", - "red-wasps-cover", - "shiny-kiwis-beam", - "silly-cows-serve", - "silly-timers-repair", - "sixty-beers-share", - "small-birds-arrive", - "small-dancers-smell", - "smart-coins-hammer", - "smooth-planets-flow", - "soft-candles-do", - "sour-mirrors-accept", - "spotty-ducks-punch", - "spotty-pants-wink", - "strong-pianos-provide", - "sweet-dolphins-invent", - "tender-jobs-collect", - "thick-bikes-laugh", - "tidy-books-smell", - "tiny-buckets-teach", - "tricky-houses-invite", - "twelve-actors-hide", - "two-eagles-report", - "two-tigers-dream", - "weak-jobs-hide", - "weak-parents-sip", - "wet-deers-think", - "wet-dragons-boil", - "wet-steaks-reflect", - "wicked-ads-walk", - "wild-mirrors-return", - "witty-cherries-tan", - "witty-donkeys-unite", - "yellow-cars-tell" - ] -} diff --git a/.changeset/rare-beds-accept.md b/.changeset/rare-beds-accept.md deleted file mode 100644 index dccd97a96..000000000 --- a/.changeset/rare-beds-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Add verbose structured log level diff --git a/.changeset/rare-mails-fail.md b/.changeset/rare-mails-fail.md deleted file mode 100644 index ef4e9861b..000000000 --- a/.changeset/rare-mails-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/build": patch ---- - -Add Lightpanda extension diff --git a/.changeset/real-rats-drop.md b/.changeset/real-rats-drop.md deleted file mode 100644 index 953794afd..000000000 --- a/.changeset/real-rats-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add onCancel lifecycle hook diff --git a/.changeset/red-chairs-begin.md b/.changeset/red-chairs-begin.md deleted file mode 100644 index e54857e55..000000000 --- a/.changeset/red-chairs-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Added AI assistance link when you have build errors diff --git a/.changeset/red-rings-marry.md b/.changeset/red-rings-marry.md deleted file mode 100644 index 87fb25647..000000000 --- a/.changeset/red-rings-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Provide realtime skipColumns option via untamperable public access tokens diff --git a/.changeset/red-wasps-cover.md b/.changeset/red-wasps-cover.md deleted file mode 100644 index 035e7549f..000000000 --- a/.changeset/red-wasps-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Suppress external instrumentation for fetch calls from ApiClient diff --git a/.changeset/shiny-kiwis-beam.md b/.changeset/shiny-kiwis-beam.md deleted file mode 100644 index c01b13116..000000000 --- a/.changeset/shiny-kiwis-beam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Handle flush errors gracefully in dev diff --git a/.changeset/silly-cows-serve.md b/.changeset/silly-cows-serve.md deleted file mode 100644 index d655dbed8..000000000 --- a/.changeset/silly-cows-serve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Added support for Preview branches in v4 projects diff --git a/.changeset/silly-timers-repair.md b/.changeset/silly-timers-repair.md deleted file mode 100644 index 711fbc7f2..000000000 --- a/.changeset/silly-timers-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Can now set project ref using the TRIGGER_PROJECT_REF env var diff --git a/.changeset/sixty-beers-share.md b/.changeset/sixty-beers-share.md deleted file mode 100644 index 862628e2c..000000000 --- a/.changeset/sixty-beers-share.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Add runtime version detection for display in the dashboard diff --git a/.changeset/small-birds-arrive.md b/.changeset/small-birds-arrive.md deleted file mode 100644 index cf1039b83..000000000 --- a/.changeset/small-birds-arrive.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@trigger.dev/react-hooks": patch ---- - -Added the ability to specify a "createdAt" filter when subscribing to tags in our useRealtime hooks: - -```tsx -// Only subscribe to runs created in the last 10 hours -useRealtimeRunWithTags("my-tag", { createdAt: "10h" }) -``` - -You can also now choose to skip subscribing to specific columns by specifying the `skipColumns` option: - -```tsx -useRealtimeRun(run.id, { skipColumns: ["usageDurationMs"] }); -``` diff --git a/.changeset/small-dancers-smell.md b/.changeset/small-dancers-smell.md deleted file mode 100644 index ff9b68c00..000000000 --- a/.changeset/small-dancers-smell.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Improve metadata flushing efficiency by collapsing operations diff --git a/.changeset/smart-coins-hammer.md b/.changeset/smart-coins-hammer.md deleted file mode 100644 index bea810c6c..000000000 --- a/.changeset/smart-coins-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -fix: Realtime streams: prevent enqueuing into closed ReadableStream diff --git a/.changeset/smooth-planets-flow.md b/.changeset/smooth-planets-flow.md deleted file mode 100644 index 708932fcc..000000000 --- a/.changeset/smooth-planets-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Update profile switcher diff --git a/.changeset/soft-candles-do.md b/.changeset/soft-candles-do.md deleted file mode 100644 index e423053d4..000000000 --- a/.changeset/soft-candles-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Update base images to latest compatible versions. The `node-22` runtime now uses v22.16.0 and `bun` uses the latest v1.2.18 release. The default `node` runtime is unchanged and points at v21.7.3. diff --git a/.changeset/sour-mirrors-accept.md b/.changeset/sour-mirrors-accept.md deleted file mode 100644 index 34084228c..000000000 --- a/.changeset/sour-mirrors-accept.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Improve usage flushing diff --git a/.changeset/spotty-ducks-punch.md b/.changeset/spotty-ducks-punch.md deleted file mode 100644 index f6c029801..000000000 --- a/.changeset/spotty-ducks-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -fix: default machine config indexing now works diff --git a/.changeset/spotty-pants-wink.md b/.changeset/spotty-pants-wink.md deleted file mode 100644 index 7021ecc8f..000000000 --- a/.changeset/spotty-pants-wink.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Prevent large outputs from overwriting each other diff --git a/.changeset/strong-pianos-provide.md b/.changeset/strong-pianos-provide.md deleted file mode 100644 index 728179393..000000000 --- a/.changeset/strong-pianos-provide.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Fail fast in CI when running deploy with missing `TRIGGER_ACCESS_TOKEN` and add useful error message with link to docs diff --git a/.changeset/sweet-dolphins-invent.md b/.changeset/sweet-dolphins-invent.md deleted file mode 100644 index df758a89e..000000000 --- a/.changeset/sweet-dolphins-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Always print full deploy logs in CI diff --git a/.changeset/tender-jobs-collect.md b/.changeset/tender-jobs-collect.md deleted file mode 100644 index 829c628b6..000000000 --- a/.changeset/tender-jobs-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -TriggerApiError 4xx errors will no longer cause tasks to be retried diff --git a/.changeset/thick-bikes-laugh.md b/.changeset/thick-bikes-laugh.md deleted file mode 100644 index 7166bfca6..000000000 --- a/.changeset/thick-bikes-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/build": patch ---- - -Add ffmpeg v7 support to existing extension: `ffmpeg({ version: "7" })` diff --git a/.changeset/tidy-books-smell.md b/.changeset/tidy-books-smell.md deleted file mode 100644 index b8ecf87f5..000000000 --- a/.changeset/tidy-books-smell.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -- Fix polling interval reset bug that could create duplicate intervals -- Protect against unexpected attempt number changes -- Prevent run execution zombies after warm starts \ No newline at end of file diff --git a/.changeset/tiny-buckets-teach.md b/.changeset/tiny-buckets-teach.md deleted file mode 100644 index fdf3ae3a9..000000000 --- a/.changeset/tiny-buckets-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix stalled run detection diff --git a/.changeset/tricky-houses-invite.md b/.changeset/tricky-houses-invite.md deleted file mode 100644 index e21e7b581..000000000 --- a/.changeset/tricky-houses-invite.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Managed run controller performance and reliability improvements diff --git a/.changeset/twelve-actors-hide.md b/.changeset/twelve-actors-hide.md deleted file mode 100644 index 7187b92ab..000000000 --- a/.changeset/twelve-actors-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix init.ts auto-import for deployed workers diff --git a/.changeset/two-eagles-report.md b/.changeset/two-eagles-report.md deleted file mode 100644 index 11f034ed3..000000000 --- a/.changeset/two-eagles-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Added runs.list filtering for queue and machine diff --git a/.changeset/two-tigers-dream.md b/.changeset/two-tigers-dream.md deleted file mode 100644 index b4fee01cb..000000000 --- a/.changeset/two-tigers-dream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -maintain proper context in metadata.root and parent getters diff --git a/.changeset/weak-jobs-hide.md b/.changeset/weak-jobs-hide.md deleted file mode 100644 index 0be1f4958..000000000 --- a/.changeset/weak-jobs-hide.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -v4: New lifecycle hooks diff --git a/.changeset/weak-parents-sip.md b/.changeset/weak-parents-sip.md deleted file mode 100644 index fb8589bae..000000000 --- a/.changeset/weak-parents-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Output esbuild metafile, can be inspected after `deploy --dry run` diff --git a/.changeset/wet-deers-think.md b/.changeset/wet-deers-think.md deleted file mode 100644 index 9002d7b94..000000000 --- a/.changeset/wet-deers-think.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Fix QUEUED status snapshot handler diff --git a/.changeset/wet-dragons-boil.md b/.changeset/wet-dragons-boil.md deleted file mode 100644 index becd48dd8..000000000 --- a/.changeset/wet-dragons-boil.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Serialize metadata to prevent invalid metadata from breaking run completions diff --git a/.changeset/wet-steaks-reflect.md b/.changeset/wet-steaks-reflect.md deleted file mode 100644 index 3a7774168..000000000 --- a/.changeset/wet-steaks-reflect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -If you pass a directory when calling deploy we validate it exists and give helpful hints diff --git a/.changeset/wicked-ads-walk.md b/.changeset/wicked-ads-walk.md deleted file mode 100644 index c9190c709..000000000 --- a/.changeset/wicked-ads-walk.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/react-hooks": patch -"@trigger.dev/core": patch ---- - -Fixes an issue with realtime when re-subscribing to a run, that would temporarily display stale data and the changes. Now when re-subscribing to a run only the latest changes will be vended diff --git a/.changeset/wild-mirrors-return.md b/.changeset/wild-mirrors-return.md deleted file mode 100644 index baee7565a..000000000 --- a/.changeset/wild-mirrors-return.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Expose esbuild `minify` option (experimental) diff --git a/.changeset/witty-cherries-tan.md b/.changeset/witty-cherries-tan.md deleted file mode 100644 index 062f1c68d..000000000 --- a/.changeset/witty-cherries-tan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix `syncEnvVars` for non-preview deployments diff --git a/.changeset/witty-donkeys-unite.md b/.changeset/witty-donkeys-unite.md deleted file mode 100644 index f1a17eb7f..000000000 --- a/.changeset/witty-donkeys-unite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/build": patch ---- - -Add playwright extension diff --git a/.changeset/yellow-cars-tell.md b/.changeset/yellow-cars-tell.md deleted file mode 100644 index a27ce329c..000000000 --- a/.changeset/yellow-cars-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Fix Bun runtime path resolution fallback diff --git a/.cursor/mcp.json b/.cursor/mcp.json index 9b3221784..da39e4ffa 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -1,7 +1,3 @@ { - "mcpServers": { - "trigger.dev": { - "url": "http://localhost:3333/sse" - } - } -} \ No newline at end of file + "mcpServers": {} +} diff --git a/.cursor/rules/webapp.mdc b/.cursor/rules/webapp.mdc index 6cda97395..a362f14fe 100644 --- a/.cursor/rules/webapp.mdc +++ b/.cursor/rules/webapp.mdc @@ -6,7 +6,7 @@ alwaysApply: false The main trigger.dev webapp, which powers it's API and dashboard and makes up the docker image that is produced as an OSS image, is a Remix 2.1.0 app that uses an express server, written in TypeScript. The following subsystems are either included in the webapp or are used by the webapp in another part of the monorepo: -- `@trigger.dev/database` exports a Prisma 5.4.1 client that is used extensively in the webapp to access a PostgreSQL instance. The schema file is [schema.prisma](mdc:internal-packages/database/prisma/schema.prisma) +- `@trigger.dev/database` exports a Prisma 6.14.0 client that is used extensively in the webapp to access a PostgreSQL instance. The schema file is [schema.prisma](mdc:internal-packages/database/prisma/schema.prisma) - `@trigger.dev/core` is a published package and is used to share code between the `@trigger.dev/sdk` and the webapp. It includes functionality but also a load of Zod schemas for data validation. When importing from `@trigger.dev/core` in the webapp, we never import the root `@trigger.dev/core` path, instead we favor one of the subpath exports that you can find in [package.json](mdc:packages/core/package.json) - `@internal/run-engine` has all the code needed to trigger a run and take it through it's lifecycle to completion. - `@trigger.dev/redis-worker` is a custom redis based background job/worker system that's used in the webapp and also used inside the run engine. @@ -31,7 +31,10 @@ We originally the Trigger.dev "Run Engine" not as a single system, but just spre - The batch trigger API endpoint is [api.v1.tasks.batch.ts](mdc:apps/webapp/app/routes/api.v1.tasks.batch.ts) - Setup code for the prisma client is in [db.server.ts](mdc:apps/webapp/app/db.server.ts) - The run engine is configured in [runEngine.server.ts](mdc:apps/webapp/app/v3/runEngine.server.ts) -- All the "services" that are found in app/v3/services/**/*.server.ts +- All the "services" that are found in app/v3/services/\*_/_.server.ts - The code for the TaskEvent data, which is the otel data sent from tasks to our servers, is in both the [eventRepository.server.ts](mdc:apps/webapp/app/v3/eventRepository.server.ts) and also the [otlpExporter.server.ts](mdc:apps/webapp/app/v3/otlpExporter.server.ts). The otel endpoints which are hit from production and development otel exporters is [otel.v1.logs.ts](mdc:apps/webapp/app/routes/otel.v1.logs.ts) and [otel.v1.traces.ts](mdc:apps/webapp/app/routes/otel.v1.traces.ts) -- We use "presenters" to move more complex loader code into a class, and you can find those are app/v3/presenters/**/*.server.ts +- We use "presenters" to move more complex loader code into a class, and you can find those are app/v3/presenters/\*_/_.server.ts +- All the "services" that are found in app/v3/services/\*_/_.server.ts +- The code for the TaskEvent data, which is the otel data sent from tasks to our servers, is in both the [eventRepository.server.ts](mdc:apps/webapp/app/v3/eventRepository.server.ts) and also the [otlpExporter.server.ts](mdc:apps/webapp/app/v3/otlpExporter.server.ts). The otel endpoints which are hit from production and development otel exporters is [otel.v1.logs.ts](mdc:apps/webapp/app/routes/otel.v1.logs.ts) and [otel.v1.traces.ts](mdc:apps/webapp/app/routes/otel.v1.traces.ts) +- We use "presenters" to move more complex loader code into a class, and you can find those are app/v3/presenters/\*_/_.server.ts diff --git a/.cursor/rules/writing-tasks.mdc b/.cursor/rules/writing-tasks.mdc index 6090b85f0..5116d083e 100644 --- a/.cursor/rules/writing-tasks.mdc +++ b/.cursor/rules/writing-tasks.mdc @@ -431,28 +431,6 @@ export async function POST(request: Request) { } ``` -### tasks.triggerAndPoll() - -Triggers a task and polls until completion. Not recommended for web requests as it blocks until the run completes. Consider using Realtime docs for better alternatives. - -```ts -import { tasks } from "@trigger.dev/sdk/v3"; -import type { emailSequence } from "~/trigger/emails"; - -export async function POST(request: Request) { - const data = await request.json(); - const result = await tasks.triggerAndPoll( - "email-sequence", - { - to: data.email, - name: data.name, - }, - { pollIntervalMs: 5000 } - ); - return Response.json(result); -} -``` - ### batch.trigger() Triggers multiple runs of different tasks at once, useful when you need to execute multiple tasks simultaneously. diff --git a/.dockerignore b/.dockerignore index d3f8720a9..a3ea4db8e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,6 +16,8 @@ **/dist **/node_modules +**/generated/prisma + apps/webapp/build apps/webapp/public/build diff --git a/.github/workflows/unit-tests-webapp.yml b/.github/workflows/unit-tests-webapp.yml index f73eada84..ff0052582 100644 --- a/.github/workflows/unit-tests-webapp.yml +++ b/.github/workflows/unit-tests-webapp.yml @@ -85,7 +85,7 @@ jobs: DIRECT_URL: postgresql://postgres:postgres@localhost:5432/postgres SESSION_SECRET: "secret" MAGIC_LINK_SECRET: "secret" - ENCRYPTION_KEY: "secret" + ENCRYPTION_KEY: "dummy-encryption-keeeey-32-bytes" DEPLOY_REGISTRY_HOST: "docker.io" CLICKHOUSE_URL: "http://default:password@localhost:8123" diff --git a/.gitignore b/.gitignore index 9bee46fc2..6f435d040 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,5 @@ apps/**/public/build /packages/core/src/package.json /packages/trigger-sdk/src/package.json /packages/python/src/package.json -.claude \ No newline at end of file +.claude +.mcp.log \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 6d0839208..d135aa70a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -59,7 +59,7 @@ "request": "launch", "name": "Debug V3 Dev CLI", "command": "pnpm exec trigger dev", - "cwd": "${workspaceFolder}/references/v3-catalog", + "cwd": "${workspaceFolder}/references/hello-world", "sourceMaps": true }, { @@ -83,7 +83,7 @@ "request": "launch", "name": "Debug V3 Deploy CLI", "command": "pnpm exec trigger deploy --self-hosted --load-image", - "cwd": "${workspaceFolder}/references/v3-catalog", + "cwd": "${workspaceFolder}/references/hello-world", "sourceMaps": true }, { @@ -91,7 +91,7 @@ "request": "launch", "name": "Debug V3 list-profiles CLI", "command": "pnpm exec trigger list-profiles --log-level debug", - "cwd": "${workspaceFolder}/references/v3-catalog", + "cwd": "${workspaceFolder}/references/hello-world", "sourceMaps": true }, { @@ -99,7 +99,7 @@ "request": "launch", "name": "Debug V3 update CLI", "command": "pnpm exec trigger update", - "cwd": "${workspaceFolder}/references/v3-catalog", + "cwd": "${workspaceFolder}/references/hello-world", "sourceMaps": true }, { @@ -107,7 +107,7 @@ "request": "launch", "name": "Debug V3 Management", "command": "pnpm run management", - "cwd": "${workspaceFolder}/references/v3-catalog", + "cwd": "${workspaceFolder}/references/hello-world", "sourceMaps": true }, { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 709dcca47..e578974a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,17 +84,17 @@ branch are tagged into a release periodically. 2. Once the app is running click the magic link button and enter your email. You will automatically be logged in, since you are running locally. Create an Org and your first project in the dashboard. -## Manual testing using v3-catalog +## Manual testing using hello-world -We use the `/references/v3-catalog` subdirectory as a staging ground for testing changes to the SDK (`@trigger.dev/sdk` at `/packages/trigger-sdk`), the Core package (`@trigger.dev/core` at `packages/core`), the CLI (`trigger.dev` at `/packages/cli-v3`) and the platform (The remix app at `/apps/webapp`). The instructions below will get you started on using the `v3-catalog` for local development of Trigger.dev (v3). +We use the `/references/hello-world` subdirectory as a staging ground for testing changes to the SDK (`@trigger.dev/sdk` at `/packages/trigger-sdk`), the Core package (`@trigger.dev/core` at `packages/core`), the CLI (`trigger.dev` at `/packages/cli-v3`) and the platform (The remix app at `/apps/webapp`). The instructions below will get you started on using the `hello-world` for local development of Trigger.dev. ### First-time setup First, make sure you are running the webapp according to the instructions above. Then: -1. Visit http://localhost:3030 in your browser and create a new V3 project called "v3-catalog". +1. Visit http://localhost:3030 in your browser and create a new V3 project called "hello-world". -2. In Postgres go to the "Projects" table and for the project you create change the `externalRef` to `yubjwjsfkxnylobaqvqz`. +2. In Postgres go to the "Projects" table and for the project you create change the `externalRef` to `proj_rrkpdguyagvsoktglnod`. 3. Build the CLI @@ -105,10 +105,10 @@ pnpm run build --filter trigger.dev pnpm i ``` -4. Change into the `/references/v3-catalog` directory and authorize the CLI to the local server: +4. Change into the `/references/hello-world` directory and authorize the CLI to the local server: ```sh -cd references/v3-catalog +cd references/hello-world cp .env.example .env pnpm exec trigger login -a http://localhost:3030 ``` @@ -118,7 +118,7 @@ This will open a new browser window and authorize the CLI against your local use You can optionally pass a `--profile` flag to the `login` command, which will allow you to use the CLI with separate accounts/servers. We suggest using a profile called `local` for your local development: ```sh -cd references/v3-catalog +cd references/hello-world pnpm exec trigger login -a http://localhost:3030 --profile local # later when you run the dev or deploy command: pnpm exec trigger dev --profile local @@ -137,84 +137,29 @@ The following steps should be followed any time you start working on a new featu pnpm run dev --filter trigger.dev --filter "@trigger.dev/*" ``` -3. Open another terminal window, and change into the `/references/v3-catalog` directory. +3. Open another terminal window, and change into the `/references/hello-world` directory. -4. You'll need to run the following commands to setup prisma and migrate the database: +4. Run the `dev` command, which will register all the local tasks with the platform and allow you to start testing task execution: ```sh -pnpm exec prisma migrate deploy -pnpm run generate:prisma -``` - -5. Run the `dev` command, which will register all the local tasks with the platform and allow you to start testing task execution: - -```sh -# in /references/v3-catalog +# in /references/hello-world pnpm exec trigger dev ``` If you want additional debug logging, you can use the `--log-level debug` flag: ```sh -# in /references/v3-catalog +# in /references/hello-world pnpm exec trigger dev --log-level debug ``` -6. If you make any changes in the CLI/Core/SDK, you'll need to `CTRL+C` to exit the `dev` command and restart it to pickup changes. Any changes to the files inside of the `v3-catalog/src/trigger` dir will automatically be rebuilt by the `dev` command. +6. If you make any changes in the CLI/Core/SDK, you'll need to `CTRL+C` to exit the `dev` command and restart it to pickup changes. Any changes to the files inside of the `hello-world/src/trigger` dir will automatically be rebuilt by the `dev` command. -7. Navigate to the `v3-catalog` project in your local dashboard at localhost:3030 and you should see the list of tasks. +7. Navigate to the `hello-world` project in your local dashboard at localhost:3030 and you should see the list of tasks. -8. Go to the "Test" page in the sidebar and select a task. Then enter a payload and click "Run test". You can tell what the payloads should be by looking at the relevant task file inside the `/references/v3-catalog/src/trigger` folder. Many of them accept an empty payload. +8. Go to the "Test" page in the sidebar and select a task. Then enter a payload and click "Run test". You can tell what the payloads should be by looking at the relevant task file inside the `/references/hello-world/src/trigger` folder. Many of them accept an empty payload. -9. Feel free to add additional files in `v3-catalog/src/trigger` to test out specific aspects of the system, or add in edge cases. - -## Running end-to-end webapp tests (deprecated) - -To run the end-to-end tests, follow the steps below: - -1. Set up environment variables (copy example envs into the correct place) - -```sh -cp ./.env.example ./.env -cp ./references/nextjs-test/.env.example ./references/nextjs-test/.env.local -``` - -2. Set up dependencies - -```sh -# Build packages -pnpm run build --filter @references/nextjs-test^... -pnpm --filter @trigger.dev/database generate - -# Move trigger-cli bin to correct place -pnpm install --frozen-lockfile - -# Install playwrite browsers (ONE TIME ONLY) -npx playwright install -``` - -3. Set up the database - -```sh -pnpm run docker -pnpm run db:migrate -pnpm run db:seed -``` - -4. Run the end-to-end tests - -```sh -pnpm run test:e2e -``` - -### Cleanup - -The end-to-end tests use a `setup` and `teardown` script to seed the database with test data. If the test runner doesn't exit cleanly, then the database can be left in a state where the tests can't run because the `setup` script will try to create data that already exists. If this happens, you can manually delete the `users` and `organizations` from the database using prisma studio: - -```sh -# With the database running (i.e. pnpm run docker) -pnpm run db:studio -``` +9. Feel free to add additional files in `hello-world/src/trigger` to test out specific aspects of the system, or add in edge cases. ## Adding and running migrations diff --git a/README.md b/README.md index dab0551dc..22181725b 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,75 @@
- - - - Trigger.dev logo - - -### Open source background jobs and AI infrastructure -[Discord](https://trigger.dev/discord) | [Website](https://trigger.dev) | [Issues](https://github.com/triggerdotdev/trigger.dev/issues) | [Docs](https://trigger.dev/docs) +![Trigger.dev logo](https://content.trigger.dev/github-header-banner.jpg) -[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/triggerdotdev.svg?style=social&label=Follow%20%40trigger.dev)](https://twitter.com/triggerdotdev) +### Build and deploy fully‑managed AI agents and workflows + +[Website](https://trigger.dev) | [Docs](https://trigger.dev/docs) | [Issues](https://github.com/triggerdotdev/trigger.dev/issues) | [Feature requests](https://triggerdev.featurebase.app/) | [Public roadmap](https://triggerdev.featurebase.app/roadmap) | [Self-hosting](https://trigger.dev/docs/self-hosting/overview) + +[![Open Source](https://img.shields.io/badge/Open%20Source-%E2%9D%A4-red.svg)](https://github.com/triggerdotdev/trigger.dev) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/triggerdotdev/trigger.dev/blob/main/LICENSE) +[![npm](https://img.shields.io/npm/v/@trigger.dev/sdk.svg?label=npm)](https://www.npmjs.com/package/@trigger.dev/sdk) +[![SDK downloads](https://img.shields.io/npm/dm/@trigger.dev/sdk.svg?label=SDK%20downloads)](https://www.npmjs.com/package/@trigger.dev/sdk) + +[![Twitter Follow](https://img.shields.io/twitter/follow/triggerdotdev?style=social)](https://twitter.com/triggerdotdev) +[![Discord](https://img.shields.io/discord/1066956501299777596?logo=discord&logoColor=white&color=7289da)](https://discord.gg/nkqV9xBYWy) +[![GitHub stars](https://img.shields.io/github/stars/triggerdotdev/trigger.dev?style=social)](https://github.com/triggerdotdev/trigger.dev)
## About Trigger.dev -Trigger.dev is an open source platform and SDK which allows you to create long-running background jobs. Write normal async code, deploy, and never hit a timeout. +Trigger.dev is the open-source platform for building AI workflows in TypeScript. Long-running tasks with retries, queues, observability, and elastic scaling. -### Key features: +## The platform designed for building AI agents -- JavaScript and TypeScript SDK -- No timeouts -- Retries (with exponential backoff) -- Queues and concurrency controls -- Schedules and crons -- Full Observability; logs, live trace views, advanced filtering -- React hooks to interact with the Trigger API from your React app -- Pipe LLM streams straight to your users through the Realtime API -- Trigger tasks and display the run status and metadata anywhere in your app -- Custom alerts, get notified by email, Slack or webhooks -- No infrastructure to manage -- Elastic (scaling) -- Works with your existing tech stack +Build [AI agents](https://trigger.dev/product/ai-agents) using all the frameworks, services and LLMs you're used to, deploy them to Trigger.dev and get durable, long-running tasks with retries, queues, observability, and elastic scaling out of the box. -## In your codebase +- **Long-running without timeouts**: Execute your tasks with absolutely no timeouts, unlike AWS Lambda, Vercel, and other serverless platforms. + +- **Durability, retries & queues**: Build rock solid agents and AI applications using our durable tasks, retries, queues and idempotency. + +- **True runtime freedom**: Customize your deployed tasks with system packages – run browsers, Python scripts, FFmpeg and more. + +- **Human-in-the-loop**: Programmatically pause your tasks until a human can approve, reject or give feedback. + +- **Realtime apps & streaming**: Move your background jobs to the foreground by subscribing to runs or streaming AI responses to your app. + +- **Observability & monitoring**: Each run has full tracing and logs. Configure error alerts to catch bugs fast. + +## Key features: + +- **[JavaScript and TypeScript SDK](https://trigger.dev/docs/tasks/overview)** - Build background tasks using familiar programming models +- **[Long-running tasks](https://trigger.dev/docs/runs/max-duration)** - Handle resource-heavy tasks without timeouts +- **[Durable cron schedules](https://trigger.dev/docs/tasks/scheduled#scheduled-tasks-cron)** - Create and attach recurring schedules of up to a year +- **[Trigger.dev Realtime](https://trigger.dev/docs/realtime/overview)** - Trigger, subscribe to, and get real-time updates for runs, with LLM streaming support +- **[Build extensions](https://trigger.dev/docs/config/extensions/overview#build-extensions)** - Hook directly into the build system and customize the build process. Run Python scripts, FFmpeg, browsers, and more. +- **[React hooks](https://trigger.dev/docs/frontend/react-hooks#react-hooks)** - Interact with the Trigger.dev API on your frontend using our React hooks package +- **[Batch triggering](https://trigger.dev/docs/triggering#tasks-batchtrigger)** - Use batchTrigger() to initiate multiple runs of a task with custom payloads and options +- **[Structured inputs / outputs](https://trigger.dev/docs/tasks/schemaTask#schematask)** - Define precise data schemas for your tasks with runtime payload validation +- **[Waits](https://trigger.dev/docs/wait)** - Add waits to your tasks to pause execution for a specified duration +- **[Preview branches](https://trigger.dev/docs/deployment/preview-branches)** - Create isolated environments for testing and development. Integrates with Vercel and git workflows +- **[Waitpoints](https://trigger.dev/docs/wait-for-token#wait-for-token)** - Add human-in-the-loop judgment at critical decision points without disrupting workflow +- **[Concurrency & queues](https://trigger.dev/docs/queue-concurrency#concurrency-and-queues)** - Set concurrency rules to manage how multiple tasks execute +- **[Multiple environments](https://trigger.dev/docs/how-it-works#dev-mode)** - Support for DEV, PREVIEW, STAGING, and PROD environments +- **[No infrastructure to manage](https://trigger.dev/docs/how-it-works#trigger-dev-architecture)** - Auto-scaling infrastructure that eliminates timeouts and server management +- **[Automatic retries](https://trigger.dev/docs/errors-retrying)** - If your task encounters an uncaught error, we automatically attempt to run it again +- **[Checkpointing](https://trigger.dev/docs/how-it-works#the-checkpoint-resume-system)** - Tasks are inherently durable, thanks to our checkpointing feature +- **[Versioning](https://trigger.dev/docs/versioning)** - Atomic versioning allows you to deploy new versions without affecting running tasks +- **[Machines](https://trigger.dev/docs/machines)** - Configure the number of vCPUs and GBs of RAM you want the task to use +- **[Observability & monitoring](https://trigger.dev/product/observability-and-monitoring)** - Monitor every aspect of your tasks' performance with comprehensive logging and visualization tools +- **[Logging & tracing](https://trigger.dev/docs/logging)** - Comprehensive logging and tracing for all your tasks +- **[Tags](https://trigger.dev/docs/tags#tags)** - Attach up to ten tags to each run, allowing you to filter via the dashboard, realtime, and the SDK +- **[Run metadata](https://trigger.dev/docs/runs/metadata#run-metadata)** - Attach metadata to runs which updates as the run progresses and is available to use in your frontend for live updates +- **[Bulk actions](https://trigger.dev/docs/bulk-actions)** - Perform actions on multiple runs simultaneously, including replaying and cancelling +- **[Real-time alerts](https://trigger.dev/docs/troubleshooting-alerts#alerts)** - Choose your preferred notification method for run failures and deployments + +## Write tasks in your codebase Create tasks where they belong: in your codebase. Version control, localhost, test and review like you're already used to. ```ts -import { task } from "@trigger.dev/sdk/v3"; +import { task } from "@trigger.dev/sdk"; //1. You need to export each task export const helloWorld = task({ @@ -58,13 +89,13 @@ Use our SDK to write tasks in your codebase. There's no infrastructure to manage ## Environments -We support `Development`, `Staging`, and `Production` environments, allowing you to test your tasks before deploying them to production. +We support `Development`, `Staging`, `Preview`, and `Production` environments, allowing you to test your tasks before deploying them to production. ## Full visibility of every job run View every task in every run so you can tell exactly what happened. We provide a full trace view of every task run so you can see what happened at every step. -![Trace view image](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/7c1b347f-004c-4482-38a7-3f6fa9c00d00/public) +![Trace view image](https://content.trigger.dev/trace-view.png) # Getting started @@ -73,14 +104,19 @@ The quickest way to get started is to create an account and project in our [web ### Useful links: - [Quick start](https://trigger.dev/docs/quick-start) - get up and running in minutes -- [How it works](https://trigger.dev/docs/v3/how-it-works) - understand how Trigger.dev works under the hood +- [How it works](https://trigger.dev/docs/how-it-works) - understand how Trigger.dev works under the hood - [Guides and examples](https://trigger.dev/docs/guides/introduction) - walk-through guides and code examples for popular frameworks and use cases ## Self-hosting -If you prefer to self-host Trigger.dev, you can follow our [self-hosting guide](https://trigger.dev/docs/v3/open-source-self-hosting#overview). +If you prefer to self-host Trigger.dev, you can follow our [self-hosting guides](https://trigger.dev/docs/self-hosting/overview): -We also have a dedicated self-hosting channel in our [Discord server](https://trigger.dev/discord) for support. +- [Docker self-hosting guide](https://trigger.dev/docs/self-hosting/docker) - use Docker Compose to spin up a Trigger.dev instance +- [Kubernetes self-hosting guide](https://trigger.dev/docs/self-hosting/kubernetes) - use our official Helm chart to deploy Trigger.dev to your Kubernetes cluster + +## Support and community + +We have a large active community in our official [Discord server](https://trigger.dev/discord) for support, including a dedicated channel for self-hosting. ## Development diff --git a/apps/supervisor/package.json b/apps/supervisor/package.json index ae3654927..e9609bf15 100644 --- a/apps/supervisor/package.json +++ b/apps/supervisor/package.json @@ -13,13 +13,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@aws-sdk/client-ecr": "^3.839.0", "@kubernetes/client-node": "^1.0.0", "@trigger.dev/core": "workspace:*", "dockerode": "^4.0.6", "prom-client": "^15.1.0", "socket.io": "4.7.4", "std-env": "^3.8.0", - "zod": "3.23.8" + "zod": "3.25.76" }, "devDependencies": { "@types/dockerode": "^3.3.33" diff --git a/apps/supervisor/src/env.ts b/apps/supervisor/src/env.ts index fd6bd6105..a225af5ea 100644 --- a/apps/supervisor/src/env.ts +++ b/apps/supervisor/src/env.ts @@ -15,7 +15,7 @@ const Env = z.object({ OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(), // set on the runners // Workload API settings (coordinator mode) - the workload API is what the run controller connects to - TRIGGER_WORKLOAD_API_ENABLED: BoolEnv.default("true"), + TRIGGER_WORKLOAD_API_ENABLED: BoolEnv.default(true), TRIGGER_WORKLOAD_API_PROTOCOL: z .string() .transform((s) => z.enum(["http", "https"]).parse(s.toLowerCase())) @@ -32,7 +32,7 @@ const Env = z.object({ RUNNER_PRETTY_LOGS: BoolEnv.default(false), // Dequeue settings (provider mode) - TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"), + TRIGGER_DEQUEUE_ENABLED: BoolEnv.default(true), TRIGGER_DEQUEUE_INTERVAL_MS: z.coerce.number().int().default(250), TRIGGER_DEQUEUE_IDLE_INTERVAL_MS: z.coerce.number().int().default(1000), TRIGGER_DEQUEUE_MAX_RUN_COUNT: z.coerce.number().int().default(10), @@ -76,6 +76,11 @@ const Env = z.object({ KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv KUBERNETES_EPHEMERAL_STORAGE_SIZE_LIMIT: z.string().default("10Gi"), KUBERNETES_EPHEMERAL_STORAGE_SIZE_REQUEST: z.string().default("2Gi"), + KUBERNETES_STRIP_IMAGE_DIGEST: BoolEnv.default(false), + + // Placement tags settings + PLACEMENT_TAGS_ENABLED: BoolEnv.default(false), + PLACEMENT_TAGS_PREFIX: z.string().default("node.cluster.x-k8s.io"), // Metrics METRICS_ENABLED: BoolEnv.default(true), diff --git a/apps/supervisor/src/envUtil.ts b/apps/supervisor/src/envUtil.ts index 95d44d6c4..917f984cc 100644 --- a/apps/supervisor/src/envUtil.ts +++ b/apps/supervisor/src/envUtil.ts @@ -3,7 +3,7 @@ import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLog const logger = new SimpleStructuredLogger("env-util"); -export const BoolEnv = z.preprocess((val) => { +const baseBoolEnv = z.preprocess((val) => { if (typeof val !== "string") { return val; } @@ -11,6 +11,11 @@ export const BoolEnv = z.preprocess((val) => { return ["true", "1"].includes(val.toLowerCase().trim()); }, z.boolean()); +// Create a type-safe version that only accepts boolean defaults +export const BoolEnv = baseBoolEnv as Omit & { + default: (value: boolean) => z.ZodDefault; +}; + export const AdditionalEnvVars = z.preprocess((val) => { if (typeof val !== "string") { return val; diff --git a/apps/supervisor/src/index.ts b/apps/supervisor/src/index.ts index 83fe89c1e..1ed00edad 100644 --- a/apps/supervisor/src/index.ts +++ b/apps/supervisor/src/index.ts @@ -247,6 +247,7 @@ class ManagedSupervisor { nextAttemptNumber: message.run.attemptNumber, snapshotId: message.snapshot.id, snapshotFriendlyId: message.snapshot.friendlyId, + placementTags: message.placementTags, }); // Disabled for now diff --git a/apps/supervisor/src/workloadManager/docker.ts b/apps/supervisor/src/workloadManager/docker.ts index 6aa74a7ec..4ebbe11ca 100644 --- a/apps/supervisor/src/workloadManager/docker.ts +++ b/apps/supervisor/src/workloadManager/docker.ts @@ -8,14 +8,16 @@ import { env } from "../env.js"; import { getDockerHostDomain, getRunnerId, normalizeDockerHostUrl } from "../util.js"; import Docker from "dockerode"; import { tryCatch } from "@trigger.dev/core"; +import { ECRAuthService } from "./ecrAuth.js"; export class DockerWorkloadManager implements WorkloadManager { private readonly logger = new SimpleStructuredLogger("docker-workload-manager"); private readonly docker: Docker; private readonly runnerNetworks: string[]; - private readonly auth?: Docker.AuthConfig; + private readonly staticAuth?: Docker.AuthConfig; private readonly platformOverride?: string; + private readonly ecrAuthService?: ECRAuthService; constructor(private opts: WorkloadManagerOptions) { this.docker = new Docker({ @@ -44,13 +46,18 @@ export class DockerWorkloadManager implements WorkloadManager { url: env.DOCKER_REGISTRY_URL, }); - this.auth = { + this.staticAuth = { username: env.DOCKER_REGISTRY_USERNAME, password: env.DOCKER_REGISTRY_PASSWORD, serveraddress: env.DOCKER_REGISTRY_URL, }; + } else if (ECRAuthService.hasAWSCredentials()) { + this.logger.info("πŸ‹ AWS credentials found, initializing ECR auth service"); + this.ecrAuthService = new ECRAuthService(); } else { - this.logger.warn("πŸ‹ No Docker registry credentials provided, skipping auth"); + this.logger.warn( + "πŸ‹ No Docker registry credentials or AWS credentials provided, skipping auth" + ); } } @@ -160,9 +167,12 @@ export class DockerWorkloadManager implements WorkloadManager { imageArchitecture: inspectResult?.Architecture, }); + // Get auth config (static or ECR) + const authConfig = await this.getAuthConfig(); + // Ensure the image is present const [createImageError, imageResponseReader] = await tryCatch( - this.docker.createImage(this.auth, { + this.docker.createImage(authConfig, { fromImage: imageRef, ...(this.platformOverride ? { platform: this.platformOverride } : {}), }) @@ -216,6 +226,26 @@ export class DockerWorkloadManager implements WorkloadManager { logger.debug("create succeeded", { startResult, containerId: container.id }); } + /** + * Get authentication config for Docker operations + * Uses static credentials if available, otherwise attempts ECR auth + */ + private async getAuthConfig(): Promise { + // Use static credentials if available + if (this.staticAuth) { + return this.staticAuth; + } + + // Use ECR auth if service is available + if (this.ecrAuthService) { + const ecrAuth = await this.ecrAuthService.getAuthConfig(); + return ecrAuth || undefined; + } + + // No auth available + return undefined; + } + private async attachContainerToNetworks({ containerId, networkNames, diff --git a/apps/supervisor/src/workloadManager/ecrAuth.ts b/apps/supervisor/src/workloadManager/ecrAuth.ts new file mode 100644 index 000000000..33e98f631 --- /dev/null +++ b/apps/supervisor/src/workloadManager/ecrAuth.ts @@ -0,0 +1,144 @@ +import { ECRClient, GetAuthorizationTokenCommand } from "@aws-sdk/client-ecr"; +import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger"; +import { tryCatch } from "@trigger.dev/core"; +import Docker from "dockerode"; + +interface ECRTokenCache { + token: string; + username: string; + serverAddress: string; + expiresAt: Date; +} + +export class ECRAuthService { + private readonly logger = new SimpleStructuredLogger("ecr-auth-service"); + private readonly ecrClient: ECRClient; + private tokenCache: ECRTokenCache | null = null; + + constructor() { + this.ecrClient = new ECRClient(); + + this.logger.info("πŸ” ECR Auth Service initialized", { + region: this.ecrClient.config.region, + }); + } + + /** + * Check if we have AWS credentials configured + */ + static hasAWSCredentials(): boolean { + if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) { + return true; + } + + if ( + process.env.AWS_PROFILE || + process.env.AWS_ROLE_ARN || + process.env.AWS_WEB_IDENTITY_TOKEN_FILE + ) { + return true; + } + + return false; + } + + /** + * Check if the current token is still valid with a 10-minute buffer + */ + private isTokenValid(): boolean { + if (!this.tokenCache) { + return false; + } + + const now = new Date(); + const bufferMs = 10 * 60 * 1000; // 10 minute buffer before expiration + return now < new Date(this.tokenCache.expiresAt.getTime() - bufferMs); + } + + /** + * Get a fresh ECR authorization token from AWS + */ + private async fetchNewToken(): Promise { + const [error, response] = await tryCatch( + this.ecrClient.send(new GetAuthorizationTokenCommand({})) + ); + + if (error) { + this.logger.error("Failed to get ECR authorization token", { error }); + return null; + } + + const authData = response.authorizationData?.[0]; + if (!authData?.authorizationToken || !authData.proxyEndpoint) { + this.logger.error("Invalid ECR authorization response", { authData }); + return null; + } + + // Decode the base64 token to get username:password + const decoded = Buffer.from(authData.authorizationToken, "base64").toString("utf-8"); + const [username, password] = decoded.split(":", 2); + + if (!username || !password) { + this.logger.error("Failed to parse ECR authorization token"); + return null; + } + + const expiresAt = authData.expiresAt || new Date(Date.now() + 12 * 60 * 60 * 1000); // Default 12 hours + + const tokenCache: ECRTokenCache = { + token: password, + username, + serverAddress: authData.proxyEndpoint, + expiresAt, + }; + + this.logger.info("πŸ” Successfully fetched ECR token", { + username, + serverAddress: authData.proxyEndpoint, + expiresAt: expiresAt.toISOString(), + }); + + return tokenCache; + } + + /** + * Get ECR auth config for Docker operations + * Returns cached token if valid, otherwise fetches a new one + */ + async getAuthConfig(): Promise { + // Check if cached token is still valid + if (this.isTokenValid()) { + this.logger.debug("Using cached ECR token"); + return { + username: this.tokenCache!.username, + password: this.tokenCache!.token, + serveraddress: this.tokenCache!.serverAddress, + }; + } + + // Fetch new token + this.logger.info("Fetching new ECR authorization token"); + const newToken = await this.fetchNewToken(); + + if (!newToken) { + return null; + } + + // Cache the new token + this.tokenCache = newToken; + + return { + username: newToken.username, + password: newToken.token, + serveraddress: newToken.serverAddress, + }; + } + + /** + * Clear the cached token (useful for testing or forcing refresh) + */ + clearCache(): void { + this.tokenCache = null; + this.logger.debug("ECR token cache cleared"); + } +} diff --git a/apps/supervisor/src/workloadManager/kubernetes.ts b/apps/supervisor/src/workloadManager/kubernetes.ts index 81618e8eb..b38e6c5b4 100644 --- a/apps/supervisor/src/workloadManager/kubernetes.ts +++ b/apps/supervisor/src/workloadManager/kubernetes.ts @@ -4,7 +4,8 @@ import { type WorkloadManagerCreateOptions, type WorkloadManagerOptions, } from "./types.js"; -import type { EnvironmentType, MachinePreset } from "@trigger.dev/core/v3"; +import type { EnvironmentType, MachinePreset, PlacementTag } from "@trigger.dev/core/v3"; +import { PlacementTagProcessor } from "@trigger.dev/core/v3/serverOnly"; import { env } from "../env.js"; import { type K8sApi, createK8sApi, type k8s } from "../clients/kubernetes.js"; import { getRunnerId } from "../util.js"; @@ -17,9 +18,14 @@ export class KubernetesWorkloadManager implements WorkloadManager { private readonly logger = new SimpleStructuredLogger("kubernetes-workload-provider"); private k8s: K8sApi; private namespace = env.KUBERNETES_NAMESPACE; + private placementTagProcessor: PlacementTagProcessor; constructor(private opts: WorkloadManagerOptions) { this.k8s = createK8sApi(); + this.placementTagProcessor = new PlacementTagProcessor({ + enabled: env.PLACEMENT_TAGS_ENABLED, + prefix: env.PLACEMENT_TAGS_PREFIX, + }); if (opts.workloadApiDomain) { this.logger.warn("[KubernetesWorkloadManager] ⚠️ Custom workload API domain", { @@ -28,6 +34,35 @@ export class KubernetesWorkloadManager implements WorkloadManager { } } + private addPlacementTags( + podSpec: Omit, + placementTags?: PlacementTag[] + ): Omit { + const nodeSelector = this.placementTagProcessor.convertToNodeSelector( + placementTags, + podSpec.nodeSelector + ); + + return { + ...podSpec, + nodeSelector, + }; + } + + private stripImageDigest(imageRef: string): string { + if (!env.KUBERNETES_STRIP_IMAGE_DIGEST) { + return imageRef; + } + + const atIndex = imageRef.lastIndexOf("@"); + + if (atIndex === -1) { + return imageRef; + } + + return imageRef.substring(0, atIndex); + } + async create(opts: WorkloadManagerCreateOptions) { this.logger.log("[KubernetesWorkloadManager] Creating container", { opts }); @@ -48,12 +83,12 @@ export class KubernetesWorkloadManager implements WorkloadManager { }, }, spec: { - ...this.#defaultPodSpec, + ...this.addPlacementTags(this.#defaultPodSpec, opts.placementTags), terminationGracePeriodSeconds: 60 * 60, containers: [ { name: "run-controller", - image: opts.image, + image: this.stripImageDigest(opts.image), ports: [ { containerPort: 8000, diff --git a/apps/supervisor/src/workloadManager/types.ts b/apps/supervisor/src/workloadManager/types.ts index b3cd418f1..64573fb3b 100644 --- a/apps/supervisor/src/workloadManager/types.ts +++ b/apps/supervisor/src/workloadManager/types.ts @@ -1,4 +1,4 @@ -import { type EnvironmentType, type MachinePreset } from "@trigger.dev/core/v3"; +import type { EnvironmentType, MachinePreset, PlacementTag } from "@trigger.dev/core/v3"; export interface WorkloadManagerOptions { workloadApiProtocol: "http" | "https"; @@ -23,6 +23,7 @@ export interface WorkloadManagerCreateOptions { version: string; nextAttemptNumber?: number; dequeuedAt: Date; + placementTags?: PlacementTag[]; // identifiers envId: string; envType: EnvironmentType; diff --git a/apps/webapp/.gitignore b/apps/webapp/.gitignore index 8b81451ea..24213b447 100644 --- a/apps/webapp/.gitignore +++ b/apps/webapp/.gitignore @@ -18,4 +18,5 @@ build-storybook.log storybook-static /prisma/seed.js -/prisma/populate.js \ No newline at end of file +/prisma/populate.js +.memory-snapshots \ No newline at end of file diff --git a/apps/webapp/app/assets/icons/CloudProviderIcon.tsx b/apps/webapp/app/assets/icons/CloudProviderIcon.tsx new file mode 100644 index 000000000..6c1625282 --- /dev/null +++ b/apps/webapp/app/assets/icons/CloudProviderIcon.tsx @@ -0,0 +1,76 @@ +export function CloudProviderIcon({ + provider, + className, +}: { + provider: "aws" | "digitalocean" | (string & {}); + className?: string; +}) { + switch (provider) { + case "aws": + return ; + case "digitalocean": + return ; + default: + return null; + } +} + +export function AWS({ className }: { className?: string }) { + return ( + + + + + + ); +} + +export function DigitalOcean({ className }: { className?: string }) { + return ( + + + + + + + + + + + + + + ); +} diff --git a/apps/webapp/app/assets/icons/RegionIcons.tsx b/apps/webapp/app/assets/icons/RegionIcons.tsx new file mode 100644 index 000000000..098d5bc98 --- /dev/null +++ b/apps/webapp/app/assets/icons/RegionIcons.tsx @@ -0,0 +1,106 @@ +export function FlagIcon({ + region, + className, +}: { + region: "usa" | "europe" | (string & {}); + className?: string; +}) { + switch (region) { + case "usa": + return ; + case "europe": + return ; + default: + return null; + } +} + +export function FlagUSA({ className }: { className?: string }) { + return ( + + + + + + + + + + + + + + + + + + ); +} + +export function FlagEurope({ className }: { className?: string }) { + return ( + + + + + + + + + + + + ); +} diff --git a/apps/webapp/app/assets/images/blurred-dashboard-background-menu-bottom.jpg b/apps/webapp/app/assets/images/blurred-dashboard-background-menu-bottom.jpg new file mode 100644 index 000000000..2a993f821 Binary files /dev/null and b/apps/webapp/app/assets/images/blurred-dashboard-background-menu-bottom.jpg differ diff --git a/apps/webapp/app/assets/images/blurred-dashboard-background-menu-top.jpg b/apps/webapp/app/assets/images/blurred-dashboard-background-menu-top.jpg new file mode 100644 index 000000000..8aca8563c Binary files /dev/null and b/apps/webapp/app/assets/images/blurred-dashboard-background-menu-top.jpg differ diff --git a/apps/webapp/app/assets/images/blurred-dashboard-background-table.jpg b/apps/webapp/app/assets/images/blurred-dashboard-background-table.jpg new file mode 100644 index 000000000..a2ae4029f Binary files /dev/null and b/apps/webapp/app/assets/images/blurred-dashboard-background-table.jpg differ diff --git a/apps/webapp/app/components/BackgroundWrapper.tsx b/apps/webapp/app/components/BackgroundWrapper.tsx new file mode 100644 index 000000000..ecff3af6d --- /dev/null +++ b/apps/webapp/app/components/BackgroundWrapper.tsx @@ -0,0 +1,48 @@ +import { type ReactNode } from "react"; +import blurredDashboardBackgroundMenuTop from "~/assets/images/blurred-dashboard-background-menu-top.jpg"; +import blurredDashboardBackgroundMenuBottom from "~/assets/images/blurred-dashboard-background-menu-bottom.jpg"; +import blurredDashboardBackgroundTable from "~/assets/images/blurred-dashboard-background-table.jpg"; + +export function BackgroundWrapper({ children }: { children: ReactNode }) { + return ( +
+ {/* Left menu top background - fixed width 260px, maintains aspect ratio */} +
+ + {/* Left menu bottom background - fixed width 260px, maintains aspect ratio */} +
+ + {/* Right table background - fixed width 2000px, positioned next to menu */} +
+ + {/* Content layer */} +
{children}
+
+ ); +} diff --git a/apps/webapp/app/components/BlankStatePanels.tsx b/apps/webapp/app/components/BlankStatePanels.tsx index 03ebae66e..380a6d990 100644 --- a/apps/webapp/app/components/BlankStatePanels.tsx +++ b/apps/webapp/app/components/BlankStatePanels.tsx @@ -5,6 +5,7 @@ import { ChatBubbleLeftRightIcon, ClockIcon, PlusIcon, + QuestionMarkCircleIcon, RectangleGroupIcon, RectangleStackIcon, ServerStackIcon, @@ -12,7 +13,6 @@ import { } from "@heroicons/react/20/solid"; import { useLocation } from "react-use"; import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons"; -import { TaskIcon } from "~/assets/icons/TaskIcon"; import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon"; import openBulkActionsPanel from "~/assets/images/open-bulk-actions-panel.png"; import selectRunsIndividually from "~/assets/images/select-runs-individually.png"; @@ -32,8 +32,9 @@ import { v3NewProjectAlertPath, v3NewSchedulePath, } from "~/utils/pathBuilder"; +import { AskAI } from "./AskAI"; import { InlineCode } from "./code/InlineCode"; -import { environmentFullTitle } from "./environments/EnvironmentLabel"; +import { environmentFullTitle, EnvironmentIcon } from "./environments/EnvironmentLabel"; import { Feedback } from "./Feedback"; import { EnvironmentSelector } from "./navigation/EnvironmentSelector"; import { Button, LinkButton } from "./primitives/Buttons"; @@ -42,7 +43,13 @@ import { InfoPanel } from "./primitives/InfoPanel"; import { Paragraph } from "./primitives/Paragraph"; import { StepNumber } from "./primitives/StepNumber"; import { TextLink } from "./primitives/TextLink"; -import { InitCommandV3, PackageManagerProvider, TriggerDevStepV3 } from "./SetupCommands"; +import { SimpleTooltip } from "./primitives/Tooltip"; +import { + InitCommandV3, + PackageManagerProvider, + TriggerDeployStep, + TriggerDevStepV3, +} from "./SetupCommands"; import { StepContentContainer } from "./StepContentContainer"; import { V4Badge } from "./V4Badge"; @@ -87,26 +94,60 @@ export function HasNoTasksDev() { export function HasNoTasksDeployed({ environment }: { environment: MinimumEnvironment }) { return ( - - How to deploy tasks - - } - > - - Run the CLI deploy command to - deploy your tasks to the {environmentFullTitle(environment)} environment. - - + +
+
+
+ + Deploy your tasks to {environmentFullTitle(environment)} +
+
+ + } + content="Deploy docs" + /> + + } + content="Troubleshooting docs" + /> + +
+
+ + + + This will deploy your tasks to the {environmentFullTitle(environment)} environment. Read + the full guide. + + + + + + + Read the GitHub Actions guide to + get started. + + + + + This page will automatically refresh when your tasks are deployed. + +
+
); } @@ -477,6 +518,10 @@ export function BranchesNoBranchableEnvironment() { Preview branches in Trigger.dev create isolated environments for testing new features before production. + + You must be on to access preview branches. Read our{" "} + upgrade to v4 guide to learn more. + ); } diff --git a/apps/webapp/app/components/BulkActionFilterSummary.tsx b/apps/webapp/app/components/BulkActionFilterSummary.tsx index b00d77d43..073940d7d 100644 --- a/apps/webapp/app/components/BulkActionFilterSummary.tsx +++ b/apps/webapp/app/components/BulkActionFilterSummary.tsx @@ -11,10 +11,11 @@ import { appliedSummary, dateFromString, timeFilterRenderValues } from "./runs/v import { formatNumber } from "~/utils/numberFormatter"; import { SpinnerWhite } from "./primitives/Spinner"; import { ArrowPathIcon, CheckIcon, XCircleIcon } from "@heroicons/react/20/solid"; +import { XCircleIcon as XCircleIconOutline } from "@heroicons/react/24/outline"; import assertNever from "assert-never"; import { AppliedFilter } from "./primitives/AppliedFilter"; import { runStatusTitle } from "./runs/v3/TaskRunStatus"; -import { type TaskRunStatus } from "@trigger.dev/database"; +import type { TaskRunStatus } from "@trigger.dev/database"; export const BulkActionMode = z.union([z.literal("selected"), z.literal("filter")]); export type BulkActionMode = z.infer; @@ -244,7 +245,7 @@ function Action({ action }: { action: BulkActionAction }) { case "cancel": return ( - + Canceled ); diff --git a/apps/webapp/app/components/CloudProvider.tsx b/apps/webapp/app/components/CloudProvider.tsx new file mode 100644 index 000000000..acf8cff55 --- /dev/null +++ b/apps/webapp/app/components/CloudProvider.tsx @@ -0,0 +1,10 @@ +export function cloudProviderTitle(provider: "aws" | "digitalocean" | (string & {})) { + switch (provider) { + case "aws": + return "Amazon Web Services"; + case "digitalocean": + return "Digital Ocean"; + default: + return provider; + } +} diff --git a/apps/webapp/app/components/SetupCommands.tsx b/apps/webapp/app/components/SetupCommands.tsx index e68273a0d..accb2f65a 100644 --- a/apps/webapp/app/components/SetupCommands.tsx +++ b/apps/webapp/app/components/SetupCommands.tsx @@ -208,3 +208,64 @@ export function TriggerLoginStepV3({ title }: TabsProps) { ); } + +export function TriggerDeployStep({ title, environment }: TabsProps & { environment: { type: string } }) { + const triggerCliTag = useTriggerCliTag(); + const { activePackageManager, setActivePackageManager } = usePackageManager(); + + // Generate the environment flag based on environment type + const getEnvironmentFlag = () => { + switch (environment.type) { + case "STAGING": + return " --env staging"; + case "PREVIEW": + return " --env preview"; + case "PRODUCTION": + default: + return ""; + } + }; + + const environmentFlag = getEnvironmentFlag(); + + return ( + +
+ {title && {title}} + + npm + pnpm + yarn + +
+ + + + + + + + + +
+ ); +} diff --git a/apps/webapp/app/components/Shortcuts.tsx b/apps/webapp/app/components/Shortcuts.tsx index 8349ed970..718166b55 100644 --- a/apps/webapp/app/components/Shortcuts.tsx +++ b/apps/webapp/app/components/Shortcuts.tsx @@ -123,7 +123,7 @@ function ShortcutContent() { - + diff --git a/apps/webapp/app/components/code/CodeBlock.tsx b/apps/webapp/app/components/code/CodeBlock.tsx index ca6200a18..ea1d55e5d 100644 --- a/apps/webapp/app/components/code/CodeBlock.tsx +++ b/apps/webapp/app/components/code/CodeBlock.tsx @@ -86,11 +86,17 @@ const defaultTheme: PrismTheme = { }, }, { - types: ["property", "tag", "boolean", "number", "constant", "symbol", "deleted"], + types: ["property", "tag", "constant", "symbol", "deleted"], style: { color: "#9B99FF", }, }, + { + types: ["boolean", "number"], + style: { + color: "#E5C07B", + }, + }, { types: ["selector", "attr-name", "string", "char", "builtin", "inserted"], style: { @@ -151,12 +157,6 @@ const defaultTheme: PrismTheme = { color: "#F85149", }, }, - { - types: ["boolean"], - style: { - color: "#9B99FF", - }, - }, { types: ["char"], style: { diff --git a/apps/webapp/app/components/code/codeMirrorTheme.ts b/apps/webapp/app/components/code/codeMirrorTheme.ts index 251670513..ed14ea0ac 100644 --- a/apps/webapp/app/components/code/codeMirrorTheme.ts +++ b/apps/webapp/app/components/code/codeMirrorTheme.ts @@ -11,9 +11,10 @@ export function darkTheme(): Extension { ivory = "#abb2bf", stone = "#7d8799", malibu = "#61afef", - sage = "#98c379", + sage = "#afec73", whiskey = "#d19a66", violet = "#c678dd", + lilac = "#9b99ff", darkBackground = "#21252b", highlightBackground = "rgba(71,85,105,0.2)", background = "#121317", @@ -173,7 +174,7 @@ export function darkTheme(): Extension { { tag: tags.keyword, color: violet }, { tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], - color: coral, + color: lilac, }, { tag: [tags.function(tags.variableName), tags.labelName], color: malibu }, { @@ -186,6 +187,7 @@ export function darkTheme(): Extension { tags.typeName, tags.className, tags.number, + tags.bool, tags.changed, tags.annotation, tags.modifier, @@ -213,7 +215,7 @@ export function darkTheme(): Extension { { tag: tags.link, color: stone, textDecoration: "underline" }, { tag: tags.heading, fontWeight: "bold", color: coral }, { - tag: [tags.atom, tags.bool, tags.special(tags.variableName)], + tag: [tags.atom, tags.special(tags.variableName)], color: whiskey, }, { diff --git a/apps/webapp/app/components/layout/AppLayout.tsx b/apps/webapp/app/components/layout/AppLayout.tsx index e45836e49..0793c52ca 100644 --- a/apps/webapp/app/components/layout/AppLayout.tsx +++ b/apps/webapp/app/components/layout/AppLayout.tsx @@ -1,8 +1,18 @@ import { cn } from "~/utils/cn"; /** This container is used to surround the entire app, it correctly places the nav bar */ -export function AppContainer({ children }: { children: React.ReactNode }) { - return
{children}
; +export function AppContainer({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); } export function MainBody({ children }: { children: React.ReactNode }) { diff --git a/apps/webapp/app/components/navigation/AccountSideMenu.tsx b/apps/webapp/app/components/navigation/AccountSideMenu.tsx index 374b2f316..30bf2c192 100644 --- a/apps/webapp/app/components/navigation/AccountSideMenu.tsx +++ b/apps/webapp/app/components/navigation/AccountSideMenu.tsx @@ -1,6 +1,6 @@ import { LockClosedIcon, ShieldCheckIcon, UserCircleIcon } from "@heroicons/react/20/solid"; import { ArrowLeftIcon } from "@heroicons/react/24/solid"; -import { type User } from "@trigger.dev/database"; +import type { User } from "@trigger.dev/database"; import { cn } from "~/utils/cn"; import { accountPath, diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx index de7c045ad..4e0adf979 100644 --- a/apps/webapp/app/components/navigation/SideMenu.tsx +++ b/apps/webapp/app/components/navigation/SideMenu.tsx @@ -10,6 +10,7 @@ import { CogIcon, FolderIcon, FolderOpenIcon, + GlobeAmericasIcon, IdentificationIcon, KeyIcon, PlusIcon, @@ -22,6 +23,7 @@ import { useNavigation } from "@remix-run/react"; import { useEffect, useRef, useState, type ReactNode } from "react"; import simplur from "simplur"; import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons"; +import { ListCheckedIcon } from "~/assets/icons/ListCheckedIcon"; import { RunsIconExtraSmall } from "~/assets/icons/RunsIcon"; import { TaskIconSmall } from "~/assets/icons/TaskIcon"; import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon"; @@ -46,6 +48,7 @@ import { organizationPath, organizationSettingsPath, organizationTeamPath, + regionsPath, v3ApiKeysPath, v3BatchesPath, v3BillingPath, @@ -87,8 +90,6 @@ import { HelpAndFeedback } from "./HelpAndFeedbackPopover"; import { SideMenuHeader } from "./SideMenuHeader"; import { SideMenuItem } from "./SideMenuItem"; import { SideMenuSection } from "./SideMenuSection"; -import { ListChecks } from "lucide-react"; -import { ListCheckedIcon } from "~/assets/icons/ListCheckedIcon"; type SideMenuUser = Pick & { isImpersonating: boolean }; export type SideMenuProject = Pick< @@ -143,16 +144,18 @@ export function SideMenu({ >
- +
+ +
{isAdmin && !user.isImpersonating ? ( @@ -309,6 +312,14 @@ export function SideMenu({ data-action="preview-branches" badge={} /> + } + /> ); diff --git a/apps/webapp/app/components/runs/v3/BatchStatus.tsx b/apps/webapp/app/components/runs/v3/BatchStatus.tsx index c67b1b401..ed47ab672 100644 --- a/apps/webapp/app/components/runs/v3/BatchStatus.tsx +++ b/apps/webapp/app/components/runs/v3/BatchStatus.tsx @@ -1,5 +1,5 @@ import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/20/solid"; -import { BatchTaskRunStatus } from "@trigger.dev/database"; +import type { BatchTaskRunStatus } from "@trigger.dev/database"; import assertNever from "assert-never"; import { Spinner } from "~/components/primitives/Spinner"; import { cn } from "~/utils/cn"; diff --git a/apps/webapp/app/components/runs/v3/BulkAction.tsx b/apps/webapp/app/components/runs/v3/BulkAction.tsx index d9f0a7844..3c25aa440 100644 --- a/apps/webapp/app/components/runs/v3/BulkAction.tsx +++ b/apps/webapp/app/components/runs/v3/BulkAction.tsx @@ -1,5 +1,5 @@ import { ArrowPathIcon, CheckCircleIcon, NoSymbolIcon } from "@heroicons/react/20/solid"; -import { BulkActionStatus, type BulkActionType } from "@trigger.dev/database"; +import type { BulkActionStatus, BulkActionType } from "@trigger.dev/database"; import assertNever from "assert-never"; import { Spinner } from "~/components/primitives/Spinner"; import { cn } from "~/utils/cn"; diff --git a/apps/webapp/app/components/runs/v3/DeploymentError.tsx b/apps/webapp/app/components/runs/v3/DeploymentError.tsx index 517f91789..11ee62dfa 100644 --- a/apps/webapp/app/components/runs/v3/DeploymentError.tsx +++ b/apps/webapp/app/components/runs/v3/DeploymentError.tsx @@ -9,7 +9,7 @@ type DeploymentErrorProps = { export function DeploymentError({ errorData }: DeploymentErrorProps) { return ( -
+
{errorData.message && {errorData.message}} {errorData.stack && ( @@ -18,6 +18,7 @@ export function DeploymentError({ errorData }: DeploymentErrorProps) { showLineNumbers={false} code={errorData.stack} maxLines={20} + showTextWrapping /> )} {errorData.stderr && ( @@ -28,6 +29,7 @@ export function DeploymentError({ errorData }: DeploymentErrorProps) { showLineNumbers={false} code={errorData.stderr} maxLines={20} + showTextWrapping /> )} diff --git a/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx b/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx index dbfaf12d0..6adea62f2 100644 --- a/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx +++ b/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx @@ -4,7 +4,7 @@ import { NoSymbolIcon, XCircleIcon, } from "@heroicons/react/20/solid"; -import { WorkerDeploymentStatus } from "@trigger.dev/database"; +import type { WorkerDeploymentStatus } from "@trigger.dev/database"; import assertNever from "assert-never"; import { Spinner } from "~/components/primitives/Spinner"; import { cn } from "~/utils/cn"; diff --git a/apps/webapp/app/components/runs/v3/PacketDisplay.tsx b/apps/webapp/app/components/runs/v3/PacketDisplay.tsx index 5d5878a93..24a9b66b6 100644 --- a/apps/webapp/app/components/runs/v3/PacketDisplay.tsx +++ b/apps/webapp/app/components/runs/v3/PacketDisplay.tsx @@ -1,7 +1,11 @@ import { CloudArrowDownIcon } from "@heroicons/react/20/solid"; import { CodeBlock } from "~/components/code/CodeBlock"; +import { InlineCode } from "~/components/code/InlineCode"; import { LinkButton } from "~/components/primitives/Buttons"; +import { Header3 } from "~/components/primitives/Headers"; import { Paragraph } from "~/components/primitives/Paragraph"; +import { TextLink } from "~/components/primitives/TextLink"; +import { docsPath } from "~/utils/pathBuilder"; export function PacketDisplay({ data, @@ -15,13 +19,26 @@ export function PacketDisplay({ switch (dataType) { case "application/store": { return ( -
- - {title} +
+ {title} + + This {title.toLowerCase()} exceeded the size limit and was automatically offloaded to + object storage. You can retrieve it using{" "} + runs.retrieve or download it directly + below. Learn more + . - - Download - +
+ + Download {title.toLowerCase()} + +
); } @@ -33,6 +50,7 @@ export function PacketDisplay({ code={data} maxLines={20} showLineNumbers={false} + showTextWrapping /> ); } diff --git a/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx b/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx index 4dd0e7f2f..3a30e0cb3 100644 --- a/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx +++ b/apps/webapp/app/components/runs/v3/ScheduleFilters.tsx @@ -1,6 +1,5 @@ import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/20/solid"; import { useNavigate } from "@remix-run/react"; -import { type RuntimeEnvironment } from "@trigger.dev/database"; import { useCallback } from "react"; import { z } from "zod"; import { Input } from "~/components/primitives/Input"; diff --git a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx index 20eb3901f..ae5a6b28a 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx @@ -11,7 +11,7 @@ import { TrashIcon, XCircleIcon, } from "@heroicons/react/20/solid"; -import { type TaskRunStatus } from "@trigger.dev/database"; +import type { TaskRunStatus } from "@trigger.dev/database"; import assertNever from "assert-never"; import { HourglassIcon } from "lucide-react"; import { TimedOutIcon } from "~/assets/icons/TimedOutIcon"; diff --git a/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx b/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx index 0dfc5f10e..8d81e2f36 100644 --- a/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx +++ b/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx @@ -1,5 +1,5 @@ import { ClockIcon } from "@heroicons/react/20/solid"; -import { type TaskTriggerSource } from "@trigger.dev/database"; +import type { TaskTriggerSource } from "@trigger.dev/database"; import { TaskIconSmall } from "~/assets/icons/TaskIcon"; import { cn } from "~/utils/cn"; diff --git a/apps/webapp/app/db.server.ts b/apps/webapp/app/db.server.ts index c99b2e2c4..47b67a1a4 100644 --- a/apps/webapp/app/db.server.ts +++ b/apps/webapp/app/db.server.ts @@ -1,10 +1,11 @@ import { Prisma, PrismaClient, - PrismaClientOrTransaction, - PrismaReplicaClient, - PrismaTransactionClient, - PrismaTransactionOptions, + $transaction as transac, + type PrismaClientOrTransaction, + type PrismaReplicaClient, + type PrismaTransactionClient, + type PrismaTransactionOptions, } from "@trigger.dev/database"; import invariant from "tiny-invariant"; import { z } from "zod"; @@ -12,9 +13,9 @@ import { env } from "./env.server"; import { logger } from "./services/logger.server"; import { isValidDatabaseUrl } from "./utils/db"; import { singleton } from "./utils/singleton"; -import { $transaction as transac } from "@trigger.dev/database"; import { startActiveSpan } from "./v3/tracer.server"; import { Span } from "@opentelemetry/api"; +import { queryPerformanceMonitor } from "./utils/queryPerformanceMonitor.server"; export type { PrismaTransactionClient, @@ -153,13 +154,19 @@ function getClient() { }, ] : []) satisfies Prisma.LogDefinition[]), - // verbose - ...((process.env.VERBOSE_PRISMA_LOGS === "1" + // Query performance monitoring + ...((process.env.VERBOSE_PRISMA_LOGS === "1" || + process.env.VERY_SLOW_QUERY_THRESHOLD_MS !== undefined ? [ { emit: "event", level: "query", }, + ] + : []) satisfies Prisma.LogDefinition[]), + // verbose + ...((process.env.VERBOSE_PRISMA_LOGS === "1" + ? [ { emit: "stdout", level: "query", @@ -201,10 +208,16 @@ function getClient() { message: log.message, target: log.target, }, + ignoreError: true, }); }); } + // Add query performance monitoring + client.$on("query", (log) => { + queryPerformanceMonitor.onQuery("writer", log); + }); + // connect eagerly client.$connect(); @@ -264,13 +277,19 @@ function getReplicaClient() { }, ] : []) satisfies Prisma.LogDefinition[]), - // verbose - ...((process.env.VERBOSE_PRISMA_LOGS === "1" + // Query performance monitoring + ...((process.env.VERBOSE_PRISMA_LOGS === "1" || + process.env.VERY_SLOW_QUERY_THRESHOLD_MS !== undefined ? [ { emit: "event", level: "query", }, + ] + : []) satisfies Prisma.LogDefinition[]), + // verbose + ...((process.env.VERBOSE_PRISMA_LOGS === "1" + ? [ { emit: "stdout", level: "query", @@ -316,6 +335,11 @@ function getReplicaClient() { }); } + // Add query performance monitoring for replica client + replicaClient.$on("query", (log) => { + queryPerformanceMonitor.onQuery("replica", log); + }); + // connect eagerly replicaClient.$connect(); diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index eba67df32..f20c02f6a 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -23,7 +23,12 @@ const EnvironmentSchema = z.object({ DATABASE_READ_REPLICA_URL: z.string().optional(), SESSION_SECRET: z.string(), MAGIC_LINK_SECRET: z.string(), - ENCRYPTION_KEY: z.string(), + ENCRYPTION_KEY: z + .string() + .refine( + (val) => Buffer.from(val, "utf8").length === 32, + "ENCRYPTION_KEY must be exactly 32 bytes" + ), WHITELISTED_EMAILS: z .string() .refine(isValidRegex, "WHITELISTED_EMAILS must be a valid regex.") @@ -230,7 +235,7 @@ const EnvironmentSchema = z.object({ DEPOT_ORG_ID: z.string().optional(), DEPOT_REGION: z.string().default("us-east-1"), - // Deployment registry + // Deployment registry (v3) DEPLOY_REGISTRY_HOST: z.string().min(1), DEPLOY_REGISTRY_USERNAME: z.string().optional(), DEPLOY_REGISTRY_PASSWORD: z.string().optional(), @@ -238,6 +243,39 @@ const EnvironmentSchema = z.object({ DEPLOY_REGISTRY_ECR_TAGS: z.string().optional(), // csv, for example: "key1=value1,key2=value2" DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z.string().optional(), DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z.string().optional(), + + // Deployment registry (v4) - falls back to v3 registry if not specified + V4_DEPLOY_REGISTRY_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_HOST) + .pipe(z.string().min(1)), // Ensure final type is required string + V4_DEPLOY_REGISTRY_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_USERNAME), + V4_DEPLOY_REGISTRY_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_PASSWORD), + V4_DEPLOY_REGISTRY_NAMESPACE: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_NAMESPACE) + .pipe(z.string().min(1).default("trigger")), // Ensure final type is required string + V4_DEPLOY_REGISTRY_ECR_TAGS: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_TAGS), + V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN), + V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID), + DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"), DEPLOY_TIMEOUT_MS: z.coerce .number() @@ -314,6 +352,9 @@ const EnvironmentSchema = z.object({ INTERNAL_OTEL_METRIC_EXPORTER_AUTH_HEADERS: z.string().optional(), INTERNAL_OTEL_METRIC_EXPORTER_ENABLED: z.string().default("0"), INTERNAL_OTEL_METRIC_EXPORTER_INTERVAL_MS: z.coerce.number().int().default(30_000), + INTERNAL_OTEL_HOST_METRICS_ENABLED: BoolEnv.default(true), + INTERNAL_OTEL_NODEJS_METRICS_ENABLED: BoolEnv.default(true), + INTERNAL_OTEL_ADDITIONAL_DETECTORS_ENABLED: BoolEnv.default(true), ORG_SLACK_INTEGRATION_CLIENT_ID: z.string().optional(), ORG_SLACK_INTEGRATION_CLIENT_SECRET: z.string().optional(), @@ -377,6 +418,19 @@ const EnvironmentSchema = z.object({ MARQS_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25), MARQS_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0), MARQS_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(), + MARQS_SHARED_WORKER_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(250), + MARQS_SHARED_WORKER_QUEUE_MAX_MESSAGE_COUNT: z.coerce.number().int().default(10), + + MARQS_SHARED_WORKER_QUEUE_EAGER_DEQUEUE_ENABLED: z.string().default("0"), + MARQS_WORKER_ENABLED: z.string().default("0"), + MARQS_WORKER_COUNT: z.coerce.number().int().default(2), + MARQS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), + MARQS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(5), + MARQS_WORKER_POLL_INTERVAL_MS: z.coerce.number().int().default(100), + MARQS_WORKER_IMMEDIATE_POLL_INTERVAL_MS: z.coerce.number().int().default(100), + MARQS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + MARQS_SHARED_WORKER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10), + MARQS_SHARED_WORKER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(5_000), PROD_TASK_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), @@ -403,6 +457,7 @@ const EnvironmentSchema = z.object({ EVENT_LOOP_MONITOR_ENABLED: z.string().default("1"), MAXIMUM_LIVE_RELOADING_EVENTS: z.coerce.number().int().default(1000), MAXIMUM_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000), + MAXIMUM_TRACE_DETAILED_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(10_000), TASK_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().default(524_288), // 512KB TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728), // 3MB BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(1_000_000), // 1MB @@ -449,7 +504,10 @@ const EnvironmentSchema = z.object({ RUN_ENGINE_RETRY_WARM_START_THRESHOLD_MS: z.coerce.number().int().default(30_000), RUN_ENGINE_PROCESS_WORKER_QUEUE_DEBOUNCE_MS: z.coerce.number().int().default(200), RUN_ENGINE_DEQUEUE_BLOCKING_TIMEOUT_SECONDS: z.coerce.number().int().default(10), - RUN_ENGINE_MASTER_QUEUE_CONSUMERS_INTERVAL_MS: z.coerce.number().int().default(500), + RUN_ENGINE_MASTER_QUEUE_CONSUMERS_INTERVAL_MS: z.coerce.number().int().default(1000), + RUN_ENGINE_MASTER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(10_000), + RUN_ENGINE_MASTER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10), + RUN_ENGINE_MASTER_QUEUE_CONSUMER_DEQUEUE_COUNT: z.coerce.number().int().default(10), RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_SCHEDULE: z.string().optional(), RUN_ENGINE_CONCURRENCY_SWEEPER_PROCESS_MARKED_SCHEDULE: z.string().optional(), RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_JITTER_IN_MS: z.coerce.number().int().optional(), @@ -464,6 +522,14 @@ const EnvironmentSchema = z.object({ RUN_ENGINE_RUN_LOCK_JITTER_FACTOR: z.coerce.number().default(0.15), RUN_ENGINE_RUN_LOCK_MAX_TOTAL_WAIT_TIME: z.coerce.number().int().default(15000), + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_COUNT: z.coerce.number().int().default(12), + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_DELAY_MS: z.coerce + .number() + .int() + .default(60_000 * 60 * 6), + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_INITIAL_DELAY_MS: z.coerce.number().int().default(60_000), + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_FACTOR: z.coerce.number().default(2), + RUN_ENGINE_WORKER_REDIS_HOST: z .string() .optional() @@ -728,6 +794,8 @@ const EnvironmentSchema = z.object({ .int() .default(60_000 * 5), // 5 minutes + BATCH_TRIGGER_CACHED_RUNS_CHECK_ENABLED: BoolEnv.default(false), + BATCH_TRIGGER_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), BATCH_TRIGGER_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), BATCH_TRIGGER_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), @@ -1024,6 +1092,10 @@ const EnvironmentSchema = z.object({ // AI Run Filter AI_RUN_FILTER_MODEL: z.string().optional(), + + EVENT_LOOP_MONITOR_THRESHOLD_MS: z.coerce.number().int().default(100), + + VERY_SLOW_QUERY_THRESHOLD_MS: z.coerce.number().int().optional(), }); export type Environment = z.infer; diff --git a/apps/webapp/app/eventLoopMonitor.server.ts b/apps/webapp/app/eventLoopMonitor.server.ts index db25a2813..42e982bdb 100644 --- a/apps/webapp/app/eventLoopMonitor.server.ts +++ b/apps/webapp/app/eventLoopMonitor.server.ts @@ -1,10 +1,12 @@ import { createHook } from "node:async_hooks"; import { singleton } from "./utils/singleton"; import { tracer } from "./v3/tracer.server"; +import { env } from "./env.server"; +import { context, Context } from "@opentelemetry/api"; -const THRESHOLD_NS = 1e8; // 100ms +const THRESHOLD_NS = env.EVENT_LOOP_MONITOR_THRESHOLD_MS * 1e6; -const cache = new Map(); +const cache = new Map(); function init(asyncId: number, type: string, triggerAsyncId: number, resource: any) { cache.set(asyncId, { @@ -26,6 +28,7 @@ function before(asyncId: number) { cache.set(asyncId, { ...cached, start: process.hrtime(), + parentCtx: context.active(), }); } @@ -47,13 +50,17 @@ function after(asyncId: number) { if (diffNs > THRESHOLD_NS) { const time = diffNs / 1e6; // in ms - const newSpan = tracer.startSpan("event-loop-blocked", { - startTime: new Date(new Date().getTime() - time), - attributes: { - asyncType: cached.type, - label: "EventLoopMonitor", + const newSpan = tracer.startSpan( + "event-loop-blocked", + { + startTime: new Date(new Date().getTime() - time), + attributes: { + asyncType: cached.type, + label: "EventLoopMonitor", + }, }, - }); + cached.parentCtx + ); newSpan.end(); } diff --git a/apps/webapp/app/models/runtimeEnvironment.server.ts b/apps/webapp/app/models/runtimeEnvironment.server.ts index adde2db5c..67119acd0 100644 --- a/apps/webapp/app/models/runtimeEnvironment.server.ts +++ b/apps/webapp/app/models/runtimeEnvironment.server.ts @@ -37,7 +37,7 @@ export async function findEnvironmentByApiKey( if (environment.type === "PREVIEW") { if (!branchName) { - logger.error("findEnvironmentByApiKey(): Preview env with no branch name provided", { + logger.warn("findEnvironmentByApiKey(): Preview env with no branch name provided", { environmentId: environment.id, }); return null; diff --git a/apps/webapp/app/presenters/v3/BranchesPresenter.server.ts b/apps/webapp/app/presenters/v3/BranchesPresenter.server.ts index 9951fe4b4..4aafc1844 100644 --- a/apps/webapp/app/presenters/v3/BranchesPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/BranchesPresenter.server.ts @@ -1,6 +1,6 @@ import { GitMeta } from "@trigger.dev/core/v3"; import { type z } from "zod"; -import { Prisma, type PrismaClient, prisma } from "~/db.server"; +import { type Prisma, type PrismaClient, prisma } from "~/db.server"; import { type Project } from "~/models/project.server"; import { type User } from "~/models/user.server"; import { type BranchesOptions } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route"; @@ -38,6 +38,13 @@ export type GitMetaLinks = { commitMessage: string; /** The commit author */ commitAuthor: string; + + /** The git provider, e.g., `github` */ + provider?: string; + + source?: "trigger_github_app" | "github_actions" | "local"; + ghUsername?: string; + ghUserAvatarUrl?: string; }; export class BranchesPresenter { @@ -239,5 +246,9 @@ export function processGitMetadata(data: Prisma.JsonValue): GitMetaLinks | null isDirty: parsed.data.dirty ?? false, commitMessage: parsed.data.commitMessage ?? "", commitAuthor: parsed.data.commitAuthorName ?? "", + provider: parsed.data.provider, + source: parsed.data.source, + ghUsername: parsed.data.ghUsername, + ghUserAvatarUrl: parsed.data.ghUserAvatarUrl, }; } diff --git a/apps/webapp/app/presenters/v3/RegionsPresenter.server.ts b/apps/webapp/app/presenters/v3/RegionsPresenter.server.ts new file mode 100644 index 000000000..c304597bb --- /dev/null +++ b/apps/webapp/app/presenters/v3/RegionsPresenter.server.ts @@ -0,0 +1,159 @@ +import { type Project } from "~/models/project.server"; +import { type User } from "~/models/user.server"; +import { FEATURE_FLAG, makeFlags } from "~/v3/featureFlags.server"; +import { BasePresenter } from "./basePresenter.server"; +import { getCurrentPlan } from "~/services/platform.v3.server"; + +export type Region = { + id: string; + name: string; + description?: string; + cloudProvider?: string; + location?: string; + staticIPs?: string | null; + isDefault: boolean; + isHidden: boolean; +}; + +export class RegionsPresenter extends BasePresenter { + public async call({ + userId, + projectSlug, + isAdmin = false, + }: { + userId: User["id"]; + projectSlug: Project["slug"]; + isAdmin?: boolean; + }) { + const project = await this._replica.project.findFirst({ + select: { + id: true, + organizationId: true, + defaultWorkerGroupId: true, + allowedWorkerQueues: true, + }, + where: { + slug: projectSlug, + organization: { + members: { + some: { + userId, + }, + }, + }, + }, + }); + + if (!project) { + throw new Error("Project not found"); + } + + const getFlag = makeFlags(this._replica); + const defaultWorkerInstanceGroupId = await getFlag({ + key: FEATURE_FLAG.defaultWorkerInstanceGroupId, + }); + + if (!defaultWorkerInstanceGroupId) { + throw new Error("Default worker instance group not found"); + } + + const visibleRegions = await this._replica.workerInstanceGroup.findMany({ + select: { + id: true, + name: true, + description: true, + cloudProvider: true, + location: true, + staticIPs: true, + hidden: true, + }, + where: isAdmin + ? undefined + : // Hide hidden unless they're allowed to use them + project.allowedWorkerQueues.length > 0 + ? { + masterQueue: { in: project.allowedWorkerQueues }, + } + : { + hidden: false, + }, + orderBy: { + name: "asc", + }, + }); + + const regions: Region[] = visibleRegions.map((region) => ({ + id: region.id, + name: region.name, + description: region.description ?? undefined, + cloudProvider: region.cloudProvider ?? undefined, + location: region.location ?? undefined, + staticIPs: region.staticIPs ?? undefined, + isDefault: region.id === defaultWorkerInstanceGroupId, + isHidden: region.hidden, + })); + + if (project.defaultWorkerGroupId) { + const defaultWorkerGroup = await this._replica.workerInstanceGroup.findFirst({ + select: { + id: true, + name: true, + description: true, + cloudProvider: true, + location: true, + staticIPs: true, + hidden: true, + }, + where: { id: project.defaultWorkerGroupId }, + }); + + if (defaultWorkerGroup) { + // Unset the default region + const defaultRegion = regions.find((region) => region.isDefault); + if (defaultRegion) { + defaultRegion.isDefault = false; + } + + regions.push({ + id: defaultWorkerGroup.id, + name: defaultWorkerGroup.name, + description: defaultWorkerGroup.description ?? undefined, + cloudProvider: defaultWorkerGroup.cloudProvider ?? undefined, + location: defaultWorkerGroup.location ?? undefined, + staticIPs: defaultWorkerGroup.staticIPs ?? undefined, + isDefault: true, + isHidden: defaultWorkerGroup.hidden, + }); + } + } + + // Default first + const sorted = regions.sort((a, b) => { + if (a.isDefault) return -1; + if (b.isDefault) return 1; + return a.name.localeCompare(b.name); + }); + + // Remove later duplicates + let unique = sorted.filter((region, index, self) => { + const firstIndex = self.findIndex((t) => t.id === region.id); + return index === firstIndex; + }); + + // Don't show static IPs for free users + // Even if they had the IPs they wouldn't work, but this makes it less confusing + const currentPlan = await getCurrentPlan(project.organizationId); + const isPaying = currentPlan?.v3Subscription.isPaying === true; + if (!isPaying) { + unique = unique.map((region) => ({ + ...region, + staticIPs: region.staticIPs ? null : undefined, + })); + } + + return { + regions: unique.sort((a, b) => a.name.localeCompare(b.name)), + isPaying, + }; + } +} diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index 0d9065e28..a00ffa3f3 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -1,12 +1,13 @@ import { - MachinePreset, + type MachinePreset, prettyPrintPacket, SemanticInternalAttributes, - TaskRunContext, + type TaskRunContext, TaskRunError, - V3TaskRunContext, + TriggerTraceContext, + type V3TaskRunContext, } from "@trigger.dev/core/v3"; -import { AttemptId, getMaxDuration } from "@trigger.dev/core/v3/isomorphic"; +import { AttemptId, getMaxDuration, parseTraceparent } from "@trigger.dev/core/v3/isomorphic"; import { RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus"; import { logger } from "~/services/logger.server"; import { eventRepository, rehydrateAttribute } from "~/v3/eventRepository.server"; @@ -173,6 +174,24 @@ export class SpanPresenter extends BasePresenter { const context = await this.#getTaskRunContext({ run, machine: machine ?? undefined }); + const externalTraceId = this.#getExternalTraceId(run.traceContext); + + let region: { name: string; location: string | null } | null = null; + + if (run.runtimeEnvironment.type !== "DEVELOPMENT" && run.engine !== "V1") { + const workerGroup = await this._replica.workerInstanceGroup.findFirst({ + select: { + name: true, + location: true, + }, + where: { + masterQueue: run.workerQueue, + }, + }); + + region = workerGroup ?? null; + } + return { id: run.id, friendlyId: run.friendlyId, @@ -230,10 +249,12 @@ export class SpanPresenter extends BasePresenter { maxDurationInSeconds: getMaxDuration(run.maxDurationInSeconds), batch: run.batch ? { friendlyId: run.batch.friendlyId } : undefined, engine: run.engine, + region, workerQueue: run.workerQueue, spanId: run.spanId, isCached: !!span.originalRun, machinePreset: machine?.name, + externalTraceId, }; } @@ -272,6 +293,7 @@ export class SpanPresenter extends BasePresenter { id: true, spanId: true, traceId: true, + traceContext: true, //metadata number: true, taskIdentifier: true, @@ -574,4 +596,26 @@ export class SpanPresenter extends BasePresenter { async #getV4TaskRunContext({ run }: { run: FindRunResult }): Promise { return engine.resolveTaskRunContext(run.id); } + + #getExternalTraceId(traceContext: unknown) { + if (!traceContext) { + return; + } + + const parsedTraceContext = TriggerTraceContext.safeParse(traceContext); + + if (!parsedTraceContext.success) { + return; + } + + const externalTraceparent = parsedTraceContext.data.external?.traceparent; + + if (!externalTraceparent) { + return; + } + + const parsedTraceparent = parseTraceparent(externalTraceparent); + + return parsedTraceparent?.traceId; + } } diff --git a/apps/webapp/app/root.tsx b/apps/webapp/app/root.tsx index f46d3a65f..d481a69ab 100644 --- a/apps/webapp/app/root.tsx +++ b/apps/webapp/app/root.tsx @@ -23,7 +23,7 @@ export const links: LinksFunction = () => { export const meta: MetaFunction = ({ data }) => { const typedData = data as UseDataFunctionReturn; return [ - { title: `Trigger.dev${appEnvTitleTag(typedData.appEnv)}` }, + { title: typedData?.appEnv ? `Trigger.dev${appEnvTitleTag(typedData.appEnv)}` : "Trigger.dev" }, { name: "viewport", content: "width=1024, initial-scale=1", @@ -84,11 +84,13 @@ export function ErrorBoundary() { - - - - - + + + + + + + diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx index f3bf509b0..8f8215305 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsx @@ -143,7 +143,7 @@ export default function Page() { const emailFields = useFieldList(form.ref, emails); return ( - +
} @@ -203,7 +203,7 @@ export default function Page() { } cancelButton={ - + Cancel } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx index 29efff3dc..5d6a947a4 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx @@ -14,7 +14,7 @@ import { Link, useRevalidator, useSubmit } from "@remix-run/react"; import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { DiscordIcon } from "@trigger.dev/companyicons"; import { formatDurationMilliseconds } from "@trigger.dev/core/v3"; -import { type TaskRunStatus } from "@trigger.dev/database"; +import type { TaskRunStatus } from "@trigger.dev/database"; import { Fragment, Suspense, useEffect, useState } from "react"; import { Bar, BarChart, ResponsiveContainer, Tooltip, type TooltipProps } from "recharts"; import { TypedAwait, typeddefer, useTypedLoaderData } from "remix-typedjson"; @@ -77,7 +77,6 @@ import { type TaskActivity, type TaskListItem, taskListPresenter, - TaskListPresenter, } from "~/presenters/v3/TaskListPresenter.server"; import { getUsefulLinksPreference, @@ -372,12 +371,13 @@ export default function Page() { icon={RunsIcon} to={path} title="View runs" - leadingIconClassName="text-teal-500" + leadingIconClassName="text-runs" /> } @@ -411,7 +411,7 @@ export default function Page() { ) : ( - + )} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx index a4debb832..1bedd30d0 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx @@ -13,7 +13,7 @@ import { import { Form, type MetaFunction, Outlet, useActionData, useNavigation } from "@remix-run/react"; import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { SlackIcon } from "@trigger.dev/companyicons"; -import { type ProjectAlertChannelType, type ProjectAlertType } from "@trigger.dev/database"; +import type { ProjectAlertChannelType, ProjectAlertType } from "@trigger.dev/database"; import assertNever from "assert-never"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx index 8aa34624d..197257624 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx @@ -2,7 +2,7 @@ import { ArrowPathIcon } from "@heroicons/react/20/solid"; import { Form, useRevalidator } from "@remix-run/react"; import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { tryCatch } from "@trigger.dev/core"; -import { BulkActionStatus, BulkActionType } from "@trigger.dev/database"; +import type { BulkActionType } from "@trigger.dev/database"; import { motion } from "framer-motion"; import { useEffect } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; @@ -135,7 +135,7 @@ export default function Page() { const project = useProject(); const environment = useEnvironment(); - const disabled = bulkAction.status !== BulkActionStatus.PENDING; + const disabled = bulkAction.status !== "PENDING"; const streamedEvents = useEventSource( `/resources/orgs/${organization.slug}/projects/${project.slug}/env/${environment.id}/runs/bulkaction/${bulkAction.friendlyId}/stream`, @@ -239,7 +239,7 @@ export default function Page() { @@ -327,7 +327,7 @@ function Meter({ type, successCount, failureCount, totalCount }: MeterProps) {
{formatNumber(failureCount)} {typeText(type)} failed{" "} - {type === BulkActionType.CANCEL ? " (already finished)" : ""} + {type === "CANCEL" ? " (already finished)" : ""}
@@ -337,9 +337,9 @@ function Meter({ type, successCount, failureCount, totalCount }: MeterProps) { function typeText(type: BulkActionType) { switch (type) { - case BulkActionType.CANCEL: + case "CANCEL": return "canceled"; - case BulkActionType.REPLAY: + case "REPLAY": return "replayed"; } } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx index dbb0ede67..4ae43e3e8 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx @@ -245,21 +245,20 @@ export default function Page() { )} - {deployment.deployedBy ? ( -
- - - {deployment.deployedBy.name ?? - deployment.deployedBy.displayName} - -
+ {deployment.git?.source === "trigger_github_app" ? ( + + ) : deployment.deployedBy ? ( + ) : ( "–" )} @@ -318,6 +317,15 @@ export default function Page() { ); } +function UserTag({ name, avatarUrl }: { name: string; avatarUrl?: string }) { + return ( +
+ + {name} +
+ ); +} + function DeploymentActionsCell({ deployment, path, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx index 990cb8c2a..95110490a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx @@ -17,7 +17,7 @@ import { type MetaFunction, } from "@remix-run/react"; import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/server-runtime"; -import { type RuntimeEnvironmentType } from "@trigger.dev/database"; +import type { RuntimeEnvironmentType } from "@trigger.dev/database"; import { useEffect, useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.regions/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.regions/route.tsx new file mode 100644 index 000000000..3484e1378 --- /dev/null +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.regions/route.tsx @@ -0,0 +1,464 @@ +import { + ArrowRightIcon, + ArrowUpCircleIcon, + BookOpenIcon, + ChatBubbleLeftEllipsisIcon, + InformationCircleIcon, + MapPinIcon, +} from "@heroicons/react/20/solid"; +import { Form } from "@remix-run/react"; +import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { tryCatch } from "@trigger.dev/core"; +import { useState } from "react"; +import { typedjson, useTypedLoaderData } from "remix-typedjson"; +import { z } from "zod"; +import { CloudProviderIcon } from "~/assets/icons/CloudProviderIcon"; +import { FlagIcon } from "~/assets/icons/RegionIcons"; +import { cloudProviderTitle } from "~/components/CloudProvider"; +import { Feedback } from "~/components/Feedback"; +import { V4Title } from "~/components/V4Badge"; +import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; +import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout"; +import { Badge } from "~/components/primitives/Badge"; +import { Button, LinkButton } from "~/components/primitives/Buttons"; +import { ClipboardField } from "~/components/primitives/ClipboardField"; +import { CopyableText } from "~/components/primitives/CopyableText"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "~/components/primitives/Dialog"; +import { InfoPanel } from "~/components/primitives/InfoPanel"; +import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader"; +import { Paragraph } from "~/components/primitives/Paragraph"; +import * as Property from "~/components/primitives/PropertyTable"; +import { + Table, + TableBlankRow, + TableBody, + TableCell, + TableCellMenu, + TableHeader, + TableHeaderCell, + TableRow, +} from "~/components/primitives/Table"; +import { TextLink } from "~/components/primitives/TextLink"; +import { useFeatures } from "~/hooks/useFeatures"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useHasAdminAccess } from "~/hooks/useUser"; +import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; +import { findProjectBySlug } from "~/models/project.server"; +import { type Region, RegionsPresenter } from "~/presenters/v3/RegionsPresenter.server"; +import { requireUser } from "~/services/session.server"; +import { + docsPath, + EnvironmentParamSchema, + ProjectParamSchema, + regionsPath, + v3BillingPath, +} from "~/utils/pathBuilder"; +import { SetDefaultRegionService } from "~/v3/services/setDefaultRegion.server"; + +export const loader = async ({ request, params }: LoaderFunctionArgs) => { + const user = await requireUser(request); + const { projectParam } = ProjectParamSchema.parse(params); + + const presenter = new RegionsPresenter(); + const [error, result] = await tryCatch( + presenter.call({ + userId: user.id, + projectSlug: projectParam, + isAdmin: user.admin || user.isImpersonating, + }) + ); + + if (error) { + throw new Response(undefined, { + status: 400, + statusText: error.message, + }); + } + + return typedjson(result); +}; + +const FormSchema = z.object({ + regionId: z.string(), +}); + +export const action = async ({ request, params }: ActionFunctionArgs) => { + const user = await requireUser(request); + const { organizationSlug, projectParam, envParam } = EnvironmentParamSchema.parse(params); + + const project = await findProjectBySlug(organizationSlug, projectParam, user.id); + + const redirectPath = regionsPath( + { slug: organizationSlug }, + { slug: projectParam }, + { slug: envParam } + ); + + if (!project) { + throw redirectWithErrorMessage(redirectPath, request, "Project not found"); + } + + const formData = await request.formData(); + const parsedFormData = FormSchema.safeParse(Object.fromEntries(formData)); + + if (!parsedFormData.success) { + throw redirectWithErrorMessage(redirectPath, request, "No region specified"); + } + + const service = new SetDefaultRegionService(); + const [error, result] = await tryCatch( + service.call({ + projectId: project.id, + regionId: parsedFormData.data.regionId, + isAdmin: user.admin || user.isImpersonating, + }) + ); + + if (error) { + return redirectWithErrorMessage(redirectPath, request, error.message); + } + + return redirectWithSuccessMessage(redirectPath, request, `Set ${result.name} as default`); +}; + +export default function Page() { + const { regions, isPaying } = useTypedLoaderData(); + const organization = useOrganization(); + const isAdmin = useHasAdminAccess(); + const { isManagedCloud } = useFeatures(); + + return ( + + + Regions} /> + + + + {regions.map((region) => ( + + {region.name} + {region.id} + + ))} + + + + + +
+ {regions.length === 0 ? ( + +
+ No regions found for this project. +
+
+ ) : ( + <> +
+ + + + Region + Cloud Provider + Location + Static IPs + {isAdmin && Admin} + + + When you trigger a run it will execute in your default region, unless + you override the region when triggering. + + + Read docs + + + } + > + Default region + + + + + {regions.length === 0 ? ( + + There are no regions for this project + + ) : ( + regions.map((region) => { + return ( + + + + + + {region.cloudProvider ? ( + + + {cloudProviderTitle(region.cloudProvider)} + + ) : ( + "–" + )} + + + + {region.location ? ( + + ) : null} + {region.description ?? "–"} + + + + {region.staticIPs === null ? ( + + Unlock static IPs + + ) : region.staticIPs !== undefined ? ( + + ) : ( + "Not available" + )} + + {isAdmin && ( + {region.isHidden ? "Hidden" : "Visible"} + )} + {region.isDefault ? ( + + + Default + + + ) : ( + + } + /> + )} + + ); + }) + )} + + + + Suggest a new region + + + Suggest a region… + + } + defaultValue="region" + /> + } + /> + + +
+ {isManagedCloud && ( + + + Trigger.dev is fully GDPR compliant. Learn more in our{" "} + security portal or{" "} + + get in touch + + } + defaultValue="help" + /> + . + + + )} +
+ + )} +
+
+
+ ); +} + +function SetDefaultDialog({ + regions, + newDefaultRegion, +}: { + regions: Region[]; + newDefaultRegion: Region; +}) { + const [isOpen, setIsOpen] = useState(false); + const currentDefaultRegion = regions.find((r) => r.isDefault); + + return ( + + + + + + + Set as default region + + + + Are you sure you want to set {newDefaultRegion.name} as your new default region? + + +
+
+
+ Current default +
+
+ {currentDefaultRegion?.name ?? "–"} +
+
+ + {currentDefaultRegion?.cloudProvider ? ( + <> + + {cloudProviderTitle(currentDefaultRegion.cloudProvider)} + + ) : ( + "–" + )} + +
+
+ + {currentDefaultRegion?.location ? ( + + ) : null} + {currentDefaultRegion?.description ?? "–"} + +
+
+ + {/* Middle column with arrow */} +
+
+ +
+
+ + {/* Right column */} +
+
+ New default +
+
+ {newDefaultRegion.name} +
+
+ + {newDefaultRegion.cloudProvider ? ( + <> + + {cloudProviderTitle(newDefaultRegion.cloudProvider)} + + ) : ( + "–" + )} + +
+
+ + {newDefaultRegion.location ? ( + + ) : null} + {newDefaultRegion.description ?? "–"} + +
+
+
+ + + Runs triggered from now on will execute in "{newDefaultRegion.name}", unless you{" "} + override when triggering. + +
+ + +
+ +
+
+
+
+ ); +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index a27e67f63..49a0a70c1 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -20,7 +20,7 @@ import { nanosecondsToMilliseconds, tryCatch, } from "@trigger.dev/core/v3"; -import { type RuntimeEnvironmentType } from "@trigger.dev/database"; +import type { RuntimeEnvironmentType } from "@trigger.dev/database"; import { motion } from "framer-motion"; import { useCallback, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-alerts/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-alerts/route.tsx index c4d3327be..4dd0352f8 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-alerts/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.billing-alerts/route.tsx @@ -2,6 +2,7 @@ import { conform, list, requestIntent, useFieldList, useForm } from "@conform-to import { parse } from "@conform-to/zod"; import { Form, useActionData, type MetaFunction } from "@remix-run/react"; import { json, type ActionFunction, type LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { tryCatch } from "@trigger.dev/core"; import { Fragment, useEffect, useRef, useState } from "react"; import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; @@ -22,6 +23,7 @@ import { InputGroup } from "~/components/primitives/InputGroup"; import { Label } from "~/components/primitives/Label"; import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader"; import { Paragraph } from "~/components/primitives/Paragraph"; +import { TextLink } from "~/components/primitives/TextLink"; import { prisma } from "~/db.server"; import { featuresForRequest } from "~/features.server"; import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; @@ -29,12 +31,12 @@ import { getBillingAlerts, setBillingAlert } from "~/services/platform.v3.server import { requireUserId } from "~/services/session.server"; import { formatCurrency } from "~/utils/numberFormatter"; import { + docsPath, OrganizationParamsSchema, organizationPath, v3BillingAlertsPath, } from "~/utils/pathBuilder"; import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route"; -import { tryCatch } from "@trigger.dev/core"; export const meta: MetaFunction = () => { return [ @@ -199,10 +201,17 @@ export default function Page() {
- Billing alerts - - Receive an email when your compute spend crosses different thresholds. - +
+ Billing alerts + + Receive an email when your compute spend crosses different thresholds. You can also + learn how to{" "} + + reduce your compute spend + + . + +
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx index 3cd51e264..9b292cb5a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx @@ -7,7 +7,8 @@ import { Form, useActionData, useNavigation } from "@remix-run/react"; import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson"; import invariant from "tiny-invariant"; import { z } from "zod"; -import { MainCenteredContainer } from "~/components/layout/AppLayout"; +import { BackgroundWrapper } from "~/components/BackgroundWrapper"; +import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Callout } from "~/components/primitives/Callout"; import { Fieldset } from "~/components/primitives/Fieldset"; @@ -20,15 +21,14 @@ import { Label } from "~/components/primitives/Label"; import { ButtonSpinner } from "~/components/primitives/Spinner"; import { prisma } from "~/db.server"; import { featuresForRequest } from "~/features.server"; -import { useFeatures } from "~/hooks/useFeatures"; import { redirectWithSuccessMessage } from "~/models/message.server"; import { createProject } from "~/models/project.server"; import { requireUserId } from "~/services/session.server"; import { OrganizationParamsSchema, organizationPath, - v3ProjectPath, selectPlanPath, + v3ProjectPath, } from "~/utils/pathBuilder"; export async function loader({ params, request }: LoaderFunctionArgs) { @@ -138,57 +138,61 @@ export default function Page() { const isLoading = navigation.state === "submitting" || navigation.state === "loading"; return ( - -
- } - title="Create a new project" - description={`This will create a new project in your "${organization.title}" organization.`} - /> - - {message && ( - - {message} - - )} -
- - - - {projectName.error} - - {canCreateV3Projects ? ( - - ) : ( - - )} - - {isLoading ? "Creating…" : "Create"} - - } - cancelButton={ - organization.projectsCount > 0 ? ( - - Cancel - - ) : undefined - } + + + +
+ } + title="Create a new project" + description={`This will create a new project in your "${organization.title}" organization.`} /> -
- -
-
+
+ {message && ( + + {message} + + )} +
+ + + + {projectName.error} + + {canCreateV3Projects ? ( + + ) : ( + + )} + + {isLoading ? "Creating…" : "Create"} + + } + cancelButton={ + organization.projectsCount > 0 ? ( + + Cancel + + ) : undefined + } + /> +
+
+
+
+ + ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.select-plan/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.select-plan/route.tsx index e1561becf..844c5d66e 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.select-plan/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.select-plan/route.tsx @@ -1,6 +1,7 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson"; -import { MainCenteredContainer } from "~/components/layout/AppLayout"; +import { BackgroundWrapper } from "~/components/BackgroundWrapper"; +import { AppContainer } from "~/components/layout/AppLayout"; import { Header1 } from "~/components/primitives/Headers"; import { prisma } from "~/db.server"; import { featuresForRequest } from "~/features.server"; @@ -48,16 +49,22 @@ export default function ChoosePlanPage() { useTypedLoaderData(); return ( - - Subscribe for full access - - + + +
+ Subscribe for full access +
+ +
+
+
+
); } diff --git a/apps/webapp/app/routes/_app.orgs.new/route.tsx b/apps/webapp/app/routes/_app.orgs.new/route.tsx index a17115351..a677782ea 100644 --- a/apps/webapp/app/routes/_app.orgs.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.new/route.tsx @@ -7,7 +7,8 @@ import { json, redirect } from "@remix-run/node"; import { Form, useActionData, useNavigation } from "@remix-run/react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; -import { MainCenteredContainer } from "~/components/layout/AppLayout"; +import { BackgroundWrapper } from "~/components/BackgroundWrapper"; +import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Fieldset } from "~/components/primitives/Fieldset"; import { FormButtons } from "~/components/primitives/FormButtons"; @@ -94,85 +95,92 @@ export default function NewOrganizationPage() { const isLoading = navigation.state === "submitting" || navigation.state === "loading"; return ( - - } - title="Create an Organization" - /> -
-
- - - - E.g. your company name or your workspace name. - {orgName.error} - - {isManagedCloud && ( - <> - - - - - - - - - - - -