Compare commits

...

429 Commits

Author SHA1 Message Date
github-actions[bot] 0b35cc35e0 Release v4.0.0 🚀
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
2025-08-18 12:35:03 +01:00
James Ritchie fade22015f Docs – v4 GA updates (#2298)
* Adds new features table to top of v4 upgrade guide

* Adds wait idempotency to wait-until, wait-for, and wait-for-token pages

* Adds new priority docs page and updates the v4 upgrade guide

* Adds new task lifecycle hooks

* Removes the message about requiring tasks to be exported

* Adds new global lifecycle hooks section

* Moves sections from upgrade guide into the table

* Adds hidden task page

* Improves the global lifecycle hooks section

* Updates middleware and locals section

* Adds new useWaitToken page to the react hooks section

* Adds a new ai.tool section

* Moves Docker (legacy) page into self-hosting section

* Removes known issues from v4 upgrade guide

* Replace “toolTask” with “ai.tool” in the Streams page example

* Renames guide to “Migrating from v3” and adds redirect

* Remove references to v4

* Removes changelog from migration guide

* The installation guide now references `@latest update`

* Changes all references from `/sdk/v3` to `/sdk`

* Updates @v4-beta to @latest

* Fixed broken link

* Fixes broken link

* Adds an upgrade to v4 using AI section

* Fixes 2 broken links

* Adds an entry for targetting preview branches

* Updates the run statuses

* Adds boolean helpers section to the runs and realtime pages

* Updates the concurrency page

* Updates the test page to include the new options

* Adds SDK and curl options for the preview branch targeting

* Updates new bulk actions page

* Remove the releasing concurrency section

* Got rid of some more @v4-beta mentions

* Improved rate limit docs

* Improved migrating docs

* Removed commented sections of the docs

* useWaitToken hook

* Fixed the description

* Fix for missing test image

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
Co-authored-by: Dan <8297864+D-K-P@users.noreply.github.com>
2025-08-18 12:34:55 +01:00
Dan 78af477efc Updated readmes (#2408)
* readme wip

* Added new commands

* Simplified readme

* Added docs links and improved about

* Added nice header and updated links

* Consistent headers

* Copy update
2025-08-18 12:10:15 +01:00
Eric Allam e864973e01 chore(packages): leave changesets prerelease mode (#2406) 2025-08-18 11:06:31 +01:00
github-actions[bot] 132008ff64 Release 4.0.0-v4-beta.28 (#2386)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.28

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-08-17 20:30:33 +01:00
nicktrn 26d4d08bdc chore: move all placement tag helpers to core (#2403)
* chore: move all placement tag helpers to core

* move placement tag utils into server-only
2025-08-15 17:19:39 +01:00
Eric Allam 1a8880971d fix(runner): prevent retry immediately race condition which can cause stuck runs that end up being system failures (#2402) 2025-08-15 17:05:02 +01:00
Eric Allam 3d17ce5559 feat: AI SDK 5.0 support (#2396)
* feat: AI SDK 5.0 support

* Add changeset
2025-08-15 17:03:39 +01:00
nicktrn a76905e272 chore(bun): upgrade to bun v1.2.20 (#2398) 2025-08-15 17:01:06 +01:00
Eric Allam 1c73ab930b fix(realtime): waitUntil now correctly waits for metadata.streams to finish (#2399) 2025-08-15 17:00:45 +01:00
Eric Allam 816c7ad56e fix(webapp): remove unnecessary log (#2401) 2025-08-15 12:47:55 +01:00
Eric Allam 4d975b19e7 fix: external traces now respect parent sampling, and prevent broken traces when there is no external trace context (#2395)
* fix: external traces now respect parent sampling, and prevent broken traces when there is no external trace context

* Add changeset

* improve trace flag handling and better internal host checking

* the traceFlags are now being properly passed through as a number
2025-08-15 10:12:38 +01:00
Eric Allam 4c4b774bab fix(webapp): Don't propogate region when replaying a run from or to development (#2400) 2025-08-15 10:07:59 +01:00
nicktrn fa7f4b1fed feat(k8s): add placement tags for flexible node selection (#2390)
* add tier scheduling support to supervisor

* add billing info to dequeued message w/o cache

* add cache with best effort invalidation

* fix invalidate circular dep

* add changeset

* use new plan type on runs as fallback during dequeue

* tidy up

* be more explicit with plan type fallback

* remove additional billing check from hot path

* switch to placement tags

* update changeset

* update platform package

* start using new entitlement response

* ensure skipChecks optimization validates at batch level

* add optional items to add to queue manager limits

* make the bool env helper only accept boolean defaults

* remove redundant private field

* update placement tag helper to prevent unsupported tags
2025-08-15 09:52:14 +01:00
Saadi Myftija f76765448b feat: show gh username and avatar for deployments triggerd by the github app integration (#2394)
Small change that shows the github user in the deployments page in cases
where it was triggered by the github app integration.

These deployments are not triggered using a PAT, so we cannot associate
them with a Trigger user ID.
2025-08-14 18:20:59 +02:00
Saadi Myftija 944b1875f1 feat: gather deployment git meta from the build server (#2392)
This PR adapts the deployment command to also evaluate build server env
variables when creating the git meta. The build server will initially
only support deployments triggered by the github app, but we might add
other git providers in the future.

Sticking to the GH actions naming convention for the env variables set
by the build server in favor of consistency.
2025-08-14 16:07:36 +02:00
Eric Allam a5dd6389b2 fix: sentry memory leak by patching @sentry/remix to stop cloning request (#2389)
* fix: sentry memory leak by disabling includeLocalVariables

* Enhance heap snapshot consistency and labeling

To facilitate more accurate and consistent heap memory snapshots, a new function forceConsistentGC was added before taking a snapshot. This ensures the garbage collector (GC) runs multiple times, stabilizing the heap state for more reliable analysis. This is particularly helpful when debugging memory-related issues.

Updates to the memory-leak-detector script now allow labeling of snapshot runs using the --label flag. This helps in distinguishing different runs for easier tracking and comparison of memory usage across test sessions. Additionally, the --expose-gc flag ensures that the GC can be manually triggered during test runs, leading to more consistent memory states and potentially uncovering hidden memory leaks.

* Refactor forceConsistentGC for improved readability

The function forceConsistentGC was refactored to enhance code readability and consistency. The main improvements include:

- Updated syntax for consistent string quotation and spacing.
- Simplified garbage collection by removing specific major/minor GC calls, as the distinction isn't necessary.
- Implemented minor changes to arrow function formatting for consistency.

These changes neither impact the program logic nor the function behavior but help maintain code quality standards and readability.

* Fix memory leak by removing request.clone() usage

Identified that the memory leak in the project was linked to the usage of request.clone() within the `@sentry/remix` package's callRouteAction handler. Although initially suspected as a Sentry issue, the problem appears to arise from the handling of request.clone() in Remix version 2.1.0. By removing the call to request.clone(), the memory leak has been resolved.

- Introduced garbage collection execution before snapshot to manage memory allocation effectively.
- Improved error handling and timeout mechanisms in the memory leak detector to enhance its resilience during runtime.
- Expanded testing for both GET and POST requests to monitor and validate potential memory leaks better. The POST requests involve sending large payloads to stress-test the system.
- The modification particularly focuses on enhancing robust memory tracking and providing detailed progress reporting during request phases.

* patch @sentry/remix to prevent memory leaks

* Fix pnpm lock

* undo some unrelated changes
2025-08-14 14:50:20 +01:00
Saadi Myftija 3ec95cd72d fix: code editor/block theme inconsistency (#2393)
Tiny fix to make the themes across code blocks and editor components across the app consistent.
2025-08-14 11:41:36 +02:00
Eric Allam 6f29b7dc34 fix: legacyDevProcessCwdBehaviour defaults to true (#2387)
This fixes an issue with prisma not being able to locate the native 
add-on binary
2025-08-13 14:30:10 +01:00
Eric Allam ed07bf9306 fix: prevent circular reference errors on task indexing when using schemaTask (#2383)
* fix: prevent circular reference errors on task indexing when using schemaTask

* add changeset
2025-08-13 14:10:57 +01:00
Matt Aitken cd6a0b3a3e fix: heartbeat race condition (acking) (#2380)
* The logger now supports metadata

* Added metadata to ServiceValidationError in some critical places

* Don't ack the heartbeat if there's a mismatch, it might prevent a brand new one

* Don't ack the heartbeat inside stalled. By returning it will be acked IF the deduplication key matches
2025-08-12 10:35:21 +02:00
Eric Allam e9cc7de8ea docs: Add beta.27 changelog (#2381) 2025-08-11 22:06:12 +01:00
github-actions[bot] 0448f3603a Release 4.0.0-v4-beta.27
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.27

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-08-11 15:39:59 +01:00
Matt Aitken dd4515a2eb Merge pull request #2379 from triggerdotdev/remove-trigger-and-poll
Remove triggerAndPoll()
2025-08-11 15:11:58 +01:00
Matt Aitken 97e5c22051 Added a changeset 2025-08-11 13:42:14 +01:00
Matt Aitken b8d2fb215f Remove triggerAndPoll() 2025-08-11 13:40:37 +01:00
Takeoff 21c2c136be test coverage for utils (#2057) 2025-08-11 11:53:03 +01:00
Matt Aitken ae19d0f491 Only start an attempt if not finished. Send message to worker if pending executing (#2377)
* Only start an attempt if not finished. Send message to worker if pending executing

* Fix the exit process reason tet

* Fixed cancelling test since bug fix

The old behaviour was wrong for pending executing in the test
2025-08-11 11:46:10 +01:00
Matt Aitken c58b78f43f Exit the process if status is finished and not in the middle of completing (#2378)
This solves an issue where a run is system failed from the platform and 
the worker is never shut down in the cluster.
2025-08-11 11:22:26 +01:00
Matt Aitken a968c23678 Don't use a failed run with idempotency in v4 (#2376) 2025-08-08 14:01:18 +01:00
nicktrn 4b08597c77 feat: optional v4 deploy registry overrides (#2370)
* feat: optional v4 deploy registry overrides

* only require credentials for non-ecr registries
2025-08-07 17:02:01 +01:00
Matt Aitken 9ce4f57a6a Upgraded v4 experimental flags. keepNames and autoDetectExternal now default to true. (#2371)
* keepNames default true, not experimental now

* Added docs for the `environments` declarative schedules feature

* minify and autoDetectExternal (true now)

* processKeepAlive

* devProcessCwdInBuildDir

* Changeset

* Switch to legacyDevProcessCwdBehaviour
2025-08-07 16:47:45 +01:00
nicktrn 363b9b15b4 fix(engine): set correct run status after requeue and snapshot status after dequeue (#2367)
* set correct run status on snapshot after dequeue

* set run status back to PENDING when we requeue

* remove retrying after failure status from v4 and fix tests

* fix one last test
2025-08-07 16:46:27 +01:00
James Ritchie 51305b6023 Onboarding dashboard background (#2346)
* Adds a background image dashboard wrapper

* Dashboard background image

* Adds a background image to the welcome onboarding page

* Background is constructed of 3 images

* Adds the background to the create org page

* Adds a background to the choose plan page

* Change the default button spinner color to white

* Adds the background to the create new project page

* Updates the invite team member page

* Adds background image to received invite page
2025-08-07 16:13:42 +01:00
Dan 9e41e83818 Restructured the realtime docs (#2317)
* Reordered react hooks + frontend sections

* Updated the overview and nav

* Separated out SWR hooks

* Restructured metadata sections

* Improved backend docs

* Fixed broken link

* Fixed broken links

* Updated the structure

* Restructured overview

* Updated examples cards

* Improved overview

* Updated how it works

* Updated auth

* Added type safety to the run object page

* made the subscribe description clearer

* Fixed links in triggering

* Fixed link

* Removed examples footers

* Copy tweak

* Consolidated metadata and subscribe pages

* moved metadata task examples to metadata

* Fixed links

* Fixing links like zelda

* Removed dead import

* Clearer titles

* Cap R for Realtime

* Fixes broken link

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-08-07 16:02:52 +01:00
Matt Aitken da7dc74fa4 Replaying keeps the original run region (#2368) 2025-08-07 14:43:28 +01:00
Matt Aitken af14621683 Specify a region when triggering (#2366)
* Map new allowedMasterQueues → allowedWorkerQueues

* ClickHouse worker_queue on task runs

* Added the Region to the run inspector

* Pass a region in when triggering

* Added a changeset

* Added triggering regions docs

* Added region to the ctx

* Fix for backfiller masterQueue/workerQueue
2025-08-07 12:41:39 +01:00
Matt Aitken 9787120cfb We need to set TRIGGER_OTEL_EXPORTER_OTLP_ENDPOINT for deployed runs (#2365) 2025-08-07 11:40:14 +01:00
Eric Allam e46e9bd30f docs: Manual setup guide (#2358)
* Add manual monorepo setup guide for Trigger.dev

This commit introduces a new "Manual setup" guide for setting up Trigger.dev in projects, specifically focused on monorepo configurations. The guide outlines two primary approaches for monorepos: creating a dedicated tasks package and integrating tasks directly within apps. This detailed documentation aims to help developers manually configure their projects, bypassing automated steps, and understanding the setup better.

- Provides step-by-step instructions for both 'Tasks as a package' and 'Tasks in apps' approaches.
- Includes example configurations for various package managers, environment setups, and runtime options.
- Enhances user understanding of Trigger.dev's configuration requirements in complex monorepo environments.

The purpose of adding this guide is to enable developers to seamlessly integrate Trigger.dev into their monorepos, whether they choose to abstract tasks into packages or embed them within individual applications. This flexibility supports diverse project structures while maintaining consistency with Trigger.dev's operational prerequisites.

* Correct tasks usage in documentation

The 'tasks as package' section in the documentation had an incorrect example under 'Use tasks in your apps' which needed correction to align with the actual package usage.

- Fixed incorrect import of tasks by updating to the correct import from '@repo/tasks/trigger'.
- Updated the syntax to use 'tasks.trigger' with type parameters, following the new pattern established for triggering tasks with TypeScript.
- Added error handling to catch and log errors during task execution, returning a meaningful error message instead of just failing silently.

This update ensures developers have an accurate reference when implementing tasks in their applications, especially given the breaking changes in TypeScript compatibility due to recent package updates.

* Update package configuration for Zod 3-4 compatibility

The recent Zod package updates from version 3 to 4 introduce breaking changes in TypeScript compatibility that require adjustments in our project configuration files. The primary updates involve adding type annotations and modifying import statements to support the shift without breaking existing functionality.

- Updated `package.json` and initialization files to align with new compatibility requirements.
- Modified server task examples to explicitly use type imports for improved error handling and consistency.
- Adjusted workspace and project settings to maintain compatibility and improve configuration clarity.
- Provided references to current examples for better implementation guidance.

* More manual setup guide steps

* Fix bun docs link
2025-08-07 10:22:37 +01:00
James Ritchie 3cd7dd86d4 Fixed code example in reduce spend docs page (#2350) 2025-08-06 17:18:59 +01:00
Matt Aitken a782813c6c Regions – dashboard page, switching default, allowedMasterQueues (#2354)
* Initial Regions page

* Fix for bad attribute name

* Switching regions is working. Some style improvements

* Use a dialog for confirmation, not great yet

* Added allowedMasterQueues

* Improves flag icons

* Improves the region switch modal with more info

* Adds “suggest a region” table row

* New icons for the buttons

* Improved the tooltip information

* New “small” badge style

* Make the default badge live in its column

* Better DO icon size

* Remove unused export of regions options

* Show upgrade message for free users to get static IPs

* Admins can view all regions and switch at will

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-08-06 15:56:01 +01:00
Eric Allam d950a969bd Update zod package to version 3.25.76 across all modules (#2352)
* Update zod package to version 3.25.76 across all modules

Update the zod library from version 3.23.8 to 3.25.76 in multiple package files to ensure compatibility and take advantage of new features or bug fixes introduced in recent releases. Keeping all modules synchronized with the latest version of zod helps maintain consistency across the project and reduces potential compatibility issues.

- Modified zod version in apps/supervisor, webapp, and various internal packages.
- Updated zod references in pnpm-lock.yaml to reflect the new version.
- Ensure dependencies that rely on zod are using the updated version to avoid mismatches.

* Add changeset
2025-08-06 14:48:43 +01:00
Eric Allam 8eecc7e548 chore: json schema missing changeset (#2353) 2025-08-06 13:56:57 +01:00
Eric Allam 1294076484 feat: index json schemas on tasks and schemaTask (#2351)
* Add payload schema handling for task indexing

This change introduces support for handling payload schemas during task indexing. By incorporating the `payloadSchema` attribute into various components, we ensure that each task's payload structure is clearly defined and can be validated before processing.

- Updated the TaskManifest and task metadata structures to include an optional `payloadSchema` attribute. This addition allows for more robust validation and handling of task payloads.
- Enhanced several core modules to export and utilize the new `getSchemaToJsonSchema` function, providing easier conversion of schema types to JSON schemas.
- Modified the database schema to store the `payloadSchema` attribute, ensuring that the payload schema information is persisted.
- The change helps in maintaining consistency in data handling and improves the integrity of task data across the application.

* Refactor: Remove getSchemaToJsonSchema in favor of schemaToJsonSchema

The `getSchemaToJsonSchema` function was removed and replaced with `schemaToJsonSchema` across the codebase. This update introduces a new `@trigger.dev/schema-to-json` package to handle conversions of schema validation libraries to JSON Schema format, centralizing the functionality and improving maintainability.

- Removed `getSchemaToJsonSchema` exports and references.
- Added new schema conversion utility `@trigger.dev/schema-to-json`.
- Updated `trigger-sdk` package to utilize `schemaToJsonSchema` for payloads.
- Extensive testing coverage included to ensure conversion accuracy across various schema libraries including Zod, Yup, ArkType, Effect, and TypeBox.
- The update ensures consistent and reliable schema conversions, facilitating future enhancements and supporting additional schema libraries.

* Add support for Zod 4 in schema-to-json

This change enhances the schema-to-json package by adding support for Zod version 4, which introduces the native `toJsonSchema` method. This method facilitates a direct conversion of Zod schemas to JSON Schema format, improving performance and reducing reliance on the `zod-to-json-schema` library.

- Updated README to reflect Zod 4 support with native method and retained support for Zod 3 via existing library.
- Modified package.json to allow installation of both Zod 3 and 4 versions.
- Implemented handling for Zod 4 schemas in `src/index.ts` using their native method.
- Added a test case to verify the proper conversion of Zod 4 schemas to JSON Schema.
- Included a script for updating the package version based on the root package.json.
- Introduced a specific TypeScript config for source files.

* Revise schema-to-json for bundle safety and tests

The package @trigger.dev/schema-to-json has been revised to ensure bundle safety by removing direct dependencies on schema libraries such as Zod, Yup, and Effect. This change minimizes bundle size and enhances tree-shaking by allowing external conversion libraries to be utilized only at runtime if necessary. As a result, the README was updated to reflect this usage pattern.

- Introduced `initializeSchemaConverters` function to load necessary conversion libraries at runtime, keeping the base package slim.
- Adjusted test suite to initialize converters before tests, ensuring accurate testing of schema conversion capabilities.
- Updated `schemaToJsonSchema` function to dynamically check for availability of conversion libraries, improving flexibility without increasing the package size.
- Added configuration files for Vitest to support the new testing framework, reflecting the transition from previous test setups.

These enhancements ensure that only the schema libraries actively used in an application are bundled, optimizing performance and resource usage.

* Refine JSON Schema typing across packages

The changes introduce stricter typing for JSON Schema-related definitions, specifically replacing vague types with more precise ones, such as using `z.record(z.unknown())` instead of `z.any()` and `Record<string, unknown>` in place of `any`. This is part of an effort to better align with common practices and improve type safety in the packages.

- Updated the `payloadSchema` in several files to use `z.record(z.unknown())`, enhancing the type strictness and consistency with JSON Schema Draft 7 recommendations.
- Added `@types/json-schema` as a dependency, utilizing its definitions for improved type clarity and adherence to best practices in TypeScript.
- Modified various comments to explicitly mention JSON Schema Draft 7, ensuring developers are aware of the JSON Schema version being implemented.
- These adjustments are informed by research into how popular libraries and tools handle JSON Schema typing, aiming to integrate best practices for improved maintainability and interoperability.

* Add JSON Schema examples using various libraries

The change introduces extensive examples of using JSON Schemas in the 'references/hello-world' project within the 'trigger.dev' repository. These examples utilize libraries like Zod, Yup, and TypeBox for JSON Schema conversion and validation. The new examples demonstrate different use cases, including automatic conversion with schemaTask, manual schema provision, and schema conversion at build time. We also updated the dependencies in 'package.json' to include the necessary libraries for schema conversion and validation.

- Included examples of processing tasks with JSON Schema using libraries such as Zod, Yup, TypeBox, and ArkType.
- Showcased schema conversion techniques and type-safe JSON Schema creation.
- Updated 'package.json' to ensure all necessary dependencies for schema operations are available.
- Created illustrative scripts that cover task management from user processing to complex schema implementations.

* Refactor SDK to encapsulate schema-to-json package

The previous implementation required users to directly import and initialize functions from the `@trigger.dev/schema-to-json` package, which was not the intended user experience. This change refactors the SDK so that all necessary functions and types from `@trigger.dev/schema-to-json` are encapsulated within the `@trigger.dev/*` packages.

- The examples in `usage.ts` have been updated to clearly mark `@trigger.dev/schema-to-json` as an internal-only package.
- Re-export JSON Schema types and conversions in the SDK to improve developer experience (DX).
- Removed unnecessary direct dependencies on `@trigger.dev/schema-to-json` from user-facing code, ensuring initialization and conversion logic is handled internally.
- Replaced instances where users were required to manually perform schema conversions with automatic handling within the SDK for simplification and better maintainability.

* Add JSONSchema type for payloadSchema in tasks

The change was necessary to improve type safety by using a proper JSONSchema type definition instead of a generic Record<string, unknown>. This enhances the developer experience and ensures that task payloads conform to the JSON Schema Draft 7 specification. The JSONSchema type is now re-exported from the SDK for user convenience, hiding internal complexity and maintaining a seamless developer experience.

- Added JSONSchema type based on Draft 7 specification
- Updated task metadata and options to use JSONSchema type
- Hid internal schema conversion logic from users by re-exporting types from SDK
- Improved bundle safety and dependency management

* Add JSON schema testing and revert package dependencies

This commit introduces a comprehensive set of JSON schema testing within the monorepo, specifically adding a new test project in `references/json-schema-test`. This includes a variety of schema definitions and tasks utilizing multiple validation libraries to ensure robust type-checking and runtime validation.

Additionally, the dependency versions for `@effect/schema` have been adjusted from `^0.76.5` to `^0.75.5` to maintain compatibility across the project components. This ensures consistent behavior and compatibility with existing code bases without introducing breaking changes or unexpected behavior due to version discrepancies.

Key updates include:
- Added new test project with extensive schema validation tests.
- Ensured type safety across various task implementations.
- Reverted dependency versions to ensure compatibility.
- Created multiple schema tasks using libraries like Zod, Yup, and others for thorough testing.

* Refactor JSON Schema test files for clarity

Whitespace and formatting changes were applied across the `json-schema-test` reference project to enhance code readability and cohesion. This included removing unnecessary trailing spaces and ensuring consistent indentation patterns, which improves maintainability and readability by following the project's code style guidelines.

- Renamed JSONSchema type annotations to adhere to TypeScript conventions, ensuring that all schema definitions properly satisfy the JSONSchema interface.
- Restructured some object declarations for improved clarity, especially within complex schema definitions.
- These adjustments are crucial for better future maintainability, reducing potential developer errors when interacting with these test schemas.

* Fixed some stuff

* WIP

* we now convert schema to jsonSchema on the CLI side via the indexing

* Remove the json-schema-test reference project

* Improve schema-to-json peer deps and fix effect schema

* Explain the casting and match the version numbers

* Fixed a bunch more schema stuff

* Don't clean files that might be written to

* Don't use a custom version of vitest in the new package

* fix attw in schema-to-json
2025-08-06 13:44:56 +01:00
nicktrn f82a4e9651 fix(playwright): improve chrome installation and fix spinner duplication on narrow terminals (#2347)
* headless: false will now install binaries for headless mode too

* fix spinner message duplication on narrow terminals

* add changeset
2025-08-06 12:08:33 +02:00
Eric Allam 1f4434035e latest @opentelemetry packages and correlate external traces (#2334) 2025-08-06 08:10:46 +01:00
Dan 7bb88451be Sidebar fix (#2349)
* Commented out tooltip style

* Deleted stylesheet
2025-08-05 11:08:44 +01:00
James Ritchie be70f42bec Adds a link to the reduce spend docs page from the billing alerts page (#2345)
* Adds a link to the reduce spend docs page from the billing alerts page

* Copy tweak
2025-08-04 13:22:18 +01:00
James Ritchie bb54e97806 Show text wrapping on code blocks (#2338)
* Show text wrapping on deployment code blocks

* Show textWrapping on some more CodeBlocks

* Adds missing margins to the error block
2025-08-04 13:21:30 +01:00
James Ritchie edafc71142 Explain run status on run inspector panel (#2342) 2025-08-04 13:21:03 +01:00
James Ritchie 792c97339e Force-wrap long strings in the run error callout (#2340) 2025-08-04 13:19:57 +01:00
James Ritchie 0fc234b970 Fixes incorrect icon colors (#2339) 2025-08-04 13:18:32 +01:00
James Ritchie f8126871f3 Improves large output messaging (#2341)
* Adds more information to the large payloads/outputs

* Changes the Download logs button to secondary
2025-08-04 13:18:07 +01:00
James Ritchie df0dc07678 New Task page deploy blank state (#2344)
* Adds a step by step deploy blank state to Tasks page

* Adds links to Ask AI, docs and troubleshooting
2025-08-04 13:17:06 +01:00
James Ritchie 63857b0445 Makes it clear you only get preview branches if you’re on v4 (#2343) 2025-08-04 13:13:38 +01:00
James Ritchie b985843642 Prevents long project menu titles pushing the admin buttons out of bounds (#2332) 2025-08-04 13:13:03 +01:00
Boon Kai 9ecda816dc fix(cli): improve contrast for chalkWorker yellow in light mode (#2239)
* fix(cli-output): improve color contrast for chalkWorker on light mode

* add changeset

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-08-04 12:09:38 +01:00
nicktrn 7b54c3527e fix(otel): prevent infinite retry loops on unicode hex escape errors (#2337)
* prevent infinite retry loop for unicode errors

* structured logs for prisma events

* preserve all prisma event fields

* either use structured logs or stdout, never both

* split runs repo tests

* decrease test shards to 8
2025-08-02 16:54:11 +01:00
nicktrn 60b8c76adc fix(hosting): pass registry namespace env var to webapp, bump helm (#2336)
* pass deploy registry namespace to webapp

* bump helm chart and images
2025-08-02 14:20:55 +01:00
Eric Allam 3fd8ea80da fix: backwards compatible snapshot runStatus to prevent DEQUEUED status from breaking older runners (#2335)
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
2025-08-01 17:19:58 +01:00
Matt Aitken c576e98dbc Fix v4 changelog latest date (#2333) 2025-08-01 15:23:55 +01:00
Matt Aitken b9e62139ff v4 docs upgrade changelog (#2331) 2025-08-01 14:17:40 +01:00
github-actions[bot] 44af80e055 Release v4.0.0-v4-beta.26
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
* chore: Update version for release (v4-beta)

* Release v4.0.0-v4-beta.26

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-08-01 12:48:08 +01:00
James Ritchie 1feabd732e Docs: Adds a Reduce your spend docs page (#2330) 2025-08-01 09:20:10 +01:00
Eric Allam 134942b2dd The schedule HTTP API now accepts deduplicationKeys as well as schedule IDs in the URL (#2329) 2025-07-31 14:12:31 +01:00
Eric Allam b153324cd9 fix: importing from runEngine/index.js breaks non-node runtimes (#2328) 2025-07-31 13:22:02 +01:00
James Ritchie 59bfce86db Docs: Fixes incorrect concurrency pricing on the limits page (#2324) 2025-07-31 12:07:42 +01:00
Eric Allam 748565c9db Fix backwards compatible execution by allowing additional properties to passthrough (#2327) 2025-07-31 11:14:46 +01:00
Matt Aitken 8b31871998 Usage billing alerts (#2323)
* First draft billing alerts page

* Budget alert form working

* Don't let free plan users change the billing alert amount

* Fix missing key in map in the form

* Disable queues/org from admin API endpoint

* Don't allow resuming if runsEnabled is false

* Refer to "Billing alerts" not "Plans"

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Form missing dependencies fix

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Deal with thrown errors, fix for duplicating email fields

* Added a RuntimeEnvironment organizationId index

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-30 13:48:02 +01:00
Eric Allam 14dcc76f93 feat: run.ctx tidying and additions (#2322)
* Cleanup context and execution creation, cache stuff, add parent and root task run ids

* more efficient by using friendly IDs instead of doing joins

* metadata.root/parent now reference current run when run has no root/parent

* Adding changeset

* try to make test less flaky

* Clean imports

* Another attempt to fix the flaky test

* Fix usage by still passing durationMs and costInCents to the execution, just not the run.ctx
2025-07-30 13:47:12 +01:00
Nicolas Rigaudière 5ea6605ec6 feat(extensions): add lightpanda (#2192)
* feat: add lightpanda structure

* chore: add lightpanda doc links

* fix: lightpanda extension instructions

* feat: add Lightpanda guide and examples

* feat: lightpanda - add 3rd example

* feat: add lightpandaTask

* fix: lightpanda 3rd example

* fix: lightpanda 1st example

* chore: add changeset

* add v4 tag to guide

* fix: merge lightpanda docker instructions

* fix: add failsafes

* add scrape warning

* lint

* successful login also switches to that profile

* update docs and links as this is v4 only

* extension tweaks

* simplify extension

* update examples

* update docs

* remove from extensions list as v4 only

* remove from catalog

* update changeset

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-07-28 18:38:13 +01:00
nicktrn 3e1cc69db9 feat(ecr): make assume role optional (#2321) 2025-07-28 14:47:30 +01:00
nicktrn 2ea52da628 fix(runner): disable warm starts after SIGTERM (#2316)
* security: bump form-data to latest patch versions (CVE-2025-7783)

* disable warm starts on sigterm

* add changeset
2025-07-28 11:29:49 +01:00
Saadi Myftija 3493b9464f Cancel readonly ClickHouse queries on client close (#2319) 2025-07-28 11:00:35 +01:00
Eric Allam a7ff6de388 fix: otel logs better DynamicFlushScheduler (#2318)
* Improve dynamic flush scheduler for otel data

The changes introduce a more flexible and adaptive dynamic flush scheduler to address production issues where the system wasn't flushing data fast enough, causing memory growth and crashes. This issue arises from the existing scheduler handling only a single flush at a time, limiting concurrency and failing to cope with the influx of logs.

- Added configuration options for setting minimum and maximum concurrency levels, maximum batch size, and memory pressure threshold. These parameters ensure that flush operations adjust dynamically based on workload and pressure.
- Implemented `pLimit` to facilitate concurrent flush operations, with adjustments made according to batch queue length and memory pressure.
- Metrics reporting improvements were added to monitor the dynamic behavior of the flush scheduler, aiding in identifying performance issues and optimizing the operation accordingly.

* Implement load shedding for TaskEvent records

This change introduces load shedding mechanisms to manage TaskEvent
records, particularly those of kind LOG, when the system experiences
high volumes and is unable to flush to the database in a timely
manner. The addition aims to prevent overwhelming the system and
ensure critical tasks are prioritized.

- Added configuration options for `loadSheddingThreshold` and
  `loadSheddingEnabled` in multiple modules to activate load shedding.
- Introduced `isDroppableEvent` function to allow specific events to
  be dropped when load shedding is enabled.
- Ensured metrics are updated to reflect dropped events and load
  shedding status, providing visibility into system performance
during high load conditions.
- Updated loggers to inform about load shedding state changes,
  ensuring timely awareness of load management activities.

* Fix undefined 'queuePressure' variable in DynamicFlushScheduler

The 'queuePressure' variable was being used without being defined
in the DynamicFlushScheduler class, causing potential runtime
errors. This commit adds the missing definition and ensures that
the variable is correctly calculated based on the 'totalQueuedItems'
and 'memoryPressureThreshold'.

- Addressed code inconsistencies and improved formatting.
- Defined 'queuePressure' in the 'adjustConcurrency' method
  to prevent potential undefined errors.
- Enhanced readability by maintaining consistent spacing and
  format across the file, contributing to the stability and
  maintainability of the code.
- Adjusted batch size logic based on the newly defined 'queuePressure'
  variable.

* Refactor concurrency adjustment logic in scheduler

The concurrency adjustment logic in the dynamic flush scheduler has been refactored to improve clarity and maintainability. This change moves the calculation of pressure metrics outside of the conditional blocks to ensure they are always determined prior to decision-making.

- The queue pressure and time since last flush calculations were moved up in the code to be independent of the 'backOff' condition.
- This refactor sets up the groundwork for more reliable concurrency scaling and better performance monitoring capabilities. The overall logic of adjusting concurrency based on system pressure metrics remains unchanged.

This adjustment addresses ongoing issues with the scheduler that were not resolved by previous changes.

* Some tweaks
2025-07-26 11:06:56 +01:00
Eric Allam 0d136a3e0c fix runs.retrieve when the payload or output has unstringifiable JSON (#2315) 2025-07-25 12:00:05 +01:00
nicktrn c8165ccd44 security: bump form-data to latest patch versions (CVE-2025-7783) (#2314) 2025-07-24 17:44:19 +01:00
Eric Allam 00cc07dad3 Ignore queryRoute() call aborted errors (#2313) 2025-07-24 16:22:21 +01:00
Eric Allam 71693eb9b3 stop double reporting errors to sentry (#2312) 2025-07-24 16:02:26 +01:00
Eric Allam fb59bfd8f8 sentry: remove node native integration because it breaks (#2311) 2025-07-24 15:37:06 +01:00
Matt Aitken 038ee75c28 Queue "View runs" buttons turn off root only (#2310) 2025-07-24 15:26:56 +01:00
Eric Allam 09d0e80804 Add sentry error reporting (#2309)
* Sentry WIP

* Configure sentry for uploading and releasing during the publish webapp step

* Delete source maps after uploading

* Forward logger.error calls to sentry through Logger.onError

* Couple tweaks to the dockerfile
2025-07-24 15:09:50 +01:00
Saadi Myftija 74808d7400 Pre-merge and pre-sort run batches before sending to clickhouse (#2308)
* Premerge run batch before sending it to clickhouse

* Pre-order batch items before sending to clickhouse in favor of performance

* existing

* Emit event on batch flushes

* When merging batches, keep the last occurrence items with the same version

* Add a couple of tests
2025-07-24 14:17:26 +01:00
Matt Aitken 6791328a94 Queues dashboard update (burst concurrency) (#2293)
* Initial burst changes to queue page

* Added tooltip and changed wording around

* View runs from Queues page

* Fix for ugly Version filter "Current" badge
2025-07-24 10:17:54 +01:00
Eric Allam 3dce1f66f7 docs: beta.25 changelog entry (#2306) 2025-07-24 09:38:24 +01:00
github-actions[bot] 17bb0c0cef Release v4.0.0-v4-beta.25
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
* chore: Update version for release (v4-beta)

* Release v4.0.0-v4-beta.25

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Allam <eallam@icloud.com>
2025-07-23 20:22:33 +01:00
Eric Allam ef59a62368 Ability to perform bulk run backfills (#2304)
* Ability to perform bulk run backfills

* Fix tests

* Allow controlling the delay interval when creating the batch
2025-07-23 20:12:35 +01:00
Eric Allam b447a8040f Use sync insert strategy since we are already batching client-side (#2303) 2025-07-23 17:17:13 +01:00
Matt Aitken ed86b4f5db Run repository test fix (#2302)
The normal database needs to be mocked because an import somewhere is 
using it which causes issues with the test container dbs and hanging at 
the end. 

Strategy copied from the trigger test file.
2025-07-23 17:10:20 +01:00
Matt Aitken 9b0eb64035 Fallback from ClickHouse to Postgres (#2300)
* Set the default replication concurrency to 2 for self-hosters

100 was a bit crazy

* Made the run repository an interface, deferring just to CH for now

* Added run repository feature flag, allowing passing a default when getting a flag

* Switch run repository using a feature flag

* Added spans

* Pass the default repository in, so we can try Postgres in the tests

* Fallback to Postgres if ClickHouse errors

* Update feature flags API endpoint
2025-07-23 16:30:30 +01:00
Eric Allam c927fbc8e4 Gracefully shutdown task run processes (#2299)
* Gracefully shutdown task run processes

* better log, thanks coderabbit
2025-07-23 16:11:26 +01:00
Matt Aitken bf4fff9cca The tasks page shows an error rather than breaking the entire page (#2297) 2025-07-23 12:28:06 +01:00
Saadi Myftija ad1672d95f Fail gracefully when runs cannot be loaded (#2296) 2025-07-23 11:21:54 +01:00
Eric Allam 6df7eb2bb5 docs: new release concurrency system and run statuses (#2287)
* WIP new docs for the new release concurrency and run statuses

* Fixed status description

* Add the beta.24 changelog entry
2025-07-22 11:30:28 +01:00
github-actions[bot] 36ced50c04 Release 4.0.0-v4-beta.24
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.24

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Allam <eallam@icloud.com>
2025-07-22 10:42:59 +01:00
Eric Allam 684e02c8c8 fix: prevent large root/parent metadata updates from endlessly retrying (#2290)
* fixing metadata WIP

* WIP

* fix: prevent large root/parent metadata updates from endlessly retrying

* Fixed other calls to handleMetadataPacket
2025-07-21 23:04:44 +01:00
Matt Aitken 4b9f33082e AI run filtering (#2285)
* Queue in run table and filtering

* Debounce the filter changes

* Remove console log

* Added machine filtering

* Added version filtering

* Filter by version in the db

* Removed duplicate classes

* Version filtering hasFilters consistency

* Added queues and machines to the bulk action summary

* runs.list filtering for queue and machine

* Fix for machine errors

* Input field now has accessory instead of shortcut

* First experiments with the UI

* Got the fake filtering working

* AI filtering is working pretty well 

* Started working on tool calling

* Tool calling is working

* Styling progress

* Working on the error

* Errors work, improved the styling

* Nice glow effect

* Tweak the darkness of the text field

* Re-ordered the UI, set AI settings to use system prompt and telemetry

* Refactored to make it testable

* Added basic evals

* Better time inputs and evals

* Removed some code comments

* Remove unused useSearchParam change

* Tidy imports

* If no OpenAI API key send json back

* Fix for merge conflict with duplicate query filters

* Another conflict resolved

* Another merge conflict resolved

* Pass the model in, allow changing it
2025-07-21 17:53:43 +01:00
Matt Aitken a90b73c7ca Filter runs by queue, machine, version (#2277)
* Queue in run table and filtering

* Debounce the filter changes

* Remove console log

* Added machine filtering

* Added version filtering

* Filter by version in the db

* Removed duplicate classes

* Version filtering hasFilters consistency

* Added queues and machines to the bulk action summary

* runs.list filtering for queue and machine

* Fix for machine errors
2025-07-21 16:21:46 +01:00
Eric Allam ff018718f7 Added experimental_devProcessCwdInBuildDir config option (#2269)
* Added experimental_devProcessCwdInBuildDir config option

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.

This makes it so the files added via the additionalFiles extension can be read using the same path in dev and deployed tasks.

* Remove claude code reference because it breaks windows tests 😡
2025-07-21 16:06:25 +01:00
Eric Allam 07980f8ebd fix: allow updating run metadata up to 1 hour after completion (#2288) 2025-07-21 16:05:29 +01:00
Eric Allam 8d5c86fea0 v4: simplified release concurrency system and status changes (#2284)
* WIP

* Make release concurrency system extremely simple, everything just releases all the time

* update the deadlock detection to use the new lockedQueueReleaseConcurrencyOnWaitpoint column

* WIP new release concurrency system

* Remove releaseConcurrency and releaseConcurrencyOnWaitpoint

Also removed deadlock detection, and added environment burst concurrency

* Added new DEQUEUED status

Cleaned up the API run statuses, including now detecting new clients and not breaking older clients by adding an API version header to all requests

* Introduce the new "current dequeued concurrency set"

* Remove QUEUED_EXECUTING because we no longer "eagerly" release before checkpointing

* Remove waitpoint test for QUEUED_EXECUTING

* Add isWaiting

* Add changeset

* Use createdAt for ordering realtime runs instead of number

* Clarify the envCurrentDequeuedKey usage

* mock the db.server file to fix the tests

* Updated changset "EXECUTED" -> "EXECUTING"

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-07-21 15:45:27 +01:00
Eric Allam a9b0b4f7c9 Add beta.23 changelog entry (#2286) 2025-07-21 14:34:59 +01:00
nicktrn 4dee2c360e fix(helm): allow electric db url var to be set from secret (#2282)
* correctly use db url from secret for electric

* bump app and chart version

* bump electric version
2025-07-21 13:57:23 +01:00
James Ritchie 4565f19d3a Copyable admin area table fields and autofocus search (#2280) 2025-07-18 18:48:56 +01:00
github-actions[bot] 0796df2aa9 Release 4.0.0-v4-beta.23
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
* Flatten undefined properties correctly (#2276)

* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.23

---------

Co-authored-by: Eric Allam <eric@trigger.dev>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-07-18 11:53:43 +01:00
Eric Allam 6bf580ac21 Flatten undefined properties correctly (#2276) 2025-07-17 18:15:57 +01:00
James Ritchie 3ad4b8b32d Add machine to run list (#2275)
* Access machinePreset from the run list presenter

* New icons for machine presets

* New icon + name combo label for the machine preset

* Adds new “Machine” column to the runs list

* Make a separate component for the machine tooltip info

* add machinePreset to the span presenter

* Show the Machine in the Details tab in the Run inspector

* Show an admin only separator

* Fix docs icon in the button

* Small padding tweak

* Move the Machine nearer the costs

* Don't cast the machine preset

* Remove typecast, better to have a bad label if we add a new machine and don't update thos

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-07-17 14:26:52 +01:00
Dan f685c6637d Added Mastra + Trigger example (#2278)
* Added mastra agent docs

* Added links in the ai agents section

* Caught typos all thanks to coderabbit. Thanks coderabbit
2025-07-17 13:58:59 +01:00
Dan 1dbcf661f3 Added openai agents sdk for typescript playground docs (#2194)
* Added openai agents sdk typescript playground docs and updated guardrails

* Removed broken link and import

* Typo fix
2025-07-17 13:39:18 +01:00
Matt Aitken 6f6852621d Bundle p-map (#2274) 2025-07-16 16:36:36 +01:00
nicktrn aa3a56a85d fix(docker): apply max old space size env var to webapp (#2273) 2025-07-16 16:36:05 +01:00
Matt Aitken 5e999c8992 Use p-map to speed up bulk actions (#2272) 2025-07-16 15:52:42 +01:00
Matt Aitken 569cecb675 The email counts should come from the completed bulk action… not this iteration (#2271) 2025-07-16 13:46:48 +01:00
Saadi Myftija c0b6c41afc Disable payload editing for run replays with large payloads (#2268)
* Use sexy scrollbars for templates and recent runs popover content

@Nick this is for you!!1

* Fix TabButton component disabled state

* Disable payload tab when replaying runs with large payloads
2025-07-15 21:54:43 +01:00
Matt Aitken 35c7a1ecb0 Bulk action 2 fixes (#2267)
* Fix for filtering by run id

Simplified to have a single filter for runId which accepts an array. This unifies the filtering by multiple runs (used by the waitpoint page) and the individual run filtering. It fixes a bug with bulk actions

* Truncate long bulk action titles
2025-07-15 14:43:19 +01:00
Saadi Myftija a64875a297 fix: regression affecting replayed runs with superjson payloads (#2266)
* Fix regression that broke superjson behavior for the replay flow

* Ignore undefined type metadata when overriding superjson payloads

Addresses https://github.com/triggerdotdev/trigger.dev/issues/1968

* Add a few unit tests for replaceSuperJsonPayload
2025-07-15 12:30:33 +01:00
Matthieu Mordrel 8690878a86 Updated generatedCopy to generatedCopy.text (#2247)
I believe the generatedCopy is the object that the AI SDK returns and we want to pass the text of the object rather in our template literal.
2025-07-14 15:58:55 +01:00
Saadi Myftija b78b3dceb2 feat: link the original run from replayed runs (#2262)
* Add ID of the replayedFrom run to the TaskRun schema

* Propagate the replayedFrom run ID in the replay flow

* Link the replayed run in the run details pane
2025-07-14 15:57:54 +01:00
Matt Aitken 630e9556b0 Bulk actions 2.0 (and switch all run listing to ClickHouse) (#2264)
* useSearchParams has

* useSearchParams has

* useSearchParams has

* Consistent way to get the run filters

* Consistent way to get the run filters

* Consistent way to get the run filters

* Initial work on the new bulk actions

* Initial work on the new bulk actions

* Initial work on the new bulk actions

* WIP actions and filtering

* WIP actions and filtering

* WIP actions and filtering

* Empty filter arrays are set to undefined

* Empty filter arrays are set to undefined

* Empty filter arrays are set to undefined

* WIP prisma schema

Removed extra runtimeEnvironmentId

* WIP prisma schema

Removed extra runtimeEnvironmentId

* WIP prisma schema

Removed extra runtimeEnvironmentId

* Migrations

* Migrations

* Migrations

* BulkActionGroup changed some columns around

* BulkActionGroup changed some columns around

* BulkActionGroup changed some columns around

* New badge variant, removed unused ones

* New badge variant, removed unused ones

* New badge variant, removed unused ones

* Bulk action button

* Bulk action button

* Bulk action button

* Make the next runs page the default now

* Make the next runs page the default now

* Make the next runs page the default now

* Improved the RadioButton style

* Improved the RadioButton style

* Improved the RadioButton style

* Remove the old bulk action bar

* Remove the old bulk action bar

* Remove the old bulk action bar

* More UI progress

* More UI progress

* More UI progress

* Lots of UI changes to the Runs page

* Lots of UI changes to the Runs page

* Lots of UI changes to the Runs page

* Fixed period filter resetting everything

* Fixed period filter resetting everything

* Fixed period filter resetting everything

* Improved the Switch secondary style

* Improved the Switch secondary style

* Improved the Switch secondary style

* Buggy filter fixes

* Buggy filter fixes

* Buggy filter fixes

* Improved the filter display and fixed a bug with search param from object

* Improved the filter display and fixed a bug with search param from object

* Improved the filter display and fixed a bug with search param from object

* Clear button is minimal

* Clear button is minimal

* Clear button is minimal

* Using a presenter now

* Using a presenter now

* Using a presenter now

* Bulk actions are created, but not actually processed (yet)

* Bulk actions are created, but not actually processed (yet)

* Bulk actions are created, but not actually processed (yet)

* Bulk replay/cancel is working

* Bulk replay/cancel is working

* Bulk replay/cancel is working

* Multiple fixes, added bulk column to PG

* Multiple fixes, added bulk column to PG

* Multiple fixes, added bulk column to PG

* Bulk action run filtering working using CH

* Bulk action run filtering working using CH

* Bulk action run filtering working using CH

* Replay setting the bulk id on the runs

* Replay setting the bulk id on the runs

* Replay setting the bulk id on the runs

* Properly cap the time when doing a bulk action

* Properly cap the time when doing a bulk action

* Properly cap the time when doing a bulk action

* If the bulk action isn't recent, add it to the dropdown anyway

* If the bulk action isn't recent, add it to the dropdown anyway

* If the bulk action isn't recent, add it to the dropdown anyway

* Blank version of the bulk actions page

* Blank version of the bulk actions page

* Blank version of the bulk actions page

* Individually selected runs working

* Individually selected runs working

* Individually selected runs working

* Use selected mode if runs are checked

* Use selected mode if runs are checked

* Use selected mode if runs are checked

* Added the modal

* Added the modal

* Added the modal

* Marked the old bulk actions stuff as deprecated

* Marked the old bulk actions stuff as deprecated

* Marked the old bulk actions stuff as deprecated

* Renamed bulk action file

* Renamed bulk action file

* Renamed bulk action file

* Bulk run filter with the name and a default

* Bulk run filter with the name and a default

* Bulk run filter with the name and a default

* WIP on bulk actions page

* WIP on bulk actions page

* WIP on bulk actions page

* Updated panel, added new truncated id component

* Updated panel, added new truncated id component

* Updated panel, added new truncated id component

* Style improvements to the radio buttons

* Style improvements to the radio buttons

* Style improvements to the radio buttons

* Added an option action completion email

* Added an option action completion email

* Added an option action completion email

* Adds a blank state for the bulk actions page

* Adds a blank state for the bulk actions page

* Adds a blank state for the bulk actions page

* Nicer completed email

* Nicer completed email

* Nicer completed email

* Don't open the bulk action panel if there are no runs

* Don't open the bulk action panel if there are no runs

* Don't open the bulk action panel if there are no runs

* Runs blank state and bulk action accordion

* Runs blank state and bulk action accordion

* Runs blank state and bulk action accordion

* Updates secondary/small switch style

* Updates secondary/small switch style

* Updates secondary/small switch style

* Pagination buttons no longer split in twain (WIP)

* Pagination buttons no longer split in twain (WIP)

* Pagination buttons no longer split in twain (WIP)

* Aborting working

* Aborting working

* Aborting working

* Bulk action live reloading

* Bulk action live reloading

* Bulk action live reloading

* ListPagination works correctly in all states

* ListPagination works correctly in all states

* ListPagination works correctly in all states

* Run page, show friendlyId instead of number

* Run page, show friendlyId instead of number

* Run page, show friendlyId instead of number

* Bulk action help open by default if you have none

* Bulk action help open by default if you have none

* Bulk action help open by default if you have none

* Extra status filtering step because of replication delay

* Extra status filtering step because of replication delay

* Extra status filtering step because of replication delay

* Wider bulk action onboarding

* Wider bulk action onboarding

* Wider bulk action onboarding

* More sensible widths on the bulk action side panel

* More sensible widths on the bulk action side panel

* More sensible widths on the bulk action side panel

* Border color tweak to the RadioButton

* Border color tweak to the RadioButton

* Border color tweak to the RadioButton

* Improved the accordion component hover states

* Improved the accordion component hover states

* Improved the accordion component hover states

* Updates the bulk action blank state images to the latest UI

* Updates the bulk action blank state images to the latest UI

* Updates the bulk action blank state images to the latest UI

* Added R and C shortcuts back in

* Added R and C shortcuts back in

* Added R and C shortcuts back in

* Fix for selecting a single run

* Fix for selecting a single run

* Fix for selecting a single run

* Improved exit icon, added shortcut to modal

* Improved exit icon, added shortcut to modal

* Improved exit icon, added shortcut to modal

* Tidy imports

* Tidy imports

* Tidy imports

* Tidy imports

* Tidy imports

* Tidy imports

* Tidy imports

* Tidy imports

* Tidy imports

* Tidy imports

* Fix for grid layout when 1 page of bulk actions visible

* Fix for grid layout when 1 page of bulk actions visible

* Fix for grid layout when 1 page of bulk actions visible

* Removed the ... on the abort button

* Removed the ... on the abort button

* Removed the ... on the abort button

* Removed the ... on the abort button

* Animate the progress bar

* Set TZ="UTC" in the env example

* Filter summary in the bulk inspector

* Improves the pagination styling

* Improves the pagination styling

* Delete old bulk action routes

* Removed old Postgres RunListPresenter

* Retry any replication error where the message contains "timeout"

* Increase wait to make test less flaky

* The test was using run id instead of friendly id

* Safer array access

* Remove error log if there's a bad status

* Nicer frontend type safety with the bulk action and mode

* Switched a log to a debug log

* Retry replication unless the error is a known non-retry error

Flip the strategy to retry by default

* Make ClickHouse required

* Backfill run replication admin API endpoint

* Set a CLICKHOUSE_URL for unit tests

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-07-14 15:50:01 +01:00
Eric Allam 6db1b71202 fix: prevent stuck batchTriggerAndWait by finalizing runs when they are cancelled (#2265) 2025-07-14 14:29:14 +01:00
Eric Allam 70b4b1257e fix: Logging large objects is now much more performant and uses less memory (#2263)
* Logging large objects performance fixes

* Add changeset

* docs: new otel attribute count defaults

* Scope the switch/case statement with braces

* Add tests for nested map and sets

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-07-14 13:16:04 +01:00
Saadi Myftija 87c21ab50a feat: enable overriding run options when replaying (#2260)
* Rearrange the layout of the replay modal

* Add run options to the replay modal

* Handle payload and metadata correctly in the shared json editor

* Apply run options in replays

* Fix clear button issue in json editor

Memoization of the clear function caused some problems. Removing it should not cause performance issues.

* Update replay modal hint

* Move machine and version fields to the top for visibility

* Use the same field ordering in the test page

* Reload queues and versions on env override

* Adapt json editor to fill full height

* Clean up a few excessive ternaries

* Avoid ui jump on env selection

* Switch to sexy scrollbars for scheduled tasks in the test page
2025-07-11 16:54:20 +02:00
Matt Aitken 4c635f72a2 Fixed queues SSE live reloading (#2259) 2025-07-11 12:53:28 +01:00
Eric Allam 46dad7dc76 fix: prevent processKeepAlive OOM error process reuse (#2261)
* Improve TaskRunProcess health detection so we don't try and reuse an unhealthy process

This was happening after the process was killed internally, like by an OOM error

* Add changeset
2025-07-11 10:50:05 +01:00
nicktrn 4fdf23b38b fix(deploy): fail in CI with descriptive message and instructions if no access token set (#2258)
* don't try interactive login in ci, link to docs

* add ci note to cli deploy docs

* add ci note to github actions

* add changeset

* make cron test less strict
2025-07-10 17:28:12 +01:00
nicktrn 0c71dc74df feat: add node 22 and bun runtime support with version display (#2254)
* update node-22 image

* update bun image

* disable io_uring

* fix fallback bun path

* prevent duplicate warnings

* add runtime and version to deployments

* runtime icons

* fallback to nodejs

* prevent empty table cell menu

* log if local build on deploy

* pass io_uring env var to child

* denormalize runtime and version, display on run details

* add changesets

* disable pr checks for changeset commits..

* add runtime data to deployed bg workers
2025-07-10 16:43:10 +01:00
Eric Allam 6ff0b923e1 New internal idempotency implementation for trigger and batch trigger (#2256)
* Introduce request idempotency to prevent duplicate triggers

* Implement request idempotency on trigger

* Use x-trigger-request-idempotency-key header instead

Plus cleanup shared logic in the routes

* Add changeset

* Oops, lets not hardcode a 408

* A couple of improvements
2025-07-10 16:42:32 +01:00
nicktrn 1a64013825 fix(helm): external config and secret support (#2245)
* fix postgres and redis external config, incl redis tls

* external secret support for postgres, redis, clickhouse, plus fixes

* s3: full existing secret support, external fixes

* bump chart version

* add subchart links

* update external config docs

* tidy up validation and helper
2025-07-10 10:08:00 +02:00
nicktrn f456f68f88 fix(docs): force latest buildx version in sample gh action (#2252) 2025-07-09 19:41:02 +02:00
Saadi Myftija 28b6be2491 feat: introduce run templates for reusing test run configs (#2253)
* Add new prisma model for task run templates

* Create run templates in a new service

* Add modal to create run templates in the test page

* Show templates list and apply values when selected

* Hide template creation time in the dropdown list, only show date

* Enable deleting run templates

* Show success toast on template creation

* Validate template label length

* Improve the template creation success indicator

* Use formAction consistently to differentiate submissions

* Type formAction for better editor support

* Prettify run template payload and metadata

* Add triggerSource, concurrencyKey and ttl to run templates
2025-07-09 19:09:25 +02:00
Saadi Myftija dbcf632b4e fix: add missing field descriptions in the test page (#2251)
* Switch to sexy scrollbars in the options pane

* Add missing field descriptions

* Add a run options link to the docs

* Make the copy for the field descriptions more concise

* Fix tooltip issue in the Select component
2025-07-09 16:25:06 +02:00
Saadi Myftija 3cfde48bfd feat: expose all run options in the test run page (#2227)
* Implement a new primitive UI component for picking durations

* Implement a new component to input run tags

* Expose all run options in the test run page

* Add subtle animations when adding/removing run tags in the test page

* Add a new resource endpoint for fetching queues

* Fetch usable queues for the selected task

* Fix width display issue in the select component

* Enable locking a run to a version from the test page

* Disable entering max attemps <0

* Validate tags

* Add recent runs popover

* Only show latest version for development environments

* Update run options when selecting a recent run

* Rearrange the test page layout

* Add subtle animation to the duration picker segments on focus

* Improve queue selection dropdown styling

* Fix disabled state issue for the SelectTrigger component

* Disable version selection field for dev envs

* Add usage hints next to the run option fields

* Add machine preset to the run options list

* Allow arbitrary queue inputs for v1 engine runs

* Show truncated run ID instead of run numbers for recent runs

Run numbers will soon get deprecated due to contention issues

* Fix duplicate queue issue

* Extract common elements across the standard and scheduled test task forms

* Apply values from recent runs to scheduled tasks too

* Add additional run options for scheduled tasks

* Use a slightly smaller font size for run option labels

* Disallow commas in the run tag input field

* Switch to a custom icon for recent runs button

* Flatten the load function test task result object

* Avoid redefining machine presets, use zod schema instead

* Fix ClockRotateLeftIcon jsx issues

* Remove recent runs button tooltip as it causes nesting errors

* Adjust the page layout to make it clear which task is currently selected

* Inline the tab group with the copy/clear buttons
2025-07-09 10:40:10 +01:00
Eric Allam ba2e0cc1c6 Prevent circular references from breaking run logs in task outputs (#2249) 2025-07-09 10:28:12 +01:00
Eric Allam b113e5a99c Fix non-MFA login (#2246)
* Fix non-MFA login

* fixed typecheck errors
2025-07-08 14:59:31 +01:00
Eric Allam b119a52e08 Implement MFA (#2244)
* Adds a new route for logging in with mfa

* New path for security page

* Adds “Security” link to account side menu

* Update the Switch component to allow label positions left and right

* Optionally hide the Close button in the Dialog title bar

* Installs `qrcode` react package for generating QR codes.

* CopyButton component now takes children

* New Security route for setting up MFA

* Adds new OTP package for the chadcn InputOTP component

* Adds new InputOTP chadcn component

* Adds InputOTP chadcn component to the MFA login screen

* InputOTP component supports variant styles

* Improvements to form handling

* Show a confirmation modal before you can disable MFA

* Revert redirect back to the dashboard for now

* Implement MFA enabling and disabling

* Refactor and cleanup mfa management code

* More cleanup

* Handle errors in the management action

* Implement mfa login flow

* recovery code input should be password

* Implement rate limiting on the mfa validation endpoint

* Better error ux

* Implement mfa emails and apply James' updates

* Use latest @better-auth/utils

* Improvements via CodeRabbit review

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-07-08 13:44:47 +01:00
nicktrn e02c90a4ea Update self hosting images to beta.22 (#2241)
* docker: set new cli tag env var

* helm: update app and chart versions
2025-07-08 09:33:27 +01:00
Eric Allam 493ca11c43 Add ability to disable the prisma query in the healthcheck endpoint (#2242) 2025-07-07 16:41:55 +01:00
Eric Allam b36d81a622 Add v4 changelog to v4 docs (#2240)
* Add v4-beta changelog docs

* Add beta.22 changelog entry
2025-07-06 11:38:10 +01:00
Eric Allam 275ce9f290 Release 4.0.0-v4-beta.22 (#2238)
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
2025-07-04 16:15:15 +01:00
github-actions[bot] 5cff5c45e9 chore: Update version for release (v4-beta) (#2159)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-04 16:13:20 +01:00
Eric Allam 47e583c19d Properly scope the batch list to the selected environment (#2236)
Prevents showing batch runs from environments that are not in the selected environment
2025-07-04 14:41:10 +01:00
nicktrn 1019c9cdea Switch trigger Helm chart RBAC from cluster to namespace scope and bump version (#2232)
* replace supervisor cluster role with ns-scoped role

* fix cross-ns binding

* bump chart version
2025-07-04 13:54:25 +01:00
Eric Allam 469a8df0cf Prevent redis worker from enqueuing pending jobs (#2235) 2025-07-04 13:53:32 +01:00
Eric Allam 922c7cb3a6 Increase batch trigger processing job visibility timeout (#2234) 2025-07-04 13:43:26 +01:00
nicktrn 0fcb9705ae Document new otel limits (#2196)
* update webapp env vars

* update limits
2025-07-04 13:41:54 +01:00
nicktrn 74fdfa93c5 Show project details when running the whoami command (#2231)
* add project details to whoami

* findFirst

* skip project check if no orgs found

* add changeset
2025-07-04 13:40:41 +01:00
Eric Allam bdaa2ed15a Move batch trigger processing jobs to a dedicated redis worker (#2233) 2025-07-04 12:16:05 +01:00
nicktrn afc9a6ade1 Add ECR support for remote builds (#2224)
* create repo if doesn't exist

* fresh auth token for each deploy

* optional assume role

* log when machine overrides enabled

* make test repo namespace configurable

* assume role fix and env var changes

* improve ecr check

* improve tag parsing

* tag parsing tests

* track if repo created and fix test

* missing tryCatch for sts call
2025-07-03 14:25:30 +01:00
Eric Allam 7bb7e7aedc When sharding, use the where clause in the shard key to distribute requests more evenly (#2229) 2025-07-03 14:21:58 +01:00
Eric Allam e90235b317 fix processKeepAlive disabled performance regression (#2228)
* processKeepAlive disabled performance fix

When processKeepAlive was disabled, the eagerly created TaskRunProcess being discarded and a whole new TaskRunProcess created at execution time.

* Don't use a TaskRunProcess if it's being killed, fixes retryImmediately
2025-07-03 14:21:49 +01:00
James Ritchie b7134dd769 Docs: Adds 2 references to the node.js versions we use for v3 and v4 (#2118)
* Adds 2 snippets to reference node.js version for v3 and v4

* Updated the node versions we support

* Remove specific node version from title
2025-07-02 13:08:49 +01:00
nicktrn 6fea802505 helm: set default webapp container and add extraVolumes (#2222)
* set default webapp container

* bump version

* add webapp extraVolumes and extraVolumeMounts

* bump version
2025-07-02 13:03:02 +01:00
Eric Allam 09ef7625ff Merge pull request #2219 from triggerdotdev/metadata-serialization
Serialize metadata to prevent invalid data from breaking run completions
2025-07-02 12:49:08 +01:00
Eric Allam 5cf37e9f8a Merge pull request #2189 from triggerdotdev/increased-and-configurable-attribute-limits
Increase otel attribute limits and make them configurable
2025-07-02 12:48:38 +01:00
Eric Allam e7b6c2fab6 Merge pull request #2183 from triggerdotdev/process-keep-alive
v4: Experimental process keep alive
2025-07-02 12:39:43 +01:00
nicktrn d619e79f74 helm: increase default postgres resource preset (#2221)
* switch to small preset for postgres

* lint

* bump version
2025-07-02 11:57:14 +01:00
Eric Allam 38a94b3b70 Add changeset 2025-07-02 11:30:13 +01:00
Eric Allam 0c2af6d87c Increase otel attribute limits and make them configurable 2025-07-02 11:30:13 +01:00
Eric Allam 0deb2c640d remove change from process-keep-alive 2025-07-02 11:30:13 +01:00
Eric Allam 21c70ad673 Serialize metadata to prevent invalid data from breaking run completions 2025-07-02 11:30:13 +01:00
Eric Allam 0793c2b0f4 address some nick comments in the review 2025-07-02 11:30:13 +01:00
Eric Allam 8dc90d61b4 increase run engine test timeouts 2025-07-02 11:30:13 +01:00
Eric Allam 57f3a89a86 Fix timeline span events tests 2025-07-02 11:30:13 +01:00
Eric Allam ceb737e9f1 Fix broken webapp test 2025-07-02 11:30:13 +01:00
Eric Allam 632d6b91e9 Fix taskSlug extraction 2025-07-02 11:30:13 +01:00
Eric Allam b548561220 Remvoe the sometimes incorrect import file name 2025-07-02 11:30:13 +01:00
Eric Allam aecee4d35f Don't reset task lifecycle hooks between executions 2025-07-02 11:30:13 +01:00
Eric Allam 9ab590bead Better handle snapshot changes with the new persistent task run process,
Also don’t re-import the task file if the task is already in the resourceCatalog
2025-07-02 11:30:13 +01:00
Eric Allam 2cc1453f25 Always set the cold/warm variant on attempt spans, even in dev 2025-07-02 11:30:13 +01:00
Eric Allam 82f3db6825 Only bootstrap on the cold start execution, and record how many executions in the attempt span 2025-07-02 11:30:13 +01:00
Eric Allam f0bc4ad2bd Don't delete env vars with falsy values 2025-07-02 11:30:13 +01:00
Eric Allam 4d15c8f7cf Better reset in the prod usage manager 2025-07-02 11:30:13 +01:00
Eric Allam 7f3b1c1735 Ensure the usage interval is cleared when resetting 2025-07-02 11:30:13 +01:00
Eric Allam 707bc09dac Make sure maxExecutionsPerProcess is a positive integer 2025-07-02 11:30:13 +01:00
Eric Allam a0f2c982f9 Make sure to clear existing metadata flush timer when resetting 2025-07-02 11:30:13 +01:00
Eric Allam bffd18a6b8 Properly reset listener arrays by setting length to 0 2025-07-02 11:30:13 +01:00
Eric Allam 2018b41d22 TRIGGER -> TRIGGERDOTDEV 2025-07-02 11:30:13 +01:00
Eric Allam c41d63497b Handle immediate retries better 2025-07-02 11:30:13 +01:00
Eric Allam 56146afcb6 TaskRunProcessProvider cleanup is async 2025-07-02 11:30:13 +01:00
Eric Allam 020565c009 Handle dev process pool shutdown errors 2025-07-02 11:30:13 +01:00
Eric Allam 58d56f8381 Use hostname instead of host 2025-07-02 11:30:13 +01:00
Eric Allam 5b49b420cb Add changeset 2025-07-02 11:30:13 +01:00
Eric Allam 3fb68b784a couple of tweaks for testing stuff 2025-07-02 11:30:13 +01:00
Eric Allam a414e65efe handle env vars that have been removed between executions 2025-07-02 11:30:13 +01:00
Eric Allam 38fe854559 Make refreshing a task run process work correctly 2025-07-02 11:30:13 +01:00
Eric Allam c24f3a56cc Reset deployed run worker and also get the warm start stuff to work properly 2025-07-02 11:30:13 +01:00
Eric Allam 8798352be3 Super-warm starts in deployed tasks proof 2025-07-02 11:30:13 +01:00
Eric Allam 1a1e70ae3d WIP deployed processKeepAlive 2025-07-02 11:30:13 +01:00
Eric Allam 19bdf3b6ea scope the pool by background version, allow configuring pool params in dev/prod 2025-07-02 11:30:13 +01:00
Eric Allam 427c450391 Fixed metrics, reusing a process counts as a warm start 2025-07-02 11:30:13 +01:00
Eric Allam 9374aea401 Split resource and span attributes 2025-07-02 11:30:13 +01:00
Eric Allam fd3db5f77b Reset execution environment between runs 2025-07-02 11:30:13 +01:00
Eric Allam e2049fcfbf Initial dev process keep alive. moved resource attributes to span attributes because resource attributes are global and immutable 2025-07-02 11:30:13 +01:00
Eric Allam 1be4fcbb82 Fixes the jitter option for the scanConcurrencySets cron job (#2220) 2025-07-02 11:29:54 +01:00
Eric Allam 124435e5e7 Eliminate race conditions in continueRunIfUnblocked to prevent stuck runs (#2217)
* Eliminate race conditions in continueRunIfUnblocked to prevent stuck runs

* Use assertNever for typecheck friendliness
2025-07-01 16:34:12 +01:00
Matt Aitken 4a79b3b35b Fixed retrying on run replication inserts on connection errors (#2216) 2025-07-01 13:35:13 +01:00
Boon Kai 0da6a19987 docs: Update triggering.mdx import for triggering from inside another task (#2215)
When triggering from inside another task, `runs` and `batch` should be imported from `@trigger.dev/sdk/v3`
2025-07-01 08:03:02 +01:00
Eric Allam 08d84eb596 Fix v4 concurrency sweeper flaky test (#2213) 2025-06-30 16:37:52 +01:00
Matt Aitken f38d35e9f6 ClickHouse replication improvements (retrying, strip bad unicode chars) (#2205)
* Add retry logic for insert operations

Add a generic retry mechanism for task run and payload inserts to handle
transient connection errors. The new #insertWithRetry method retries up to
three times with exponential backoff and jitter on retryable connection
errors such as connection resets or timeouts. Errors are logged and
recorded in tracing spans to improve observability and robustness of the
replication service.

* Replication settings are configurable

* Log out the runIds for failed batches

* Detecting bad JSON in run replication and ignoring it

* Reproduced split unicode error

* Move output file

* Massively improved the performance

* Minor performance improvements

* Unskip tests

* Remove unused test in CH package

* Fix for the ClickHouse UI explorer

* RunReplication keepAlive defaults to false

* Add concurrency_key and bulk_action_group_ids to ClickHouse task runs

* ClickHouse package doesn't need to be built anymore for the webapp

* Set the concurrency_key from the run replication service
2025-06-30 16:32:02 +01:00
Eric Allam 1b4dabb8ba v4: current concurrency sweeper (#2206)
* v4: current concurrency sweeper

* Fix webapp tests

* Ensure only a single instance performs concurrency sweeping by using redis-worker cron jobs

* Improved the mark phase

* Ensure cron jobs get rescheduled even if the handler throws an error

* Better property names
2025-06-30 16:22:19 +01:00
nicktrn aa24a9aae6 Fix external registry config and other helm tweaks (#2212)
* fix both internal and external registry host

* add next steps to self-hosting overview

* separate into webapp and registry ingress

* fix external registry config check

* fix localhost registry warning

* bump chart version
2025-06-30 14:21:00 +01:00
nicktrn dc568e7f3e Self-hosting: improved worker and bucket bootstrap (#2209) 2025-06-29 10:42:52 +01:00
nicktrn fa6675eb00 Helm chart improvements (#2207)
* add helm deps

* configurablecli tag for setup commands

* enabled -> deploy

* extraEnv -> extraEnvVars

* update docs

* minio -> s3

* improve persistence config

* set cli tag to v4-beta

* chart and image bump

* fix tests

* lock chart

* workflow hotfix

* quote for niels

* make probes configurable

* fix webapp and supervisor templates

* bump chart
2025-06-28 12:59:29 +01:00
Eric Allam 8a86e7b41d Remove deprecated schedule trigger jobs when registering with the schedule engine (#2204)
* Remove deprecated schedule trigger jobs when registering with the schedule engine

* Use a tryCatch just to be absolutely sure
2025-06-27 12:32:02 +01:00
nicktrn 119bfc0068 Fix update command version detection (#2199)
* fix root package.json resolution

* add changeset
2025-06-27 10:06:36 +01:00
nicktrn 7a6d9dc225 v4 self-hosting docs updates (#2202)
* quote version strings

* Update architecture diagram to include kubernetes

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-06-26 16:53:10 +01:00
Eric Allam 0f3179f493 Provide realtime skipColumns option via untamperable public access tokens (#2201)
* Provide realtime skipColumns option via untamperable public access tokens

* Fixed realtime client tests

* Create red-rings-marry.md
2025-06-26 15:13:26 +01:00
Eric Allam b87e1c4630 Improve clickhouse client debug logging (#2197)
* Improve clickhouse client debug logging

* Stop hardcoding ScheduleEngineWorker log level

* Add DLQ debug log message
2025-06-26 15:12:54 +01:00
Eric Allam 14058d557f fix: queues no longer unpaused on deploy (#2198) 2025-06-26 15:08:52 +01:00
nicktrn 6535cf918b v4 helm chart and docs (#2195)
* v4 helm chart

* ignore packaged chart archives

* chart publish prep

* add helm chart release workflow

* enable draft gh release

* stateful services should be statefulsets

* expose internal otel config

* podAnnotations for all services

* postgresql.extraArgs

* postgresql -> postgres

* add clickhouse vars as extraEnv examples

* support external secrets

* improve clickhouse config

improve clickhouse config

* disable internal otel config by default

* allow nil internal config

* fix gh release permission

* fix validation step for ci

* secrets.enabled

* simplify secret keys

* bump chart version

* fix contents permission

* remove redundant print steps

* remove helm release heading

* optimise clickhouse config

* support wait-for-it.sh

* fix ch overrides

* add low-resource ch overrides to compose setup

* supervisor bootstrap config

* disable otel trace logging

* retain shared volume

* enable clickhouse for compose setup

* disable trace logging

* bump chart

* add extraManifests

* telemetry config

* add repo namespace config

* scope cluster role to namespace to support multiple installs

* external s3 config

* values tweaks

* helm docs

* bump chart

* set contents read permission

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* add object store root credentials warning

* run init containers as uid 1000

* pass ch user creds with explicit flag

* improve ingress anotation handling

* remove duplicate colume claim block

* trim htpasswd auth

* lock curl images used for tests

* add v4-beta package version warning

* make schema and sslmode configurable

* package tag info -> warning

* improve secrets config example

* update docs

* bump version

* bump webapp reqs to account for ch

* make ch creds more easily configurable

* publish ch http port

* example point at latest beta chart

* small docs tweaks

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-06-25 23:57:51 +01:00
Eric Allam aaf40c3be3 Fix "Duplicate path found during parsing JSON object" clickhouse error (#2193) 2025-06-24 14:34:32 +01:00
James Ritchie 35dedee1eb Update number of allowed tags (#2187) 2025-06-24 10:38:59 +01:00
Eric Allam 485f71c8d4 Improve schedule engine performance (#2191)
* Improve schedule engine performance

* Convert an info log to a debug log
2025-06-23 16:55:18 +01:00
nicktrn ec2175fec8 Some docs tweaks (#2190)
* add v4 tag to playwright extension

* add self-hosting architecture section

* add v4-beta tag warning
2025-06-23 10:25:07 +01:00
Eric Allam fb9abe5289 fix: Incorrect/missing logs on new schedule engine runs (#2185)
* Fix incorrect logs on new schedule engine triggered taskss

Also added the ability to recover schedules in the schedule engine via an Admin API endpoint in the new schedule engine

* Fixed schedule recovery failing test
2025-06-19 15:37:50 +01:00
Eric Allam f2db1b8aa5 Create dedicated alerts worker (#2184)
* Add a new alerts worker that just handle alerts

* fallback to graphile worker when a tx is required
2025-06-19 11:54:49 +01:00
nicktrn 3c93783e8b remove nodetype label requirement (#2182) 2025-06-18 11:34:35 +01:00
nicktrn 55f41a2168 Ensure webapp container does not fetch pnpm at runtime (#2181)
* install pnpm during build

* install pnpm for node user as well
2025-06-18 11:31:36 +01:00
Matt Aitken 4ca6506655 Delay reschedule fix (v3 & v4) (#2180)
* Fix for v3 rescheduling bug…

* v4 rescheduling working
2025-06-17 18:22:15 +01:00
Matt Aitken 9f9e74b0b6 Fix hydration error (#2179)
* Remove unused hook

* The Impersonation button was causing DOM hydration issues
2025-06-17 11:17:17 +01:00
Eric Allam ea6f66bdfc Improved schedule engine (#2174) 2025-06-17 06:48:05 +01:00
Eric Allam a42c9acf44 v4: Improved run locking (#2173) 2025-06-17 06:45:59 +01:00
nicktrn f7f8bc28c5 Docker self-hosting improvements (#2177)
* shallow clone

* fix image tag locking example

* electric should use db url env var

* add htpasswd note

* use local driver with log rotation by default

* make worker urls more easily configurable

* configure dev otel endpoint via .env

* increase min recommended worker specs

* move worker url section
2025-06-16 22:22:02 +01:00
Matt Aitken 5f6fd8e235 Tasks page didn't include Delayed runs (#2176)
* Delayed runs weren’t shown in the Tasks page mini graph

* The Tasks page Queued count didn’t include Delayed runs (since ClickHouse switchover)
2025-06-16 14:14:09 +01:00
James Ritchie 33e1008d83 Adds a docker error entry to common problems docs page (#2172) 2025-06-14 11:11:24 +01:00
Matt Aitken a0815c8e3e Clickhouse performance improvements (#2175)
* Runs filter by org id and add created at to ordering

* CopyableText can accept an alternative value for copying

* The runs table now shows the ID instead of run number

* Paginating back/forwards fix

* The task stats need org id and project id too
2025-06-14 11:10:28 +01:00
James Ritchie 8a5e7f34b1 Added more limits and information to the limits page (#2171) 2025-06-13 10:32:44 +01:00
Matt Aitken c3dca5835d Fix for the Kapa widget not working with SSR (#2170)
* Move all the logic into a single component, preparing for client only

* Use ClientOnly

* Fix for search param not working
2025-06-12 22:17:22 +01:00
James Ritchie 20a8e7c6c1 Updated kapa sdk to 0.1.3 (#2169) 2025-06-12 16:21:34 +01:00
Eric Allam cc5514e57b Add status-first TaskRun index to speed up uncommon status filters (#2168) 2025-06-12 15:02:40 +01:00
Eric Allam ab432a2d7d Fix realtime re-subscribing stale data issue (#2162)
* Fix realtime re-subscribing stale data issue

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

* removed logs
2025-06-12 14:58:01 +01:00
James Ritchie e12c82b444 Use the Kapa AI SDK instead of the Kapa AI widget (#2113)
* Install the kapa sdk

* WIP using the SDK for the Kapa Ask AI widget

* Removes old kapa from root

* Now rendering everything inside the dialog component

* Fixes min-height of dialog content

* Remove kapa from root

* prevents kapa using reCaptcha

* Adds more functionailty with temporary UI placement for now

* Reset conversation button

* Adds a new sparkle list icon

* Adds some example questions as a blank state

* Animate in the example questions

* use “marked” package to render markdown

* Improve some animations

* Submit a question from the URL param

* adds custom scroll bar styling

* fixes modal to correct height after re-opening it

* Add button to stop generating answer mid-stream

* Adds buttons states to show submitting, generating, submittable

* Adds a helpfull sentence in the blank state

* Show a message if the chat returns an error

* Adds reset chat and feedback buttons to the bottom of an answer

* Makes sure you can give feedback in the different states of chat

* Adds a suble background to the dialog

* Fix a button inside button error

* Improve the shortcut esc key on dialog and sheet component

* Fix classname error

* organize imports

* Use our custom focus-visible

* Move the Tooltip for the button into the AskAI component

* Improved error message

* Organize imports

* Animated the modal gradient

* Small layout improvements

* Adds most asked questions from Kapa

* border glow tweak

* AskAI component is now a hook that can take a question

* remove kapa script

* Add a delay before the modal opens when usign the URL params

* Remove old component

* Update to the latest Kapa version

* Rephrased error message

* Use correct types for conversation

* Fixed types for addFeedback

* Adds DOMPurify package

* removed unused const

* Removed unnecessary platform specification

* Reset the timeout when the ai panel pops up

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Fix for coderabbit bad commit

* Clean up imports

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-06-12 14:09:03 +01:00
Dan 808c299008 Added deep research example (#2166)
* Added docs

* Improved docs and added video

* Added upgrade badge
2025-06-11 16:19:25 +01:00
Eric Allam 498b9a21af Improved TaskRun environment indexes (#2164)
### PR: Optimize **TaskRun** indexes for hot-path queries

**What changed**

| Object                          | Type                                                            | Purpose                                                                                                    |
| ------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `taskrun_runtime_id_desc_idx`   | **BTREE** `(runtimeEnvironmentId, id DESC) INCLUDE (createdAt)` | Eliminates explicit sort for the “latest task runs” query (`ORDER BY id DESC`) while remaining index-only. |
| `taskrun_runtime_createdat_idx` | **BTREE** `(runtimeEnvironmentId, createdAt DESC) INCLUDE (id)` | Accelerates the filter-only path that scans by `createdAt >= …` without any ordering requirement.          |
| `taskrun_createdat_brin`        | **BRIN** on `createdAt` (`pages_per_range = 128`)               | Lets the planner skip whole blocks older than the time window for both queries at < 100 MB cost.           |
| *(cleanup)*                     | **DROP** `TaskRun_runtimeEnvironmentId_createdAt_id_idx`        | Retires the 3-column index once the new ones are built.                                                    |

**Key details**

* All indexes created **CONCURRENTLY** to avoid write blocking.
* `fillfactor = 90` on b-trees for balanced space vs. future growth.
* Net disk usage drops **≈ 15–20 GB** while each query now gets a purpose-built access path.

**Why**

* Remove planner Sort nodes for the top-N “latest runs” view.
* Speed up environment-filtered range scans.
* Shrink index bloat and improve cache efficiency.
2025-06-11 09:24:11 +01:00
Eric Allam 48e1e7250b v4: fix stuck batch issue when processing happens before transaction commits (#2163)
* v4: fix stuck batch issue when processing happens before transaction commits

* Fix flaky test
2025-06-10 17:02:34 +01:00
Eric Allam 3f2b748a80 Delete the secure param before passing it to the ClickHouse client (#2161) 2025-06-10 13:55:19 +01:00
Eric Allam a060ceef0b Fix clickhouse migrations by adding the secure=true query param (#2160) 2025-06-10 13:45:24 +01:00
Eric Allam e6fb32194e Add environment filter to declarative schedules (#2148) 2025-06-10 12:11:11 +01:00
Eric Allam b38405cb88 Realtime and task run performance improvements (#2158)
* Add createdAt filter to realtime subscribing with tags

* Filter realtime colums and expose ability to skip some columns

* Add sharding support for electric

* Use unkey cache for the created at filter caching

* Remove 2 unused indexes on TaskRun

* Run list now filters by a single runtime environment

* Remove project ID indexes

* Use clickhouse in task list aggregation queries instead of pg (keep pg for self-hosters)

* WIP clickhouse powered runs list
stuff

* Improve the query to get the latest tasks for the task list presenter

* Update the usage task list to use clickhouse

* Implement next runs list powered by clickhouse

* Add new index for TaskRun for the runs list, by environment ID

* Add runTags gin index

* Handle possibly malicious inputs

* Ignore claude settings

* Better handling not finding an environment on the schedule page

* Use ms since epoch in test, not seconds

* Remove unused function

* Fix test

* Use an env var for the realtime maximum createdAt filter duration (defaults to 1 day)

* Fixed the query builder to correct the group by / order by order

* Make sure runs.list still works

* Create small-birds-arrive.md
2025-06-10 12:11:01 +01:00
James Ritchie e617c14456 Adds a troubleshooting entry for react.email not being bundled correctly (#2117) 2025-06-09 16:43:32 +01:00
James Ritchie 15fd731f3d Fixes embeded video width (#2121) 2025-06-09 16:40:59 +01:00
Eric Allam 4c7742c258 Enable docs AI contextual menu (#2157) 2025-06-09 10:04:56 +01:00
nicktrn e72820c080 Docs improvements for v4 self hosting (#2156)
* add switch command

* couple of changes
2025-06-07 19:51:47 +01:00
nicktrn 7a34c1102b Feat: v4 self-hosting (#2155)
* self-hosting stuff goes in /hosting

* add v4 tags

* add main compose file

* draft overview

* add webapp env vars

* overview tweaks

* add supervisor env vars

* move old docker guide

* new sidebar structure

* update github actions docs

* docker draft

* use env vars for s3 creds

* this might just work

* split into multiple files

* update guide

* document machine overrides

* split legacy docs into different section

* some fixes

* some tweaks

* add login and init instructions

* don't cursorignore .env.example
2025-06-07 00:57:47 +01:00
Eric Allam 2b3d54aff5 Make removing the httpEndpointEnvironmentId column migration correctly idempotent (#2154) 2025-06-06 11:52:14 +01:00
github-actions[bot] bb1f0a711a Release 4.0.0-v4-beta.21 (#2149)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 3s
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.21

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-06-06 10:50:28 +01:00
nicktrn 47b19a49d4 A few small v4 fixes (#2153)
* remove unused env vars

* actually use ALERT_REPLY_TO_EMAIL for alerts (non-breaking)

* increase fallback branch limit to 100M

* more unused env vars and example

* hide usage page when self-hosted

* fix for init flow

* set default or concurrency to 300 to match 100 on env

* fail fast when registry env vars are empty strings
2025-06-06 10:15:07 +01:00
Eric Allam 5301239e09 chore: remove v2 models from schema (#2107)
* Round 1 of v2 model removals

* Round 2

* Remove trigger http endpoint env

* Round 3

* Round 4

* Round 5

* Round 6

* Round 7

* Round 8

* Round 9

* Round 10

* Removed the remainder v2 code from the webapp

* GitButler WIP Commit

---------

Co-authored-by: GitButler <gitbutler@gitbutler.com>
2025-06-05 22:57:29 +01:00
Eric Allam 261dce102a v4: fix batchTriggerAndWait completion issues by processing batch chunks sequentially (#2152)
* v4: fix batchTriggerAndWait completion issues by processing batch chunks sequentially

* Remove unnecessary test
2025-06-05 17:56:56 +01:00
Eric Allam 575413c414 fix: moved dequeuing from a worker queue out of lua to be able to use blpop (#2151) 2025-06-04 22:04:30 +01:00
nicktrn e7795a06ad Fix: fixes and prerequisites for v4 self-hosting (#2150)
* remove pgadmin

* remove V3_ENABLED

* v3 is always enabled

* enfore docker machine presets by default

* rename autoremove env var

* prefix more k8s-specific env vars

* same prefix for all docker settings

* improve profile switcher copy

* supervisor can load token from file

* optional webapp worker group bootstrap

* fix error message

* fix app origin fallback for otlp endpoint

* use pnpm cache for webapp docker builds

* increase default org and env concurrency limit to 100

* optional machine preset overrides

* improve s3 pre-signing errors

* fix DOCKER_ENFORCE_MACHINE_PRESETS bool coercion

* shard unit tests

* fix for s3-compatible services

* optional object store region

* Update apps/supervisor/src/workerToken.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix DEPLOY_REGISTRY_HOST example

* fix platform mock

* remove remaining v3Enabled refs

* fix error type.. bad bot

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-06-04 18:02:38 +01:00
Eric Allam 2b3ea692fe v4: dequeue performance improvements (split concurrency from dequeue) (#2127)
* WIP

* Run queue now works with the worker queue / master queue split

* Acking should also cause the master queue to be processed

* Convert run engine tests and run engine to use runQueue changes

* Include the util files in the test tsconfig

* coordinator target should be es2020 as well

* providers target 2020

* Fix the triggerTask tests in the webapp

* v4 now working with the new worker queues, and added the legacy master queue migration stuff

* report worker queue lengths via opentelemetry metrics

* Adding lock metrics

* Release concurrency bucket metrics

* • Updated RunQueue.removeEnvironmentQueuesFromMasterQueue() method signature to take runtimeEnvironmentId instead of masterQueue parameter
• Added automatic master queue shard calculation using this.keys.masterQueueKeyForEnvironment(runtimeEnvironmentId, this.shardCount) 
• Updated RunEngine wrapper method to use new runtimeEnvironmentId parameter
• Updated DeleteProjectService to call the method once per environment instead of once per master queue
• Simplified API by encapsulating master queue sharding logic within RunQueue class

* metrics now working, configure the run queue settings, additional metrics for run engine and redis-worker

* Fix CodeRabbit suggestions

* return undefined from dequeueFromWorkerQueue, not null

* Remove message from worker queue in certain circumstances when acking

* Update log

* Ensure master queue consumers cannot stop from a processing error, and make the consumer interval configurable via an env var

* Change how the run queue master queue consumers are disabled internally

* Fixed tests

* process the queue on nack

* Fix more tests

* Fix priority tests

* Fixed dequeueing test
2025-06-04 17:18:46 +01:00
nicktrn f603725393 Feat: unified deploys for self-hosted and cloud users incl. multi-platform support (#2138)
* remove registry proxy

* remove --self-hosted flag

* automatically set network build flag

* update syncEnvVars debug log

* improve switch command

* always display deploy errors if they exist

* fix stuck deploy command after finalize error

* webapp-driven deploys, multi-platform support, lots of fixes

* add worker deployment migration

* rename image platform env var

* only try to sync parent env vars for preview deployments

* add KEEP_TMP_DIRS

* supervisor: docker api version lock, auth, multi-platform

* set image ref on create, validate digest

* use metadata for digest, fix local multi-platform builds

* print git meta branch before commit

* improve push and load flag handling

* make runs after local builds compatible with load and push

* small improvement for platform overrides

* add image platform to dequeued message

* remove deprecated init request body fields

* fix fail deployment id param

* remove build debug logs

* pass report merge with no tests

* structured run debug logs

* add required env var for tests

* should not be an error log

* add changeset
2025-06-04 15:54:10 +01:00
nicktrn a61951049f Enhance webapp version with build info (#2146)
* improve app version output

* set build info

* fix typo

* always show additional build info when self-hosting

* Update apps/webapp/app/routes/_app.orgs.$organizationSlug.settings/route.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix build timestamp name

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-06-04 15:49:07 +01:00
nicktrn 7ee0992a2f Runtime agnostic SDK configuration via env vars (#2132)
* use std-env in getEnvVar

* add changeset
2025-06-04 15:48:52 +01:00
nicktrn eee0fb2198 Fix tar-fs dependabot alerts v3 (#2145)
* update testcontainers

* override tar-fs used by testcontainers
2025-06-03 14:29:54 +01:00
nicktrn 1b62b348a9 Fix tar-fs dependabot alerts (#2143)
* update dockerode

* override tar-fs for remix dev
2025-06-03 14:10:35 +01:00
github-actions[bot] a2a5204ac5 chore: Update version for release (v4-beta) (#2144)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.20

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-06-03 13:40:04 +01:00
Matt Aitken d4a9cefd78 syncVercelEnvVars fix for syncing the wrong preview branch env vars (#2141)
* syncVercelEnvVars fix for syncing the wrong preview branch env vars

* syncVercelEnvVars() fix for syncing the wrong preview branch env vars
2025-06-03 13:15:03 +01:00
nicktrn 9fca9e0508 Release 4.0.0-v4-beta.19 (#2140) 2025-06-02 15:32:25 +01:00
Matt Aitken d6dd2c9210 Preview branches docs (#2130)
* Draft preview branches docs

* More wip on docs

* Explain what the GH action does

* Added manual deploy/archive instructions

* Added dashboard section to the docs

* More wip on the preview branch docs

* Added preview/branch to the CLI deploy docs

* Add preview branch note to triggering docs

* Added preview branch notes to the API keys docs

* Added preview stuff to the Context docs

* CLI preview archive command

* Add a better explanation and details about env vars

* Fix for weird reference to the dashboard
2025-06-02 15:31:48 +01:00
github-actions[bot] 01e7ef3d04 chore: Update version for release (v4-beta) (#2110)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-02 15:21:16 +01:00
nicktrn e84d8cfa1d Fix: lock vitest version in CI (#2139) 2025-06-02 15:14:33 +01:00
Matt Aitken 26b0523cd0 Support passing “production” when deploying (#2137) 2025-06-02 14:18:36 +01:00
nicktrn 84d8fb575f Fix syncing parent env vars for non-preview deployments (#2131)
* only try to sync parent env vars for preview deployments

* add changeset
2025-06-02 10:40:06 +01:00
Matt Aitken 80d449bfdb Preview branch alerts (#2136)
* Preview branch alerts

* Show “Preview” not the branch for new alert modal
2025-06-02 10:37:37 +01:00
Matt Aitken ff157e51a1 If the run is already PENDING_EXECUTING, log an error and ack but don’t fail the run (#2129) 2025-05-30 11:44:40 +01:00
Matt Aitken a027e6a1bb Set the branch header for the SDK realtime functions (#2128) 2025-05-30 09:27:47 +01:00
Matt Aitken 5fdc7e66af Preview branch fixes (#2126)
* SDK: auto-detect the branch using the VERCEL_GIT_COMMIT_REF env var

* Waitpoint token completion with HTTP is now working
2025-05-29 21:23:11 +01:00
Matt Aitken ea853b243e Remove onsole.log… oops (#2125) 2025-05-29 14:39:36 +01:00
Matt Aitken 12f4653cd6 Fix long preview branch names (#2124)
* Truncate long branch names, don’t let the icon shrink

* Truncate and show tooltip for long branch names
2025-05-29 14:36:24 +01:00
Matt Aitken 47f4726515 Remove log that actually appears to users (#2123) 2025-05-29 13:47:19 +01:00
Matt Aitken 7ecaf2ce53 Sync env vars (parent env vars) (#2120)
* Prefer branch variables, but load preview envs too

* WIP with vercel preview env var syncing

* Return success if either child or parent env vars are synced

* Removed log of env vars

* Remove another log
2025-05-29 12:18:25 +01:00
nicktrn 7b463710f5 Fix metrics format (#2119)
* ensure eof marker is always at the end

* strip empty lines from prisma metrics
2025-05-29 09:52:42 +01:00
nicktrn fda9565eb0 Optionally disable run debug logs (#2116)
* disable run debug logs by default

* lightweight webapp health check

* disable debug logs for dev runs

* disable run debug logs for supervisor client

* add changeset
2025-05-29 09:27:08 +01:00
nicktrn 82e7484da8 Feat: import timings and bundle size analysis (#2114)
* cursor should ignore .env files

* fix for duplicate builds when starting dev

* output metafile in dev

* attach metafile to background worker

* attach import timings to worker manifest

* warn during dev if imports take more than 1s

* add analyze command

* update disable warnings flag message

* add changeset
2025-05-28 15:01:25 +01:00
Eric Allam 0f66023b54 v4: metadata collapsing now preserves operation order for correctness (#2115)
* v4: metadata collapsing now preserves operation order for correctness

* Add changeset
2025-05-28 14:52:06 +01:00
Eric Allam 021d6d8f20 Improve efficiency of cancelling in progress TaskEvent records in v3 and v4 (#2112) 2025-05-28 11:06:19 +01:00
Matt Aitken 068c024477 Preview branches (#2086)
* Initial preview migrations

* Modified the staging endpoint to create preview environments

* Added isBranchableEnvironment to RuntimeEnvironment

* Staging = yellow Preview = orange

* Changed the env sort order

* Set isBranchableEnvironment correctly. Create preview for new projects

* Very basic branch menu

* Creating branches from the dashboard

* Fix for string icons on project delete page

* Don’t show branch API keys

* WIP on the manage branches page

* RuntimeEnvironment added projectId index

* Only create the parentEnvironmentId column if it doesn’t exist already

* Improved the limit wording

* Add search to the branch list

* contains in both places

* Many style improvements

* Branch dropdown and v4 badge

* Arching/unarchive branches working in the dashboard

* Tidied imports

* Change preview slug from `prev` to `preview`

* Use correct color for side menu preview branch icon

* Upsert the branch and use the shortcode as a unique constraint

* Upserting working with nice messages in the dashboard

* Better errors when upserting branches

* Button shortcut, don’t allow event to propagate

* Better duplicate error message

* Filter out archived branches from the env selector

* Archiving/creating tweaked some more

* Add an archived banner to the app, fixes for archived branches and upsells

* Fixed pagination

* Disable editing schedules, pausing queues, testing tasks

* Don’t allow replaying if the env is archived

* When deploying detect the correct environment

* Get the projectClient when there’s a branch

* createGitMeta function, most code from the vercel CLI repo

* Deploy, getting the correct environment client

* Added git column to WorkerDeployment

* Add GitMeta to core schemas

* Create branch when deploying

* WIP on branch support in the API

* Delete old createTaskRunAttempt fn

* apiAuth remove export from internal functions

* Rename env var to “TRIGGER_PREVIEW_BRANCH”

* Add TRIGGER_PREVIEW_BRANCH to resolved env vars for runs

* First preview deploy and run working

* Set the preview branch in the main SDK

* Added git links to the preview branches table

* Better errors when replaying/testing archived branches

* Don’t dequeue archived environments

* Env var resolution with parent environment

* Hello world default machine small-2x to save my memory

* Fix for more env var functions

* Only return non-archived envs

* Switch to controlled state for the checkboxes

* Uncheck everything when PREVIEW is checked

* WIP on branch UI

* Show the preview branch label on the env vars list

* Fix for overriding env vars

* Adding preview branch env vars working

* Progress on new env vars

* Only allow selecting a single branch

* Layout fix when there are errors

* Set the defaultValue so there are some fields

* Conform fix for team invite page

* Archived environments don’t run scheduled tasks

* Added Git data to deployments

* Added git data to the deployment inspector

* Don’t allow upserting schedules when archived

* Deduplicate and blacklist some env vars

* Fix for wrong conform function being used

* Show a better error if all vars were blacklisted

* Added environment variable search (by key and value)

* Improved preview branch icon

* Replay now supports branches

* Schedule page render branches properly

* Show the env icon in bottom-left of the test page

* When editing older schedules (that have multi-env) show preview branches correctly

* Fix for incorrect disallowed branch name character

* Extract and improve the directory verification code

* WIP for CLI preview archive command

* Improved the preview branch action buttons

* Redirect to the project if we don’t find a matching env

* Archiving branch via the CLI working

* Fix for archiving branches

* Public access token test task

* JWTs working are with preview branches

* Add branch and git data to the Run ctx

* Updated GitMeta functions to work in CI

* Added pullRequestState

* Archive when deploying if the PR is closed/merged

* Fix for the changesets guide

* Fix for CLI dev bug introduced

* CLI promote now supports preview branches

* Add PR title. Reordered them and added tooltips

* syncEnvVars working with branches

* Added preview branch support to syncVercelEnvVars()

* Detect the branch from Vercel env var (set during build)

* Allow passing a branch in

* Use process.env.VERCEL_TOKEN as well… this used in Vercel CI

* Temp delete

* Improved regenerate api key modal

* Added Accordion component (with styles)

* Redesigned the API keys page

* Revert "Temp delete"

This reverts commit 177b92cd935a6161456bde65d01294e23ecfd47f.

* Changeset

* Fixed docs link

* The new branch panel closes when a branch is created

* Update apps/webapp/app/services/upsertBranch.server.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Removed findUniques from WorkerGroupTokenService

* Made the parentEnvironmentId migrations safe

* Latest lockfile

* Update packages/cli-v3/src/commands/workers/build.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Move isValidGitBranchName to a separate file

* Move the sanitize fn too

* removeBlacklistedVariables moved to a separate file

* Moved deduplicateVariableArray to a separate file…

* Fix broken sanitizeBranchName import

* Another import fix…

* Improved blacklisted error message

* SImplified migration to use `ADD COLUMN IF NOT EXISTS "parentEnvironmentId" TEXT`

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-28 11:02:56 +01:00
Eric Allam b74c000fb6 Improve metadata flushing efficiency by collapsing operations (fix #2104) (#2106)
* Improve metadata flushing efficiency by collapsing operations (fix #2104)

* Add changeset

* A couple of style tweaks
2025-05-27 14:08:15 +01:00
Eric Allam 0410316e14 Enable prisma metrics and add them to the /metrics endpoint (#2111)
* Enable prisma metrics and add them to the /metrics endpoint

* Add support for bearer token auth on metrics endpoint
2025-05-27 13:28:45 +01:00
Eric Allam c571c5ef32 v4: Can now set project ref using the TRIGGER_PROJECT_REF env var (#2109)
* v4: Can now set project ref using the TRIGGER_PROJECT_REF env var

* Add changeset

* Improve the name of the resolvEnvVars function and move setting NODE_ENV into the dev supervisor

* Removed moved function
2025-05-27 12:51:04 +01:00
Eric Allam e84ede9599 Upgrade to electric@1.0.13 (#2089)
* Upgrade to electric@1.0.13

* Add ELECTRIC_INSECURE env var to test container
2025-05-23 16:59:53 +01:00
Eric Allam 3fc5630be7 fix: calculating next schedule should always just be from now (#2102) 2025-05-23 16:55:52 +01:00
Eric Allam 13d378f6df v4: batches can only complete when all their runs have been created (#2101)
* v4: batches can only complete when all their runs have been created

* Fix flaky runs replication test
2025-05-23 16:53:50 +01:00
Eric Allam 1a6a9ca97a perf fix: calculateNextScheduledTimestamp speed improvement (#2099)
* Simulate "next steps" when calculating the next timestamp to prevent millions of iterations on dev schedues

* Add additional tests

* Fix typescript issue
2025-05-23 16:24:33 +01:00
Dan 6cb57b803c Added openai agent sdk guardrails docs (#2100) 2025-05-23 16:09:12 +01:00
nicktrn 7c791dd519 Improve unit test workflow performance (#2096)
* shard unit tests

* temp enable for all pushes

* fix test workflow

* update to latest vitest and only add to root package.json

* additionally use default reporter

* gather reports before uploading

* split up slow replication tests

* split up unit tests workflow

* move workflows to parent dir

* use new paths in parent workflow

* prevent artifact clashes

* we always need to create the reports dir

* speed up merge reports

* gather reports even when tests fail

* fix artifact patterns

* increase shards

* disable push trigger again

* improve dequeue snapshot test reliability
2025-05-23 12:22:14 +01:00
github-actions[bot] cf3735976e Release 4.0.0-v4-beta.18 (#2095)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.18

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-05-22 21:59:11 +01:00
nicktrn a9a40b5184 Fix dev indexing (#2094)
* save build manifest for dev runs again

* add changeset
2025-05-22 21:54:00 +01:00
github-actions[bot] 0f928e224b Release 4.0.0-v4-beta.17 (#2071)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.17

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-05-22 19:51:18 +01:00
Eric Allam a306545952 Always find debug logs when in the span sidebar (#2093) 2025-05-22 14:22:12 +01:00
Eric Allam 1bb1fc4c8f v4 run engine debug logs now filtered out in SQL by default (#2092) 2025-05-22 10:44:06 +01:00
nicktrn 1ca37f5745 Expose esbuild keepNames and minify options (experimental) (#2091)
* Expose esbuild `keepNames` option (experimental)

* Expose esbuild `minify` option (experimental)

* test new experimental options
2025-05-22 10:42:48 +01:00
nicktrn 8112c12092 Improve email whitelisting and extend to GitHub auth (#2090)
* make github auth respect email whitelist

* display github login errors

* fix smtp secure env vars

* prevent sending magic link if email not allowed

* fix MARQS_DISABLE_REBALANCING and RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS
2025-05-22 10:30:00 +01:00
nicktrn c8d252e300 Add experimental externals detection (#2083)
* add auto detect externals

* stop spinner on esbuild errors

* test problematic packages

* fix braces types

* improve detection 10x

* ignore sentry stub

* improve main package json detection

* rename to experimental_autoDetectExternal
2025-05-21 16:40:37 +01:00
nicktrn d89f740d18 Write esbuild metafile to output dir (#2087)
* write esbuild metafile to output dir

* add changeset
2025-05-21 16:17:10 +01:00
Eric Allam 41a3bdec68 Add id-token permission to the publish webapp workflow (#2088) 2025-05-21 14:30:03 +01:00
Eric Allam a9a937dd25 Implement example of streamText fallback using an AI data stream (#2068) 2025-05-21 13:22:06 +01:00
Eric Allam 14e081e17d release concurrency system reliability improvements (#2081)
* Refill release concurrency tokens when a run is cancelled

* Improved release concurrency accounting system + a sweeper to auto-refill tokens for snapshots that are no longer the latest snapshot on a run (e.g. the run has moved to a new snapshot state)

* Fix order of arguments to the releasings sweeper

* Add a heartbeat for SUSPENDED snapshots, where when stalled will attempt to continue the run if unblocked
2025-05-21 13:21:17 +01:00
Eric Allam 500a173d67 Set default values on the invite and new env var forms to fix an issue after updating conform (#2084) 2025-05-21 13:20:16 +01:00
Eric Allam cc562c58fa Remove unused publish job from the release github workflow to fix permissions error (#2085) 2025-05-21 13:19:49 +01:00
Eric Allam 0334df70ad Prevent socket hangup error by removing the aggressive clickhouse keep alive ttl env var default (#2082) 2025-05-21 13:15:15 +01:00
Dan e08e493b3c Potential fix for code scanning alert no. 2: Workflow does not contain permissions (#2078)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-05-19 21:17:35 +01:00
Dan f667d61d4c Potential fix for code scanning alert no. 3: Workflow does not contain permissions (#2077)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-05-19 21:16:58 +01:00
Dan 325ef04c7f Potential fix for code scanning alert no. 6: Workflow does not contain permissions (#2076)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-05-19 21:15:42 +01:00
Dan 79f0a6a662 Potential fix for code scanning alert no. 42: Workflow does not contain permissions (#2074)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-05-19 21:14:26 +01:00
Dan ad5027b5fd Potential fix for code scanning alert no. 1: Workflow does not contain permissions (#2079)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-05-19 20:59:50 +01:00
Dan f0809e6ad6 Potential fix for code scanning alert no. 38: Workflow does not contain permissions (#2075)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-05-19 20:48:58 +01:00
Eric Allam c63559f55d v4: fix race condition when continuing run when blocked at the same time (#2073)
* v4: fix race condition when continuing run when blocked at the same time

* Attempt to fix flaky e2e test

* Make waitpoint race condition test less flaky
2025-05-19 16:50:36 +01:00
Eric Allam 8471169b30 Fix various dependabot alerts (#2063)
* Upgrade posthog-node to clear axios vulns

* Upgrade @slack/web-api to use a secure version of axios

* Update parse-duration to fix security vulns

* Mitigate against the ws DoS vuln

* Upgrade body-parser to 1.20.3 in the webapp

* Upgrade react-use to 17.5.1 to remove the fast-loops transitive dep

* Remove unused babel dev deps and config file that's no longer used

* Upgrade prismjs to 1.30.0 and bundle parse-duration now

* upgrade express to 4.20.0 to fix issue with XSS when redirecting

* Upgrade @conform/zod to 0.9.2
2025-05-19 12:01:51 +01:00
nicktrn 558a39c644 Remove supervisor docker binary requirement and other tweaks (#2062)
* don't require docker binary

* more structured logs

* remove docker type dep

* add core changeset

* use implicit DOCKER_HOST instead

* disable resource monitor for now

* support attaching docker runners to multiple networks

* warn if dequeue interval > idle dequeue interval

* verbose logs

* add changeset
2025-05-17 12:33:41 +01:00
Eric Allam b4ab06600b Create AGENTS.md file (#2070) 2025-05-17 07:54:04 +01:00
Eric Allam c8e8bc9306 engine v2: Fix selectTopEnvs missing queues (#2069) 2025-05-17 07:53:48 +01:00
Eric Allam a69621bdcc Allow self-hosted deploys locally by pointing to docker.host.internal on macOS (#2064) 2025-05-16 15:20:48 +01:00
Eric Allam ad4daa3301 Fix run replication payloads/outputs trying to send invalid types like BigInt to clickhouse (#2065) 2025-05-16 11:02:30 +01:00
Eric Allam bb7173b4ab Improve run replication logging (#2060)
* Remove the tcp buffer monitors

* Improve run replication logging
2025-05-15 14:12:01 +01:00
Eric Allam 2aee3debc0 Docs: 3rd party log exporters (#2058) 2025-05-15 10:50:15 +01:00
Eric Allam d1e40643fc Allow creating and monitoring run replication services with different settings (#2055)
* Allow creating and monitoring run replication services with different settings

* Fix test
2025-05-14 20:40:47 +01:00
Eric Allam a8e38d9231 chore: Skip unit tests dependency when running publish image workflow (#2054) 2025-05-14 19:31:50 +01:00
github-actions[bot] 2ae0e22d81 Release 4.0.0-v4-beta.16 (#2053)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.16

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-05-14 16:26:28 +01:00
nicktrn 206a2c3245 Fix init.ts detection for sentry plugin users (#2051)
* fix init.ts detection when using the sentry plugin

* add changeset
2025-05-14 16:20:05 +01:00
Eric Allam eb3929880f runs replication leader lock expiration fix (#2050)
* runs replication leader lock expiration fix

* Allow configuring the container image --max-old-space-size using NODE_MAX_OLD_SPACE_SIZE

* Ability to configure the clickhouse keep alive settings

* Add some logging because we might not be able to do telemetry
2025-05-14 15:49:48 +01:00
github-actions[bot] 0661ee5fdd Release 4.0.0-v4-beta.15 (#2040)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.15

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-05-14 12:33:37 +01:00
Eric Allam 2deef00d48 Improvements to the runs replication service to prevent buffer exhaustion (#2047) 2025-05-14 11:35:51 +01:00
nicktrn fa5360a99d Feat: supervisor ARM builds (#2046)
* v4 worker multi-arch builds

* v4-beta tag for webapp

* add oidc permission

* deps fetcher needs python

* node-gyp needs complete toolchain

* fix headers

* bust pnpm cache

* add setuptools

* switch back to old cache id

* push worker images

* pass the correct tags
2025-05-14 11:23:41 +01:00
Eric Allam 7ace1c3e25 Fix run replication telemetry by passing in a tracer (#2045) 2025-05-14 09:52:09 +01:00
Eric Allam a815633712 Add external log exporters and fix missing external trace exporters in deployed tasks (#2038)
* Add external log exporters and fix missing external trace exporters in deployed tasks

* Generate the external traceID correctly and exporter 3rd party logs with the external traceID as well
2025-05-13 16:54:24 +01:00
Eric Allam 84a0336bdd Fix #1955: schemaTask parse payload failures handled gracefully (#2043) 2025-05-13 16:47:21 +01:00
Eric Allam 7be07b4d0e allow configuring the replication leader lock settings (#2042) 2025-05-13 14:17:36 +01:00
nicktrn bb96e848de Fix init.ts import for deployed workers (#2041)
* import init file in managed worker

* refactor for easier diff

* managed worker: attach configured incorrectly error details

* add changeset

* disable pre

* we also didn't rewrite the output path

* Revert "disable pre"

This reverts commit 89afe44f6c42d4bd7b5a51f7fda1caec6cab8a24.
2025-05-13 13:27:49 +01:00
Rudra Sankha Sinhamahapatra 39f03085b4 feat: ffmpeg v7 (#1777)
* adding ffmpeg v7

* Renaming id ffmpeg to ffmpeg7

* added space

* fixing formatting issue

* fix unit test workflow for forks

* merge ffmpeg v7 into existing extension

* verify md5 checksum

* fix dns resolution

* fix v7 install

* remove redundant code

* add changeset

* mark fluent-ffmpeg as external

* add docs

* Revert "mark fluent-ffmpeg as external"

This reverts commit 74cd317ae69c7453a22d3fa412ff00b402840b87.

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-05-13 12:27:35 +01:00
Mendy Landa 21c0dc619e feat: playwright extension (#1764)
* feat: playwright build extension

* feat: add playwright task

* full dep list & added some comments

* fix v3-catalog

* log image size for local builds

* detect playwright version

* add changesets

* add docs

* additional checks

* fix unit test workflow for forks

* use middleware and update docs

* update jsdoc

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-05-13 12:22:01 +01:00
James Ritchie da0ef73724 Docs: Removes an issue listed in ‘known issues’ in the v4 migration guide (#2039) 2025-05-13 10:14:27 +01:00
Eric Allam 65da20c225 feat: replicate task runs to clickhouse to power dashboard improvements (#2035)
* WIP clickhouse package with test containers setup

* More clickhouse client setup now with otel and real tests, and the v1 of raw run events

* Add some additional columns to raw_run_events_v1

* WIP runs dashboard service

* Create a new run engine event bus event for the runs dashboard to hook into

* Track run events in the run engine

* make sure engine v1 runs get synced to CH

* Update the attemptNumber of v3 task runs

* Restructure the run events to be more sparse

* emit more stuff

* Setup replication package

* scaffold the replication package

* replication wip

* resolve conflicts

* more replication stuff

* Add ability to drop the replication slot completely on teardown

* Use the new single replacingmergetree task events table for replication

* get it working

* insert payloads into their own table only on insert and then join

* prepare for using clickhouse cloud and now running ch migrations during boot in the entrypoint.sh

* Handover WIP and tests

* Testing the replication service

* Remove the runs dashboard stuff that we aren't using anymore

* Added a test for large payloads

* hacky typecheck fix

* Fix new internal package typecheck issues and start adding telemetry to the replication service

* tracing over spans, some other improvements

* Improvements to the runs replication service, now ready for testing

* Some fixes and cleanups

* Don't need this code anymore

* move transaction types into the runs replication service

* only send spans where there are transaction events

* A couple of suggested tweaks
2025-05-12 22:12:36 +01:00
James Ritchie ccc9764383 Active incident status panel in the side menu (#2033)
* WIP adding a side menu panel to display incident statuses

* Fixes re-rendering bug and copy tweak

* cache the betterstack response using unkey

* Style the button to fit the panel colors
2025-05-09 15:52:17 +01:00
Matt Aitken 584722d48b [v4] Complete a waitpoint and then get affected runs (#2034)
* Complete waitpoint before getting affected runs. Added more logging

* Added another error log
2025-05-09 15:46:41 +01:00
nicktrn 1b1ad16d94 Improve worker management admin route (#2032)
* improve route to add worker group, handles existing groups gracefully

* add option to remove default worker group from project

* separate project id field

* update supervisor readme with route changes
2025-05-09 10:43:31 +01:00
Matt Aitken 37aadfc80b We can use TaskRun error instead of attempt. Faster and works across v3/v4 (#2031) 2025-05-09 09:36:40 +01:00
github-actions[bot] 6736cb5ae9 Release 4.0.0-v4-beta.14 (#2029)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.14

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-05-08 11:12:25 +01:00
Matt Aitken 87630bc64f Fix for TS issue with old DB type (#2030) 2025-05-07 20:51:36 +01:00
Matt Aitken d23fa38a0a Waitpoint token callback URLs (#2025)
* Initial commit with a plan for what we’re going to do

* Some initial types and improved plan

* Add Waitpoint resolver

* Add resolver + status index

* Remove type + status index

* Only drop if exists

* Remove type index

* Update waitpoint list presenter to use resolver

* Added resolver to the engine

* Made the existing waitpoint list presenter more flexible

* Initial implentation ofr wait.forHttpCallback()

* Added the callback endpoint (no API rate limit)

* schema version

* Added jsdocs, removed schema version because of errors

* Show callback URL if it’s set

* Dashboard pages and panels

* Remove todos

* Added temporary icon

* Added a blank state

* Some tweaks and added a Replicate example

* Implement unwrap() for httpCallback

* Added unwrap to wait.forToken() as well

* Improved jsdocs

* Added docs

* Added unwrap to the token docs

* Show a dash if there are no tags

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Make the timeout error safer

* Fixed migrations… should use id desc not createdAt desc

* Fixed page title

* Fixed migration so it only adds them if they don’t exist. This allows us to manuall run in cloud first

* Respect the max content length by getting the length of the body

* Added more docs details about the callback format

* Remove code comment

* Improved the error

* Added a hash to the HTTP callback URLs

* Add the apiKey to the API input type to fix TS error

* Return the error responses. They were being caught and not preserved

* The content-length header is required. Deal with an empty body

* Removed unused types

* Added some new span icons

* Reworked http callback to be a create call then just use wait.forToken()

* Added a changeset

* Updated the docs

* Updated the wait overview docs

* Simplify to just a call

* WIP stripping right back to waitpoints just having a URL associated with them…

* More deletions

* Remove missing icon

* Updated the changeset

* Add URL to the token return types

* Remove wait for http callback page

* Updated docs

* More tidying

* Type and import fix

* Remove unused import

* Some type fixes for the retrieve

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-07 19:55:29 +01:00
James Ritchie 4ec0395424 Fixes a console error introduced when updating the loading animation (#2019) 2025-05-07 18:40:04 +01:00
James Ritchie e94558a0d5 Stop indexing the test-cloud domain (#2026) 2025-05-07 18:39:38 +01:00
James Ritchie 6f4af89adb Docs: Adds Yarn ‘Plug’n’Play” conflicts fix to troubleshooting page (#2028) 2025-05-07 18:38:40 +01:00
James Ritchie 3e45f785a0 Dashboard: Org menu improvements for admins (#1969)
* Adds path for admin route

* Updates impersonation banner and show new org button if only 1 org

* Simpler UI for displaying admin access
2025-05-07 18:38:00 +01:00
James Ritchie 04b9973f20 Shows the tooltip date time on DateTImeAccurate component (#2021) 2025-05-07 18:35:48 +01:00
github-actions[bot] 8da3043111 Release 4.0.0-v4-beta.13 (#2027)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.13

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-05-07 15:10:50 +01:00
nicktrn 2b586c87a0 Fix controller waitpoint resolution, suspendable state, and snapshot race conditions (#2006)
* remove dead code

* rename managed to shared runtime manager

* rename to resolve waitpoint for clarity

* add resolver id helper

* store and correctly resolve waipoints that come in early

* fix ipc message type change

* branded type for resolver ids

* add fixme comments

* remove more unused ipc schemas

* fix entitlement validation when client doesn't exist

* restore hello world reference workspace imports

* runtime manager debug logs

* prefix engine run logs

* managed run logger accepts nested props

* runtime suspendable state and improved logs

* require suspendable state for checkpoints, fix snapshot processing queue

* add terminal link as cli module so we can more easily patch it

* apply cursor patch

* add license info

* remove terminal-link package and add deprecation notice

* remove old patch

* remove terminal-link from sdk

* rename snapshot module

* add cli test tsconfig

* add run logger base type

* add snapshot manager tests

* fix cli builds

* improve QUEUED_EXECUTING test

* changeset

* make testcontainers wait until container has stopped

* require unit tests for publishing again

* avoid mutation during iteration when resolving pending waitpoints

* improve debug logs and make them less noisy

* always update poller snapshot id for accurate logs

* detach task run process handlers

* check for env overrides in a few more places and add verbose logs

* log when poller is still executing when we stop it

* add supervisor to publish workflow

* always print full deploy logs in CI

* Revert "avoid mutation during iteration when resolving pending waitpoints"

This reverts commit 87b0ce1e5b.

* disable pre

* print prerelease script errors

* Revert "disable pre"

This reverts commit 9403409637.

* misc fixes

* better debug logs

* add snapshots since methods and route

* prep for snapshots since

* improve deprecated execution detection

* update supervisor and schema

* properly log http server errors

* detect restore after failed snapshot fetch

* run and snapshot id can be overridden

* fix restore detection

* fix deprecation checks, move into snapshot manager

* less logs

* rename snapshot manager stop

* restore detection was moved into snapshot manager

* fix notifier logs

* make runtime manager status a debug log

* no need to attach runtime status twice

* findUnique -> findFirst

* sort snapshots by created at everywhere
2025-05-07 13:02:32 +01:00
nicktrn 053389d9b3 Fix testcontainers cleanup (#2023)
* update testcontainers packages

* increase cleanup timeout and add better logs

* small tweaks

* decrease docker network size so we can have more of them

* add a test flow to check this all works

* await all engine.quit calls

* add docker diagnostics

* reduce number of test runs

* improve network attachment output

* add setup logs

* log redis setup errors

* add cleanup log type

* stop redis container if setup fails

* disable ipv6

* tidy things up a bit

* fix unit tests workflow

* decrease container cleanup timeout

* fix types

* fix webapp typecheck
2025-05-06 08:56:34 +01:00
Eric Allam 43a4139424 docs: onCancel (#2024)
* docs: onCancel

* Add availability note
2025-05-03 21:40:03 +01:00
github-actions[bot] 7b509849e9 chore: Update version for release (v4-beta) (#1996)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.12

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eric Allam <eallam@icloud.com>
2025-05-03 21:26:12 +01:00
Eric Allam 13f750a18b v4: implement onCancel callbacks (#2022)
* v4: implement onCancel callbacks

* chat example

* Add changeset

* A few improvements
2025-05-03 20:50:11 +01:00
nicktrn 15816e9955 Fix long retry delays (#2020)
* make testcontainers wait until container has stopped

* require unit tests for publishing again

* add failing test case

* make it pass

* add retry threshold ms env var and use it
2025-05-02 16:57:21 +01:00
nicktrn a10e2a8b16 Small fixes in prep for v4 self-hosting (#2018)
* fix entitlement validation when client doesn't exist

* add supervisor to publish workflow

* fix v4 publish workflow name
2025-05-02 14:33:20 +01:00
Matt Aitken 423209661b Give helpful hint if you pass a bad directory when deploying (#2013) 2025-05-02 10:54:26 +01:00
James Ritchie 73f363f9d5 Changes the spinner to white now we have purple primary buttons (#2014) 2025-05-02 10:50:22 +01:00
James Ritchie 4456184f37 Fix task and schedule icons squishing (#2017) 2025-05-02 10:47:57 +01:00
James Ritchie 2448242be3 Small improvement to the loading animation so it moves left to right only (#2012) 2025-05-01 19:24:03 +01:00
James Ritchie 9c3aeb3efb Adds friendly formatting for max duration values (#2011) 2025-05-01 19:22:56 +01:00
James Ritchie df0bce3a8f Code blocks have an optional text-wrap toggle (#2009)
* Code blocks have an optional text-wrap button

* Wrap “words”, not “all”

* wrapping is default false

* Change the wording in the tooltip
2025-05-01 17:44:17 +01:00
James Ritchie 0fe85eaec4 Highlight queues when concurrency limit reached and new table col (#2008)
* Combines the Running and Concurrency limit cols into 1

* Display a badge when a queue is at the concurrency limit

* Colors the Running/Limit column text amber if the concurrency limit is hit

* Turns the “Running” big number amber and shows “At concurrency limit” text

* BigNumber now handles big values using formatNumber and formatNumberCompact

Also includes some responsive improvements to make sure things wrap when it gets tight

* Adds a new col for showing how the queue is limited

* Reinstates a threshold for making very big numbers compact

* Added border to make the search bar not float
2025-05-01 17:40:54 +01:00
Matt Aitken d2dde0a844 formatDateTimeISO() fix (#2010)
* Fix for the DateTime ISO function

* Fix implementation and added unit tests

* Preserve milliseconds properly
2025-05-01 17:35:55 +01:00
Dan aacec569d0 Added Python MarkItDown docs (#1977)
* Added Python MarkItDown docs

* Simplified the task

* Update example to use fetch and improved sidebar titles

* Fixed link
2025-05-01 14:18:12 +01:00
nicktrn 0df7af4403 Add idle dequeue interval to supervisor (#2007)
* supervisor uses new idle interval after empty dequeues and errors

* make supervisor heartbeat interval configurable
2025-05-01 10:53:19 +01:00
James Ritchie 553a03580f Show a tooltip when hovering over any DateTime (#2003)
* Adds a tooltip to display the UTC, timezone and local time for DateTime

* Made it safer for SSR

* Makes the copy function into a hook and separate button + adds copy dateTime button to the date tooltip

* Adds an extra-small size copy button

* Show the offset (UTC +1) next to your local time

* Optionally don’t show the date tooltip. Defaults to true (always show)

* Tidy imports

* Refactor the tooltip content to remove duplicated markup

* Coderabbit suggestion to include support for minutes for some timezones like Nepal
2025-05-01 10:00:42 +01:00
Matt Aitken af019473cd Get data for the last day of the month for usage graphs (#1999) 2025-05-01 09:53:06 +01:00
Matt Aitken cb47200d34 Added search to the Queues page (#2000)
* Added search to the queues page

* Fix for empty state copy
2025-05-01 09:33:03 +01:00
James Ritchie 98fd8045e9 Tooltip to explain dev schedules not counting towards your limit (#2004) 2025-05-01 09:32:13 +01:00
James Ritchie 44184a61c7 Updates the secret key text to be more explicit what you have to do to start triggering tasks (#2005) 2025-05-01 09:31:37 +01:00
nicktrn c0807ad0d0 Display terminal links in cursor (#1998)
* add terminal link as cli module so we can more easily patch it

* apply cursor patch

* add license info

* remove terminal-link package and add deprecation notice

* remove old patch

* remove terminal-link from sdk

* changeset
2025-05-01 08:53:11 +01:00
James Ritchie cf9460afee Post new sign up reasons to slack, not plain (#1995)
* Post new sign up reasons to slack, not plain

* Makes the Slack env vars optional

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-04-30 14:02:02 +01:00
Matt Aitken 7fe111abe9 AI dashboard widget + CLI AI generate fix suggestions (#1925)
* Made a better AI icon and improved how it looks on the create new schedules inspector

* WIP adding kapa ai to the app

* WIP adding a new Ask AI button to the side menu

* WIP using the react example from the docs

* Align the AI button in the bottom bar

* Kapa widget now works

* Trigger the Kapa modal from the custom button

* Fix imports

* Adds Ask AI shortcut to Shortcuts panel

* Adds a new enter shortcut key

* Adds a prop so you can optionally hide the shortcut key

* Latest

* Moved the Kapa/Help stuff into a component, out of root

* WIP using onModalClose

* Creates a wrapper provider to block shortcuts while kapa modal is open (has bugs)

* Fixes button alignment

* Hide the shortcut key at the button layer

* Fix for enable/disable shortcut keys globally

* Kapa is working

* You can bring up the shortcut keys without opening the help panel

* TODO remove listeners

* remove imports and fix invalid tailwind class

* style kapa widget as best i can

* Remove Kapa event listeners

* Allow passing in a query

* Open the AI widget if there’s a URL param

* Much cleaner implementation for Kapa

* Trying to auto-open the Kapa widget when the page loads

* Delay opening the widget because it was causing issues

* Improved Kapa widget colors

* Added an AI help link to the CLI

* We don’t need this anymore

* Exit with 1, indicating an error. This is important for CI

* Removed old auto-opening code

* Added some code comments to explain some of the annoying stuff

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-04-30 11:11:42 +01:00
github-actions[bot] 525285da7f chore: Update version for release (v4-beta) (#1994)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.11

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-04-29 16:48:57 +01:00
Eric Allam 07365cd405 realtime: fix streams missing chunks when streams are longer than 5 minutes and receive 408 Request Timeout errors. Also now support multiple client streams being sent to a single stream key (#1993)
* realtime: fix streams missing chunks when streams are longer than 5 minutes and receive 408 Request Timeout errors. Also now support multiple client streams being sent to a single stream key

* Safely release the reader lock
2025-04-29 12:42:38 +01:00
Matt Aitken 83d0e8710d Fix for Schedules list page slow loading (#1992)
* Fix for Schedules list page slow loading

Getting BackgroundWorkerTask was very slow (Prisma was getting every single one…)

* Same fix for the upserting of schedules in the dashboard
2025-04-29 12:40:56 +01:00
Matt Aitken 280a6d0297 V4 dequeue test fixes (#1991)
* Fix priority tests by dequeuing in sequence

* Expect this test to fail until we support parallel dequeuing of an env with streaming

* Fix one and expect fail from the other

* Fix for batch trigger test dequeuing

* Fixed pending version test
2025-04-29 12:40:22 +01:00
Matt Aitken f579afb4cc V4 dequeue performance (return faster) (#1989)
* Loop through tenants only once, return as soon as we have enough.

* Hello world deadlock task set back to triggerAndWait
2025-04-28 14:49:38 +01:00
James Ritchie 3d744c6ecc Mergent migration guide (#1988)
* Adds a migration guide for converting mergent tasks to trigger.dev

* Adds link to migration guide in the docs side bar
2025-04-28 14:18:09 +01:00
github-actions[bot] 5691480dac Release 4.0.0-v4-beta.10 (#1984)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.10

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-04-28 13:34:34 +01:00
nicktrn 09a859d056 Fix managed run controller edge cases (#1987)
* parse disconnect description

* decrease snapshot poller logs

* remove another useless log

* attempt number change detection

* ensure no executions or child processes are being reused

* prevent snapshot change handler from running after execution stopped

* prevent creating zombie intervals on reset

* add changeset

* add enabled poller check before getting latest snapshot

* make attempt number update more explicit, improve logs

* remove deprecated heartbeat service

* ..also remove the import
2025-04-28 13:22:30 +01:00
Saadi Myftija 7bdbbdc433 Handle rate limit errors from Slack on new alert creation (#1986) 2025-04-28 11:07:59 +01:00
Matt Aitken 8b0e4f2367 Fix for completing batches (when there are idempotent runs) (#1985) 2025-04-25 18:41:20 +01:00
Eric Allam e837500486 feat: v4 deadlock detection (#1970)
* Locked task runs will now require queues and tasks to be in the locked version

* Client errors caught in a run function now will skip retrying

* Extracted out the trigger queues logic

* extract validation, idempotency keys, payloads to concerns

* Extracted out a bunch of more stuff and getting trigger tests to work

* Add queue and locked version tests

* Deadlock detection WIP

* more deadlock detection

* Only detect deadlocks when the parent run is waiting on the child run

* Improve the error experience around deadlocks

* A couple tweaks to make CodeRabbit happy and fixing the tests in CI

* Fixed failing test

* Changeset

* wip

* Make sure to scope queries to the runtime env
2025-04-25 12:56:54 +01:00
github-actions[bot] 60fcf5fdac Release 4.0.0-v4-beta.9 (#1980)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.9

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-04-25 12:43:32 +01:00
Eric Allam 3a62ee36d9 v3: prevent envs with excess queues from degrading dequeue performance (#1982)
* v3: WIP maximum queues per env implementation

* Add a queue distribution test

* Add edge case tests

* Set the marqs maximumQueuePerEnvCount
2025-04-24 18:02:49 +01:00
Matt Aitken bb606a1fcd Don’t allowed canceled delayed runs to be put into the queue (#1981) 2025-04-24 15:10:47 +01:00
Matt Aitken e9c6cc71f0 New batch id length is longer (#1944) 2025-04-24 13:38:15 +01:00
Eric Allam 30f93521c8 fix: default machine config indexing now works (#1979) 2025-04-24 13:38:03 +01:00
Matt Aitken 18f77660e0 Runlock telemetry (#1974)
* Added error throwing tests to runlock

* Added some more run lock tests

* Added customConditions so the test can use the Logger

* Fix for JSON error

* Added a span to the run lock with a name
2025-04-24 13:37:33 +01:00
Matt Aitken aa1027969b Increase v4 visibility timeouts & don't try continue finished runs (#1978)
* Increase v4 visibility timeouts

* Added some logging for #continueRunIfUnblocked: run has no checkpoint

* If a run is finished or pending cancel, don’t try and continue it
2025-04-24 13:36:18 +01:00
James Ritchie 32ff569844 Fixes bug on Safari preventing Queues and Schedules tables from scrolling (#1973) 2025-04-24 11:40:47 +01:00
James Ritchie dfca04aaea Docs: Updates the run function params syntax + adds new run.list() example (#1976) 2025-04-24 11:31:22 +01:00
Matt Aitken 672a6b8be7 RedisWorker clean up orphaned items when dequeuing (#1975)
* Remove setting the invisibility timeout because it’s already done in the dequeue Lua script

* Remove orphaned queue items when dequeuing

* Added an ack to the visibility timeout test
2025-04-24 11:15:42 +01:00
github-actions[bot] 56ec0523b5 Release 4.0.0-v4-beta.8 (#1972)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.8

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-04-23 19:30:49 +01:00
nicktrn 73214d760e Prevent large outputs from overwriting each other (#1971)
* large outputs now get unique keys again

* replace other attempt id occurences

* add changeset
2025-04-23 19:21:21 +01:00
James Ritchie 853a7ef7e4 Updates button copy and layout tweaks (#1941) 2025-04-23 16:33:46 +01:00
HUORT Louis 0726620525 Switch to docker compose v2 (#1692)
* refactor: docker compose migration

* fix compose download link

* set static name for electric container

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-04-23 14:29:26 +01:00
github-actions[bot] cfb77159d7 Release 4.0.0-v4-beta.7 (#1954)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.7

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-04-23 13:34:26 +01:00
Saadi Myftija 2dbc085bb3 Fix alert creation bug related to the Slack integration (#1967)
* Fix alert creation bug related to the Slack integration

* Handle token_expired error from slack gracefully
2025-04-23 11:11:17 +01:00
Saadi Myftija 26aad58abd Add fuzzy matching to the tasks filter (#1960)
* Add a custom react hook to enable fuzzy searching in item lists

* Use fuzzy filtering in the tasks view list

* Use fuzzy filtering in the test tasks list

* Remove the old tasks filtering react hook
2025-04-22 16:34:56 +01:00
Saadi Myftija b62c17bec1 Show more recent payloads in the test task page (#1961) 2025-04-22 16:24:15 +01:00
Saadi Myftija fab565256e Allow empty payloads in test runs (#1962)
* Allow empty payloads in test runs

* Expose a basic linter configs in the code editor component

* Touch up the linting error elements in the code editor component
2025-04-22 16:23:52 +01:00
nicktrn cedd932f99 Fix queued snapshot status handler (#1963)
* handle queued status change gracefully

* add resource monitor to references

* add resource monitor example

* improve runtime manager debug logs

* fix resource monitor example

* add changeset
2025-04-22 15:32:16 +01:00
Matt Aitken e379fc15be Fix redis worker debounce (#1966)
* Added some Redis worker debounce tests (one failing that reproduces a bug)

* Added some tests for acking

* Added a deduplicationKey to prevent acking when items are queued

* The worker passes the deduplicationKey back in for acking

* Improved logs and removed events from test
2025-04-22 15:24:39 +01:00
Dan c0172d428d added cursor rules docs page (#1965) 2025-04-22 15:23:47 +01:00
Dmitry Sabanin 2c19693cfc Fixes runLimiter check on #dequeueRuns (#1953)
* Fixes runLimiter check on #dequeueRuns

This was causing issues in the dev environment with logs being spammed with `[DevSupervisor] dequeueRuns. Run limit reached, trying again later` and making the worker unresponsive and lose connection.

* Create polite-impalas-care.md

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
2025-04-19 21:45:27 +01:00
github-actions[bot] 64ca9dc9a8 Release 4.0.0-v4-beta.6 (#1951)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.6

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2025-04-18 16:50:22 +01:00
Eric Allam 39e535772f fix: realtime streams from tasks now retry if they receive a 408 timeout error from the realtime/trigger.dev server (#1952) 2025-04-18 16:20:13 +01:00
Eric Allam 006951aa53 Set a maximum number of times the dequeue loop can run (#1950) 2025-04-18 13:36:52 +01:00
nicktrn 0ebecbe574 Multiple queue consumers and dev engine URL changesets (#1949)
* add engine url changeset

* add queue consumer changeset
2025-04-18 11:36:29 +01:00
nicktrn aa74260646 Dev command engine URL now configurable via the webapp (#1948)
* return dev engine url from config call

* set and use engine url from dev config reply
2025-04-18 11:11:11 +01:00
nicktrn f683fe8d2d Multiple queue consumers per supervisor (#1947)
* multiple queue consumer in the same supervisor instance

* disable pre

* Revert "disable pre"

This reverts commit 4b154398982758d09667531e3a64761da83664eb.
2025-04-18 11:07:49 +01:00
Eric Allam 863ecf4830 fix: prevent unbounded looping while dequeueing by exiting early when no queues have messages to dequeue (#1946) 2025-04-18 09:41:09 +01:00
Matt Aitken bbf397e661 Completed batch waitpoints when we completed the BatchTaskRun (#1945)
* Completed batch waitpoints when we completed the BatchTaskRun

* Try complete the batch faster now it’s being used operationally

* Fix for tests that were using the old engine.unblockRunForCreatedBatch() function
2025-04-17 21:01:58 +01:00
github-actions[bot] 4a8303212c Release 4.0.0-v4-beta.5 (#1943)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.5

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-04-17 17:25:07 +01:00
github-actions[bot] 59eea654ac Release 4.0.0-v4-beta.4 (#1939)
* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.4

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2025-04-17 17:05:52 +01:00
Matt Aitken 0513bfadfb Changeset: The envvars.list() and retrieve() functions receive isSecret for each value. Secret values are always redacted. (#1942) 2025-04-17 16:55:51 +01:00
Matt Aitken a7e326c3f3 Remove concurrently, can’t have two in a single file and they’ve already been applied (#1940) 2025-04-17 15:30:37 +01:00
James Ritchie ad088456d4 Changes button spinner to white and Replay Run button changes to secondary variant (#1912)
* Uses the white variant of the spinner instead of dark on the replay run button

* Changes the replay run button on the run page to secondary
2025-04-17 15:09:29 +01:00
Dan e61466c3a5 Added meme generator example (#1929) 2025-04-17 15:08:47 +01:00
Eric Allam 4b42f778ce fix: release concurrency token exhaustion no longer happens on batchTriggerAndWait (#1937) 2025-04-17 15:07:06 +01:00
James Ritchie 2aacf76444 Adds a new entry in the help and feedback modal to request more feedback (#1936) 2025-04-17 15:06:41 +01:00
Matt Aitken 5597291380 Schedules list performance (#1938)
* Order the schedules: most recent first

* TaskSchedule dashboard speed indexes
2025-04-17 15:05:50 +01:00
Matt Aitken 5c9610b807 The mini task page graphs were missing Timed out and Expired (#1915) 2025-04-17 15:05:37 +01:00
Himanshu Sharma e0fd06490a maintain proper context in metadata.root and parent getters (#1917)
* fix(TRI-5140): maintain proper context in metadata.root and parent getters

* Create two-tigers-dream.md

---------

Co-authored-by: Eric Allam <eallam@icloud.com>
2025-04-17 15:04:01 +01:00
Matt Aitken 33e7b6865a Set env vars to be "secret" (#1923)
* WIP on secret env vars

* Editing individual env var values is working

* Sort the env vars by the key

* Deleting values

* Allowing setting secret env vars

* Added medium switch style

* Many style changes to the env var form

* “Copy text” -> “Copy”

* Draw a divider between hidden buttons

* Env var tweaks

* Don’t show Dev:you anymore

* Grouping the same env var keys together

* Styles improved

* Improved styling of edit panel

* Fix bun detection, dev flushing, and init command (#1914)

* update nypm to support text-based bun lockfiles

* add nypm changeset

* handle dev flushing failures gracefully

* fix path normalization for init.ts

* add changesets

* chore: remove pre.json after exiting pre mode

* init command to install v4-beta packages

* Revert "chore: remove pre.json after exiting pre mode"

This reverts commit f5694fde9314114c74a220c2213d19667bca1a6c.

* make init default to cli version for all packages

* Release 4.0.0-v4-beta.1 (#1916)

* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.1

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>

* Both run engines will only lock to versions they can handle (#1922)

* run engine v1 will only lock to v1 deployments

* run engine v2 will only lock to managed v2 deployments

* test: create background worker and deployment with correct engine version

* Add links to and from deployments (#1921)

* link from deployments tasks to filtered runs view

* jump to deployment

* don't add version links for dev (yet)

* Fix current worker deployment getter (#1924)

* only return last v1 deployment in the shared queue consumer

* be explicit about only returning managed deployments

* Add a docs page for the human-in-the-loop example project (#1919)

* Add a docs page for the human-in-the-loop example project

* Order guides, example projects and example tasks alphabetically in the docs list

* Managed run controller revamp (#1927)

* update nypm to support text-based bun lockfiles

* fix retry spans

* only download debug logs if admin

* add nypm changeset

* pull out env override logic

* use runner env gather helper

* handle dev flushing failures gracefully

* fix path normalization for init.ts

* add logger

* add execution heartbeat service

* add snapshot poller service

* fix poller

* add changesets

* create socket in constructor

* enable strictPropertyInitialization

* deprecate dequeue from version

* start is not async

* dependency injection in prep for tests

* add warm start count to all controller logs

* add restore count

* pull out run execution logic

* temp disable pre

* add a controller log when starting an execution

* refactor execution and squash some bugs

* cleanup completed docker containers by default

* execution fixes and logging improvements

* don't throw afet abort cleanup

* poller should use private interval

* rename heartbeat service file

* rename HeartbeatService to IntervalService

* restore old heartbeat service but deprecate it

* use the new interval service everywhere

* Revert "temp disable pre"

This reverts commit e03f4179de6a731c17253b68a6e00bcb7ac1736b.

* add changeset

* replace all run engine find uniques with find first

* Release 4.0.0-v4-beta.2 (#1928)

* chore: Update version for release (v4-beta)

* Release 4.0.0-v4-beta.2

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>

* Remove batch ID carryover for non-batch waits (#1930)

* add failing test case

* do not carry over previous batch id when blocking with waitpoint

* delete irrelevant test

* Delete project (#1913)

* Delete project

- Don’t schedule tasks if the project is deleted
- Delete queues from the master queues
- Add the old delete project UI back in

* Mark the project as deleted last

* Fix for overriding local variable

* Added a todo for deleting env queues

* Remove todo

* Improve usage flushing (#1931)

* add flush to global usage api

* enable controller debug logs

* initialize usage manager after env overrides

* add previous run id to more debug logs

* add changeset

* For secret env vars, don’t return the value

* Added a new env var repository function for getting secrets with redactions

* Test task for env vars

* Delete heartbeat file, merge mess up

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Saadi Myftija <saadi.myftija@gmail.com>
2025-04-17 14:39:09 +01:00
Matt Aitken 1b758bd894 Only prevent triggering runs if the parent is completed AND it’s a triggerAndWait (#1935) 2025-04-17 14:25:20 +01:00
Eric Allam e501113a73 fix: refill release concurrency token bucket queue when runs resume before checkpoints are created (#1933)
* fix: refill release concurrency token bucket queue when runs resume before checkpoints are created

* Fix the engine package.json tests

* Remove log
2025-04-16 20:18:38 +01:00
1251 changed files with 97812 additions and 25763 deletions
-10
View File
@@ -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)
-12
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/sdk": patch
---
Deprecate toolTask and replace with `ai.tool(mySchemaTask)`
-5
View File
@@ -1,5 +0,0 @@
---
"trigger.dev": patch
---
Fix init.ts in custom trigger dirs
-5
View File
@@ -1,5 +0,0 @@
---
"trigger.dev": patch
---
Init command will now correctly install v4-beta packages
-5
View File
@@ -1,5 +0,0 @@
---
"trigger.dev": patch
---
Improve warm start times by eagerly creating the child TaskRunProcess when a previous run as completed
-5
View File
@@ -1,5 +0,0 @@
---
"trigger.dev": patch
---
Update nypm package to support test-based bun.lock files
-35
View File
@@ -1,35 +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": [
"breezy-turtles-talk",
"four-needles-add",
"honest-files-decide",
"late-chairs-ring",
"moody-squids-count",
"nice-colts-boil",
"polite-lies-fix",
"red-wasps-cover",
"shiny-kiwis-beam",
"smart-coins-hammer",
"sour-mirrors-accept",
"tiny-buckets-teach",
"tricky-houses-invite",
"weak-jobs-hide"
]
}
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/core": patch
---
Suppress external instrumentation for fetch calls from ApiClient
-5
View File
@@ -1,5 +0,0 @@
---
"trigger.dev": patch
---
Handle flush errors gracefully in dev
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/core": patch
---
fix: Realtime streams: prevent enqueuing into closed ReadableStream
-6
View File
@@ -1,6 +0,0 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Improve usage flushing
-5
View File
@@ -1,5 +0,0 @@
---
"trigger.dev": patch
---
Fix stalled run detection
-6
View File
@@ -1,6 +0,0 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Managed run controller performance and reliability improvements
-7
View File
@@ -1,7 +0,0 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
v4: New lifecycle hooks
-22
View File
@@ -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<typeof emailSequence>(
"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.
+3 -1
View File
@@ -4,4 +4,6 @@ apps/proxy/
apps/coordinator/
packages/rsc/
.changeset
.zed
.zed
.env
!.env.example
+8 -29
View File
@@ -12,7 +12,9 @@ APP_ENV=development
APP_ORIGIN=http://localhost:3030
ELECTRIC_ORIGIN=http://localhost:3060
NODE_ENV=development
V3_ENABLED=true
# Set this to UTC because Node.js uses the system timezone
TZ="UTC"
# Redis is used for the v3 queuing and v2 concurrency control
REDIS_HOST="localhost"
@@ -22,6 +24,9 @@ REDIS_TLS_DISABLED="true"
DEV_OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:3030/otel"
DEV_OTEL_BATCH_PROCESSING_ENABLED="0"
# When the domain is set to `localhost` the CLI deploy command will only --load the image by default and not --push it
DEPLOY_REGISTRY_HOST=localhost:5000
# OPTIONAL VARIABLES
# This is used for validating emails that are allowed to log in. Every email that do not match this regex will be rejected.
# WHITELISTED_EMAILS="authorized@yahoo\.com|authorized@gmail\.com"
@@ -60,34 +65,11 @@ DEV_OTEL_BATCH_PROCESSING_ENABLED="0"
# FROM_EMAIL=
# REPLY_TO_EMAIL=
# Remove the following line to enable logging telemetry traces to the console
LOG_TELEMETRY="false"
# CLOUD VARIABLES
POSTHOG_PROJECT_KEY=
PLAIN_API_KEY=
CLOUD_AIRTABLE_CLIENT_ID=
CLOUD_AIRTABLE_CLIENT_SECRET=
CLOUD_GITHUB_CLIENT_ID=
CLOUD_GITHUB_CLIENT_SECRET=
CLOUD_LINEAR_CLIENT_ID=
CLOUD_LINEAR_CLIENT_SECRET=
CLOUD_SLACK_APP_HOST=
CLOUD_SLACK_CLIENT_ID=
CLOUD_SLACK_CLIENT_SECRET=
# v3 variables
PROVIDER_SECRET=provider-secret # generate the actual secret with `openssl rand -hex 32`
COORDINATOR_SECRET=coordinator-secret # generate the actual secret with `openssl rand -hex 32`
# Uncomment the following line to enable the registry proxy
# ENABLE_REGISTRY_PROXY=true
# DEPOT_ORG_ID=<Depot org id>
# DEPOT_TOKEN=<Depot org token>
# DEPOT_PROJECT_ID=<Depot project id>
# DEPLOY_REGISTRY_HOST=${APP_ORIGIN} # This is the host that the deploy CLI will use to push images to the registry
# CONTAINER_REGISTRY_ORIGIN=<Container registry origin e.g. https://registry.digitalocean.com>
# CONTAINER_REGISTRY_USERNAME=<Container registry username e.g. Digital ocean email address>
# CONTAINER_REGISTRY_PASSWORD=<Container registry password e.g. Digital ocean PAT>
# DEV_OTEL_EXPORTER_OTLP_ENDPOINT="http://0.0.0.0:4318"
# These are needed for the object store (for handling large payloads/outputs)
# OBJECT_STORE_BASE_URL="https://{bucket}.{accountId}.r2.cloudflarestorage.com"
@@ -97,8 +79,5 @@ COORDINATOR_SECRET=coordinator-secret # generate the actual secret with `openssl
# These control the server-side internal telemetry
# INTERNAL_OTEL_TRACE_EXPORTER_URL=<URL to send traces to>
# INTERNAL_OTEL_TRACE_EXPORTER_AUTH_HEADER_NAME=<Header name for the auth token>
# INTERNAL_OTEL_TRACE_EXPORTER_AUTH_HEADER_VALUE=<Auth token value>
# INTERNAL_OTEL_TRACE_LOGGING_ENABLED=1
# INTERNAL_OTEL_TRACE_SAMPING_RATE=20 # this means 1/20 traces or 5% of traces will be sampled (sampled = recorded)
# INTERNAL_OTEL_TRACE_INSTRUMENT_PRISMA_ENABLED=0,
# INTERNAL_OTEL_TRACE_INSTRUMENT_PRISMA_ENABLED=0,
+3
View File
@@ -1,5 +1,8 @@
name: "E2E"
permissions:
contents: read
on:
workflow_call:
inputs:
+1
View File
@@ -5,6 +5,7 @@ on:
types: [opened, synchronize, reopened]
paths-ignore:
- "docs/**"
- ".changeset/**"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+29 -2
View File
@@ -1,5 +1,10 @@
name: "🐳 Publish Webapp"
permissions:
contents: read
packages: write
id-token: write
on:
workflow_call:
inputs:
@@ -43,13 +48,25 @@ jobs:
ref_without_tag=ghcr.io/triggerdotdev/trigger.dev
image_tags=$ref_without_tag:${{ steps.get_tag.outputs.tag }}
# if tag is a semver, also tag it as v3
# if tag is a semver, also tag it as v4
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
image_tags=$image_tags,$ref_without_tag:v3
# TODO: switch to v4 tag on GA
image_tags=$image_tags,$ref_without_tag:v4-beta
fi
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
- name: 📝 Set the build info
id: set_build_info
run: |
tag=${{ steps.get_tag.outputs.tag }}
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
echo "BUILD_APP_VERSION=${tag}" >> "$GITHUB_OUTPUT"
fi
echo "BUILD_GIT_SHA=${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo "BUILD_GIT_REF_NAME=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP_SECONDS=$(date +%s)" >> "$GITHUB_OUTPUT"
- name: 🐙 Login to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -64,3 +81,13 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.set_tags.outputs.image_tags }}
push: true
build-args: |
BUILD_APP_VERSION=${{ steps.set_build_info.outputs.BUILD_APP_VERSION }}
BUILD_GIT_SHA=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
BUILD_GIT_REF_NAME=${{ steps.set_build_info.outputs.BUILD_GIT_REF_NAME }}
BUILD_TIMESTAMP_SECONDS=${{ steps.set_build_info.outputs.BUILD_TIMESTAMP_SECONDS }}
SENTRY_RELEASE=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
SENTRY_ORG=triggerdev
SENTRY_PROJECT=trigger-cloud
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
@@ -1,4 +1,4 @@
name: "⚒️ Publish Worker RE2"
name: "⚒️ Publish Worker (v4)"
on:
workflow_call:
@@ -14,6 +14,7 @@ on:
- "re2-prod-*"
permissions:
id-token: write
packages: write
contents: read
@@ -35,6 +36,9 @@ jobs:
env:
DOCKER_BUILDKIT: "1"
steps:
- name: 🏭 Setup Depot CLI
uses: depot/setup-action@v1
- name: ⬇️ Checkout git repo
uses: actions/checkout@v4
@@ -49,26 +53,26 @@ jobs:
fi
echo "repo=${repo}" >> "$GITHUB_OUTPUT"
- id: get_tag
- name: "#️⃣ Get image tag"
id: get_tag
uses: ./.github/actions/get-image-tag
with:
tag: ${{ inputs.image_tag }}
- name: 🐋 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# ..to avoid rate limits when pulling images
- name: 🐳 Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🚢 Build Container Image
- name: 📛 Set tags to push
id: set_tags
run: |
docker build -t infra_image -f ./apps/${{ matrix.package }}/Containerfile .
ref_without_tag=ghcr.io/triggerdotdev/${{ steps.get_repository.outputs.repo }}
image_tags=$ref_without_tag:${{ steps.get_tag.outputs.tag }}
# if tag is a semver, also tag it as v4
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
# TODO: switch to v4 tag on GA
image_tags=$image_tags,$ref_without_tag:v4-beta
fi
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
# ..to push image
- name: 🐙 Login to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -76,20 +80,10 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 🐙 Push to GitHub Container Registry
run: |
docker tag infra_image "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
docker push "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
env:
REGISTRY: ghcr.io/triggerdotdev
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
IMAGE_TAG: ${{ steps.get_tag.outputs.tag }}
- name: 🐙 Push 'v3' tag to GitHub Container Registry
if: steps.get_tag.outputs.is_semver == 'true'
run: |
docker tag infra_image "$REGISTRY/$REPOSITORY:v3"
docker push "$REGISTRY/$REPOSITORY:v3"
env:
REGISTRY: ghcr.io/triggerdotdev
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
- name: 🐳 Build image and push to GitHub Container Registry
uses: depot/build-push-action@v1
with:
file: ./apps/${{ matrix.package }}/Containerfile
platforms: linux/amd64,linux/arm64
tags: ${{ steps.set_tags.outputs.image_tags }}
push: true
+8 -8
View File
@@ -77,11 +77,11 @@ jobs:
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
IMAGE_TAG: ${{ steps.get_tag.outputs.tag }}
- name: 🐙 Push 'v3' tag to GitHub Container Registry
if: steps.get_tag.outputs.is_semver == 'true'
run: |
docker tag infra_image "$REGISTRY/$REPOSITORY:v3"
docker push "$REGISTRY/$REPOSITORY:v3"
env:
REGISTRY: ghcr.io/triggerdotdev
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
# - name: 🐙 Push 'v3' tag to GitHub Container Registry
# if: steps.get_tag.outputs.is_semver == 'true'
# run: |
# docker tag infra_image "$REGISTRY/$REPOSITORY:v3"
# docker push "$REGISTRY/$REPOSITORY:v3"
# env:
# REGISTRY: ghcr.io/triggerdotdev
# REPOSITORY: ${{ steps.get_repository.outputs.repo }}
+7
View File
@@ -68,3 +68,10 @@ jobs:
secrets: inherit
with:
image_tag: ${{ inputs.image_tag }}
publish-worker-v4:
needs: [typecheck]
uses: ./.github/workflows/publish-worker-v4.yml
secrets: inherit
with:
image_tag: ${{ inputs.image_tag }}
+143
View File
@@ -0,0 +1,143 @@
name: 🧭 Helm Chart Release
on:
push:
tags:
- 'helm-v*'
workflow_dispatch:
inputs:
chart_version:
description: 'Chart version to release'
required: true
type: string
env:
REGISTRY: ghcr.io
CHART_NAME: trigger
jobs:
lint-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: "3.18.3"
- name: Build dependencies
run: helm dependency build ./hosting/k8s/helm/
- name: Extract dependency charts
run: |
cd ./hosting/k8s/helm/
for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done
- name: Lint Helm Chart
run: |
helm lint ./hosting/k8s/helm/
- name: Render templates
run: |
helm template test-release ./hosting/k8s/helm/ \
--values ./hosting/k8s/helm/values.yaml \
--output-dir ./helm-output
- name: Validate manifests
uses: docker://ghcr.io/yannh/kubeconform:v0.7.0
with:
entrypoint: '/kubeconform'
args: "-summary -output json ./helm-output"
release:
needs: lint-and-test
runs-on: ubuntu-latest
permissions:
contents: write # for gh-release
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: "3.18.3"
- name: Build dependencies
run: helm dependency build ./hosting/k8s/helm/
- name: Extract dependency charts
run: |
cd ./hosting/k8s/helm/
for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag or input
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.chart_version }}"
else
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#helm-v}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Releasing version: $VERSION"
- name: Check Chart.yaml version matches release version
run: |
VERSION="${{ steps.version.outputs.version }}"
CHART_VERSION=$(grep '^version:' ./hosting/k8s/helm/Chart.yaml | awk '{print $2}')
echo "Chart.yaml version: $CHART_VERSION"
echo "Release version: $VERSION"
if [ "$CHART_VERSION" != "$VERSION" ]; then
echo "❌ Chart.yaml version does not match release version!"
exit 1
fi
echo "✅ Chart.yaml version matches release version."
- name: Package Helm Chart
run: |
helm package ./hosting/k8s/helm/ --destination /tmp/
- name: Push Helm Chart to GHCR
run: |
VERSION="${{ steps.version.outputs.version }}"
CHART_PACKAGE="/tmp/${{ env.CHART_NAME }}-${VERSION}.tgz"
# Push to GHCR OCI registry
helm push "$CHART_PACKAGE" "oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts"
- name: Create GitHub Release
id: release
uses: softprops/action-gh-release@v1
if: github.event_name == 'push'
with:
tag_name: ${{ github.ref_name }}
name: "Helm Chart ${{ steps.version.outputs.version }}"
body: |
### Installation
```bash
helm upgrade --install trigger \
oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts/${{ env.CHART_NAME }} \
--version "${{ steps.version.outputs.version }}"
```
### Changes
See commit history for detailed changes in this release.
files: |
/tmp/${{ env.CHART_NAME }}-${{ steps.version.outputs.version }}.tgz
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
prerelease: true
+7 -10
View File
@@ -1,5 +1,8 @@
name: 🦋 Changesets Release
permissions:
contents: write
on:
push:
branches:
@@ -18,6 +21,10 @@ jobs:
release:
name: 🦋 Changesets Release
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
pull-requests: write
if: github.repository == 'triggerdotdev/trigger.dev'
outputs:
published: ${{ steps.changesets.outputs.published }}
@@ -89,13 +96,3 @@ jobs:
run: |
package_version=$(echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -r '.[0].version')
echo "package_version=${package_version}" >> "$GITHUB_OUTPUT"
publish:
needs: release
uses: ./.github/workflows/publish.yml
secrets: inherit
# if: needs.release.outputs.published == 'true'
# disable automatic publishing for now
if: false
with:
image_tag: v${{ needs.release.outputs.published_package_version }}
+3
View File
@@ -3,6 +3,9 @@ name: "ʦ TypeScript"
on:
workflow_call:
permissions:
contents: read
jobs:
typecheck:
runs-on: ubuntu-latest
+130
View File
@@ -0,0 +1,130 @@
name: "🧪 Unit Tests: Internal"
permissions:
contents: read
on:
workflow_call:
jobs:
unitTests:
name: "🧪 Unit Tests: Internal"
runs-on: ubuntu-latest
strategy:
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
SHARD_INDEX: ${{ matrix.shardIndex }}
SHARD_TOTAL: ${{ matrix.shardTotal }}
steps:
- name: 🔧 Disable IPv6
run: |
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
- name: 🔧 Configure docker address pool
run: |
CONFIG='{
"default-address-pools" : [
{
"base" : "172.17.0.0/12",
"size" : 20
},
{
"base" : "192.168.0.0/16",
"size" : 24
}
]
}'
mkdir -p /etc/docker
echo "$CONFIG" | sudo tee /etc/docker/daemon.json
- name: 🔧 Restart docker daemon
run: sudo systemctl restart docker
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
cache: "pnpm"
# ..to avoid rate limits when pulling images
- name: 🐳 Login to DockerHub
if: ${{ env.DOCKERHUB_USERNAME }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🐳 Skipping DockerHub login (no secrets available)
if: ${{ !env.DOCKERHUB_USERNAME }}
run: echo "DockerHub login skipped because secrets are not available."
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 📀 Generate Prisma Client
run: pnpm run generate
- name: 🧪 Run Internal Unit Tests
run: pnpm run test:internal --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Gather all reports
if: ${{ !cancelled() }}
run: |
mkdir -p .vitest-reports
find . -type f -path '*/.vitest-reports/blob-*.json' \
-exec bash -c 'src="$1"; basename=$(basename "$src"); pkg=$(dirname "$src" | sed "s|^\./||;s|/\.vitest-reports$||;s|/|_|g"); cp "$src" ".vitest-reports/${pkg}-${basename}"' _ {} \;
- name: Upload blob reports to GitHub Actions Artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: internal-blob-report-${{ matrix.shardIndex }}
path: .vitest-reports/*
include-hidden-files: true
retention-days: 1
merge-reports:
name: "📊 Merge Reports"
if: ${{ !cancelled() }}
needs: [unitTests]
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
# no cache enabled, we're not installing deps
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: .vitest-reports
pattern: internal-blob-report-*
merge-multiple: true
- name: Merge reports
run: pnpm dlx vitest@3.1.4 run --merge-reports --pass-with-no-tests
+130
View File
@@ -0,0 +1,130 @@
name: "🧪 Unit Tests: Packages"
permissions:
contents: read
on:
workflow_call:
jobs:
unitTests:
name: "🧪 Unit Tests: Packages"
runs-on: ubuntu-latest
strategy:
matrix:
shardIndex: [1]
shardTotal: [1]
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
SHARD_INDEX: ${{ matrix.shardIndex }}
SHARD_TOTAL: ${{ matrix.shardTotal }}
steps:
- name: 🔧 Disable IPv6
run: |
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
- name: 🔧 Configure docker address pool
run: |
CONFIG='{
"default-address-pools" : [
{
"base" : "172.17.0.0/12",
"size" : 20
},
{
"base" : "192.168.0.0/16",
"size" : 24
}
]
}'
mkdir -p /etc/docker
echo "$CONFIG" | sudo tee /etc/docker/daemon.json
- name: 🔧 Restart docker daemon
run: sudo systemctl restart docker
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
cache: "pnpm"
# ..to avoid rate limits when pulling images
- name: 🐳 Login to DockerHub
if: ${{ env.DOCKERHUB_USERNAME }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🐳 Skipping DockerHub login (no secrets available)
if: ${{ !env.DOCKERHUB_USERNAME }}
run: echo "DockerHub login skipped because secrets are not available."
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 📀 Generate Prisma Client
run: pnpm run generate
- name: 🧪 Run Package Unit Tests
run: pnpm run test:packages --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Gather all reports
if: ${{ !cancelled() }}
run: |
mkdir -p .vitest-reports
find . -type f -path '*/.vitest-reports/blob-*.json' \
-exec bash -c 'src="$1"; basename=$(basename "$src"); pkg=$(dirname "$src" | sed "s|^\./||;s|/\.vitest-reports$||;s|/|_|g"); cp "$src" ".vitest-reports/${pkg}-${basename}"' _ {} \;
- name: Upload blob reports to GitHub Actions Artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: packages-blob-report-${{ matrix.shardIndex }}
path: .vitest-reports/*
include-hidden-files: true
retention-days: 1
merge-reports:
name: "📊 Merge Reports"
if: ${{ !cancelled() }}
needs: [unitTests]
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
# no cache enabled, we're not installing deps
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: .vitest-reports
pattern: packages-blob-report-*
merge-multiple: true
- name: Merge reports
run: pnpm dlx vitest@3.1.4 run --merge-reports --pass-with-no-tests
+138
View File
@@ -0,0 +1,138 @@
name: "🧪 Unit Tests: Webapp"
permissions:
contents: read
on:
workflow_call:
jobs:
unitTests:
name: "🧪 Unit Tests: Webapp"
runs-on: ubuntu-latest
strategy:
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
SHARD_INDEX: ${{ matrix.shardIndex }}
SHARD_TOTAL: ${{ matrix.shardTotal }}
steps:
- name: 🔧 Disable IPv6
run: |
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
- name: 🔧 Configure docker address pool
run: |
CONFIG='{
"default-address-pools" : [
{
"base" : "172.17.0.0/12",
"size" : 20
},
{
"base" : "192.168.0.0/16",
"size" : 24
}
]
}'
mkdir -p /etc/docker
echo "$CONFIG" | sudo tee /etc/docker/daemon.json
- name: 🔧 Restart docker daemon
run: sudo systemctl restart docker
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
cache: "pnpm"
# ..to avoid rate limits when pulling images
- name: 🐳 Login to DockerHub
if: ${{ env.DOCKERHUB_USERNAME }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🐳 Skipping DockerHub login (no secrets available)
if: ${{ !env.DOCKERHUB_USERNAME }}
run: echo "DockerHub login skipped because secrets are not available."
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 📀 Generate Prisma Client
run: pnpm run generate
- name: 🧪 Run Webapp Unit Tests
run: pnpm run test:webapp --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
DIRECT_URL: postgresql://postgres:postgres@localhost:5432/postgres
SESSION_SECRET: "secret"
MAGIC_LINK_SECRET: "secret"
ENCRYPTION_KEY: "secret"
DEPLOY_REGISTRY_HOST: "docker.io"
CLICKHOUSE_URL: "http://default:password@localhost:8123"
- name: Gather all reports
if: ${{ !cancelled() }}
run: |
mkdir -p .vitest-reports
find . -type f -path '*/.vitest-reports/blob-*.json' \
-exec bash -c 'src="$1"; basename=$(basename "$src"); pkg=$(dirname "$src" | sed "s|^\./||;s|/\.vitest-reports$||;s|/|_|g"); cp "$src" ".vitest-reports/${pkg}-${basename}"' _ {} \;
- name: Upload blob reports to GitHub Actions Artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: webapp-blob-report-${{ matrix.shardIndex }}
path: .vitest-reports/*
include-hidden-files: true
retention-days: 1
merge-reports:
name: "📊 Merge Reports"
if: ${{ !cancelled() }}
needs: [unitTests]
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
# no cache enabled, we're not installing deps
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: .vitest-reports
pattern: webapp-blob-report-*
merge-multiple: true
- name: Merge reports
run: pnpm dlx vitest@3.1.4 run --merge-reports --pass-with-no-tests
+12 -47
View File
@@ -1,53 +1,18 @@
name: "🧪 Unit Tests"
permissions:
contents: read
on:
workflow_call:
jobs:
unitTests:
name: "🧪 Unit Tests"
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v4
with:
node-version: 20.11.1
cache: "pnpm"
# ..to avoid rate limits when pulling images
- name: 🐳 Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 📀 Generate Prisma Client
run: pnpm run generate
- name: 🧪 Run Webapp Unit Tests
run: pnpm run test:webapp
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
DIRECT_URL: postgresql://postgres:postgres@localhost:5432/postgres
SESSION_SECRET: "secret"
MAGIC_LINK_SECRET: "secret"
ENCRYPTION_KEY: "secret"
- name: 🧪 Run Package Unit Tests
run: pnpm run test:packages
- name: 🧪 Run Internal Unit Tests
run: pnpm run test:internal
webapp:
uses: ./.github/workflows/unit-tests-webapp.yml
secrets: inherit
packages:
uses: ./.github/workflows/unit-tests-packages.yml
secrets: inherit
internal:
uses: ./.github/workflows/unit-tests-internal.yml
secrets: inherit
+1
View File
@@ -63,3 +63,4 @@ apps/**/public/build
/packages/core/src/package.json
/packages/trigger-sdk/src/package.json
/packages/python/src/package.json
.claude
+3 -3
View File
@@ -25,8 +25,8 @@
{
"type": "node-terminal",
"request": "launch",
"name": "Debug fairDequeuingStrategy.test.ts",
"command": "pnpm run test -t FairDequeuingStrategy",
"name": "Debug triggerTask.test.ts",
"command": "pnpm run test --run ./test/engine/triggerTask.test.ts",
"envFile": "${workspaceFolder}/.env",
"cwd": "${workspaceFolder}/apps/webapp",
"sourceMaps": true
@@ -146,7 +146,7 @@
"type": "node-terminal",
"request": "launch",
"name": "Debug RunQueue tests",
"command": "pnpm run test ./src/run-queue/index.test.ts",
"command": "pnpm run test ./src/run-queue/index.test.ts --run",
"cwd": "${workspaceFolder}/internal-packages/run-engine",
"sourceMaps": true
},
+68
View File
@@ -0,0 +1,68 @@
# Guidance for Coding Agents
This repository is a pnpm monorepo managed with Turbo. It contains multiple apps and packages that make up the Trigger.dev platform and SDK.
## Repository layout
- `apps/webapp` Remix application that serves as the main API and dashboard.
- `apps/supervisor` Node application for executing built tasks.
- `packages/*` Published packages such as `@trigger.dev/sdk`, the CLI (`trigger.dev`), and shared libraries.
- `internal-packages/*` Internal-only packages used by the webapp and other apps.
- `references/*` Example projects for manual testing and development of new features.
- `ai/references` Contains additional documentation including an overview (`repo.md`) and testing guidelines (`tests.md`).
See `ai/references/repo.md` for a more complete explanation of the workspaces.
## Development setup
1. Install dependencies with `pnpm i` (pnpm `8.15.5` and Node.js `20.11.1` are required).
2. Copy `.env.example` to `.env` and generate a random 16 byte hex string for `ENCRYPTION_KEY` (`openssl rand -hex 16`). Update other secrets if needed.
3. Start the local services with Docker:
```bash
pnpm run docker
```
4. Run database migrations:
```bash
pnpm run db:migrate
```
5. Build the webapp, CLI and SDK packages:
```bash
pnpm run build --filter webapp && pnpm run build --filter trigger.dev && pnpm run build --filter @trigger.dev/sdk
```
6. Launch the development server:
```bash
pnpm run dev --filter webapp
```
The webapp runs on <http://localhost:3030>.
For full setup instructions see `CONTRIBUTING.md`.
## Running tests
- Unit tests use **vitest**. Run all tests:
```bash
pnpm run test
```
- Run tests for a specific workspace (example for `webapp`):
```bash
pnpm run test --filter webapp
```
- Prefer running a single test file from within its directory:
```bash
cd apps/webapp
pnpm run test ./src/components/Button.test.ts
```
If packages in that workspace need to be built first, run `pnpm run build --filter webapp`.
Refer to `ai/references/tests.md` for details on writing tests. Tests should avoid mocks or stubs and use the helpers from `@internal/testcontainers` when Redis or Postgres are needed.
## Coding style
- Formatting is enforced using Prettier. Run `pnpm run format` before committing.
- Follow the existing project conventions. Test files live beside the files under test and use descriptive `describe` and `it` blocks.
- Do not commit directly to the `main` branch. All changes should be made in a separate branch and go through a pull request.
## Additional docs
- The root `README.md` describes Trigger.dev and links to documentation.
- The `docs` workspace contains our documentation site, which can be run locally with:
```bash
pnpm run dev --filter docs
```
- `references/README.md` explains how to create new reference projects for manual testing.
+6 -20
View File
@@ -30,28 +30,14 @@ Please follow the best-practice of adding changesets in the same commit as the c
## Snapshot instructions
!MAKE SURE TO UPDATE THE TAG IN THE INSTRUCTIONS BELOW!
1. Delete the `.changeset/pre.json` file (if it exists)
1. Add changesets as usual
2. Do a temporary commit (do NOT push this, you should undo it after)
```sh
pnpm run changeset:add
```
3. Copy the `GITHUB_TOKEN` line from the .env file
2. Create a snapshot version (replace "prerelease" with your tag)
4. Run `GITHUB_TOKEN=github_pat_12345 ./scripts/publish-prerelease.sh re2`
```sh
pnpm exec changeset version --snapshot prerelease
```
Make sure to replace the token with yours. `re2` is the tag that will be used for the pre-release.
3. Build the packages:
```sh
pnpm run build --filter "@trigger.dev/*" --filter "trigger.dev"
```
4. Publish the snapshot (replace "dev" with your tag)
```sh
pnpm exec changeset publish --no-git-tag --snapshot --tag prerelease
```
5. Undo the commit where you deleted the pre.json file.
+6 -10
View File
@@ -62,15 +62,13 @@ branch are tagged into a release periodically.
pnpm run docker
```
This will also start and run a local instance of [pgAdmin](https://www.pgadmin.org/) on [localhost:5480](http://localhost:5480), preconfigured with email `admin@example.com` and pwd `admin`. Then use `postgres` as the password to the Trigger.dev server.
9. Migrate the database
```
pnpm run db:migrate
```
10. Build the server app
10. Build everything
```
pnpm run build --filter webapp
pnpm run build --filter webapp && pnpm run build --filter trigger.dev && pnpm run build --filter @trigger.dev/sdk
```
11. Run the app. See the section below.
@@ -94,13 +92,11 @@ We use the `<root>/references/v3-catalog` subdirectory as a staging ground for t
First, make sure you are running the webapp according to the instructions above. Then:
1. In Postgres go to the "Organizations" table and on your org set the `v3Enabled` column to `true`.
1. Visit http://localhost:3030 in your browser and create a new V3 project called "v3-catalog".
2. Visit http://localhost:3030 in your browser and create a new V3 project called "v3-catalog". If you don't see an option for V3, you haven't set the `v3Enabled` flag to true.
2. In Postgres go to the "Projects" table and for the project you create change the `externalRef` to `yubjwjsfkxnylobaqvqz`.
3. In Postgres go to the "Projects" table and for the project you create change the `externalRef` to `yubjwjsfkxnylobaqvqz`.
4. Build the CLI
3. Build the CLI
```sh
# Build the CLI
@@ -109,7 +105,7 @@ pnpm run build --filter trigger.dev
pnpm i
```
5. Change into the `<root>/references/v3-catalog` directory and authorize the CLI to the local server:
4. Change into the `<root>/references/v3-catalog` directory and authorize the CLI to the local server:
```sh
cd references/v3-catalog
+24 -15
View File
@@ -8,47 +8,56 @@ If you don't have Docker installed on your machine, you'll run into some complic
Below are the steps on how you can avoid that.
First you need to setup docker-compose as it is an underlying tool that this command: `pnpm run docker` fires behind the scene.
First you need to setup docker compose as it is an underlying tool that this command: `pnpm run docker` fires behind the scene.
## Linux
To install Docker Compose on Linux Ubuntu via the terminal, you can follow these steps:
To install Docker Compose on Linux Ubuntu, you can follow these steps:
1. Update the package index on your system by running the following command:
1. Create the Docker config directory and cli-plugins subdirectory:
```shell
sudo apt update
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
```
2. Install the required dependencies by running the following command:
2. Download the Docker Compose plugin:
```shell
sudo apt install curl
curl -SL "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o $DOCKER_CONFIG/cli-plugins/docker-compose
```
3. Download the Docker Compose binary into the `/usr/local/bin` directory using the `curl` command:
Note:
- To install for all users, replace `$DOCKER_CONFIG/cli-plugins` with `/usr/local/lib/docker/cli-plugins`
3. Set the appropriate permissions to make the Docker Compose plugin executable:
```shell
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
```
4. Set the appropriate permissions to make the `docker-compose` binary executable:
If you installed for all users:
```shell
sudo chmod +x /usr/local/bin/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
```
5. Verify that Docker Compose has been successfully installed by running the following command:
4. Verify that Docker Compose has been successfully installed:
```shell
docker-compose --version
docker compose version
```
This command should display the version information of Docker Compose without any errors.
You should see output similar to:
After following these steps, you should have Docker Compose installed on your Ubuntu system, and you can use it by running `docker-compose` commands in the terminal.
```
Docker Compose version vX.Y.Z
```
When you've verified that the `docker-compose` package is installed and you proceed to start Docker with `pnpm run docker`.
After following these steps, you should have Docker Compose installed on your Ubuntu system, and you can use it by running `docker compose` commands in the terminal.
When you've verified that the `docker compose` package is installed and you proceed to start Docker with `pnpm run docker`.
You'll probably get an error similar to the one below:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2018",
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"resolveJsonModule": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2018",
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2018",
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
+1 -2
View File
@@ -7,12 +7,11 @@ MANAGED_WORKER_SECRET=managed-secret
# Point this at the webapp in prod
TRIGGER_API_URL=http://localhost:3030
# Point this at the OTel collector in prod
# Point this at the webapp or an OTel collector in prod
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3030/otel
# Use this on macOS
# OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:3030/otel
# Optional settings
DEBUG=1
ENFORCE_MACHINE_PRESETS=1
TRIGGER_DEQUEUE_INTERVAL_MS=1000
+1
View File
@@ -19,6 +19,7 @@ COPY --from=pruner --chown=node:node /app/out/pnpm-workspace.yaml ./pnpm-workspa
RUN corepack enable && corepack prepare --activate
FROM base AS deps-fetcher
RUN apk add --no-cache python3-dev py3-setuptools make g++ gcc linux-headers
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile
FROM deps-fetcher AS dev-deps
+47 -9
View File
@@ -19,6 +19,8 @@ curl -sS \
-d "{\"name\": \"$wg_name\"}"
```
If the worker group is newly created, the response will include a `token` field. If the group already exists, no token is returned.
2. Create `.env` and set the worker token
```sh
@@ -34,7 +36,7 @@ TRIGGER_WORKER_TOKEN=tr_wgt_...
pnpm dev
```
4. Build CLI, then deploy a reference project
4. Build CLI, then deploy a test project
```sh
pnpm exec trigger deploy --self-hosted
@@ -43,16 +45,36 @@ pnpm exec trigger deploy --self-hosted
pnpm exec trigger deploy --self-hosted --network host
```
## Additional worker groups
## Worker group management
When adding more worker groups you might also want to make them the default for a specific project. This will allow you to test it without having to change the global default:
### Shared variables
```sh
api_url=http://localhost:3030
admin_pat=tr_pat_... # edit this
```
- These are used by all commands
### Create a worker group
```sh
wg_name=my-worker
# edit these
admin_pat=tr_pat_...
curl -sS \
-X POST \
"$api_url/admin/api/v1/workers" \
-H "Authorization: Bearer $admin_pat" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$wg_name\"}"
```
- If the worker group already exists, no token will be returned
### Set a worker group as default for a project
```sh
wg_name=my-worker
project_id=clsw6q8wz...
curl -sS \
@@ -60,8 +82,24 @@ curl -sS \
"$api_url/admin/api/v1/workers" \
-H "Authorization: Bearer $admin_pat" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$wg_name\",
\"makeDefaultForProjectId\": \"$project_id\"
}"
-d "{\"name\": \"$wg_name\", \"projectId\": \"$project_id\", \"makeDefaultForProject\": true}"
```
- If the worker group doesn't exist, yet it will be created
- If the worker group already exists, it will be attached to the project as default. No token will be returned.
### Remove the default worker group from a project
```sh
project_id=clsw6q8wz...
curl -sS \
-X POST \
"$api_url/admin/api/v1/workers" \
-H "Authorization: Bearer $admin_pat" \
-H "Content-Type: application/json" \
-d "{\"projectId\": \"$project_id\", \"removeDefaultFromProject\": true}"
```
- The project will then use the global default again
- When `removeDefaultFromProject: true` no other actions will be performed
+3 -7
View File
@@ -15,17 +15,13 @@
"dependencies": {
"@kubernetes/client-node": "^1.0.0",
"@trigger.dev/core": "workspace:*",
"dockerode": "^4.0.3",
"nanoid": "^5.0.9",
"dockerode": "^4.0.6",
"prom-client": "^15.1.0",
"socket.io": "4.7.4",
"std-env": "^3.8.0",
"tinyexec": "^0.3.1",
"zod": "3.23.8"
"zod": "3.25.76"
},
"devDependencies": {
"@types/dockerode": "^3.3.33",
"docker-api-ts": "^0.2.2",
"vitest": "^1.4.0"
"@types/dockerode": "^3.3.33"
}
}
+4 -1
View File
@@ -3,9 +3,12 @@ import { Informer } from "@kubernetes/client-node";
import { ListPromise } from "@kubernetes/client-node";
import { KubernetesObject } from "@kubernetes/client-node";
import { assertExhaustive } from "@trigger.dev/core/utils";
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
export const RUNTIME_ENV = process.env.KUBERNETES_PORT ? "kubernetes" : "local";
const logger = new SimpleStructuredLogger("kubernetes-client");
export function createK8sApi() {
const kubeConfig = getKubeConfig();
@@ -31,7 +34,7 @@ export function createK8sApi() {
export type K8sApi = ReturnType<typeof createK8sApi>;
function getKubeConfig() {
console.log("getKubeConfig()", { RUNTIME_ENV });
logger.debug("getKubeConfig()", { RUNTIME_ENV });
const kubeConfig = new k8s.KubeConfig();
+42 -16
View File
@@ -6,14 +6,16 @@ import { AdditionalEnvVars, BoolEnv } from "./envUtil.js";
const Env = z.object({
// This will come from `spec.nodeName` in k8s
TRIGGER_WORKER_INSTANCE_NAME: z.string().default(randomUUID()),
TRIGGER_WORKER_HEARTBEAT_INTERVAL_SECONDS: z.coerce.number().default(30),
// Required settings
TRIGGER_API_URL: z.string().url(),
TRIGGER_WORKER_TOKEN: z.string(),
TRIGGER_WORKER_TOKEN: z.string(), // accepts file:// path to read from a file
MANAGED_WORKER_SECRET: z.string(),
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()))
@@ -27,34 +29,57 @@ const Env = z.object({
RUNNER_HEARTBEAT_INTERVAL_SECONDS: z.coerce.number().optional(),
RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS: z.coerce.number().optional(),
RUNNER_ADDITIONAL_ENV_VARS: AdditionalEnvVars, // optional (csv)
RUNNER_DOCKER_AUTOREMOVE: BoolEnv.default(true),
RUNNER_PRETTY_LOGS: BoolEnv.default(false),
// Dequeue settings (provider mode)
TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"),
TRIGGER_DEQUEUE_INTERVAL_MS: z.coerce.number().int().default(1000),
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),
TRIGGER_DEQUEUE_MAX_CONSUMER_COUNT: z.coerce.number().int().default(1),
// Optional services
TRIGGER_WARM_START_URL: z.string().optional(),
TRIGGER_CHECKPOINT_URL: z.string().optional(),
TRIGGER_METADATA_URL: z.string().optional(),
// Used by the workload manager, e.g docker/k8s
DOCKER_NETWORK: z.string().default("host"),
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(),
ENFORCE_MACHINE_PRESETS: z.coerce.boolean().default(false),
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv
// Used by the resource monitor
OVERRIDE_CPU_TOTAL: z.coerce.number().optional(),
OVERRIDE_MEMORY_TOTAL_GB: z.coerce.number().optional(),
RESOURCE_MONITOR_ENABLED: BoolEnv.default(false),
RESOURCE_MONITOR_OVERRIDE_CPU_TOTAL: z.coerce.number().optional(),
RESOURCE_MONITOR_OVERRIDE_MEMORY_TOTAL_GB: z.coerce.number().optional(),
// Kubernetes specific settings
// Docker settings
DOCKER_API_VERSION: z.string().default("v1.41"),
DOCKER_PLATFORM: z.string().optional(), // e.g. linux/amd64, linux/arm64
DOCKER_STRIP_IMAGE_DIGEST: BoolEnv.default(true),
DOCKER_REGISTRY_USERNAME: z.string().optional(),
DOCKER_REGISTRY_PASSWORD: z.string().optional(),
DOCKER_REGISTRY_URL: z.string().optional(), // e.g. https://index.docker.io/v1
DOCKER_ENFORCE_MACHINE_PRESETS: BoolEnv.default(true),
DOCKER_AUTOREMOVE_EXITED_CONTAINERS: BoolEnv.default(true),
/**
* Network mode to use for all runners. Supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`.
* Any other value is taken as a custom network's name to which all runners should connect to.
*
* Accepts a list of comma-separated values to attach to multiple networks. Additional networks are interpreted as network names and will be attached after container creation.
*
* **WARNING**: Specifying multiple networks will slightly increase startup times.
*
* @default "host"
*/
DOCKER_RUNNER_NETWORKS: z.string().default("host"),
// Kubernetes settings
KUBERNETES_FORCE_ENABLED: BoolEnv.default(false),
KUBERNETES_NAMESPACE: z.string().default("default"),
KUBERNETES_WORKER_NODETYPE_LABEL: z.string().default("v4-worker"),
EPHEMERAL_STORAGE_SIZE_LIMIT: z.string().default("10Gi"),
EPHEMERAL_STORAGE_SIZE_REQUEST: z.string().default("2Gi"),
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"),
// 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),
@@ -73,6 +98,7 @@ const Env = z.object({
// Debug
DEBUG: BoolEnv.default(false),
SEND_RUN_DEBUG_LOGS: BoolEnv.default(false),
});
export const env = Env.parse(stdEnv);
+10 -2
View File
@@ -1,6 +1,9 @@
import { z } from "zod";
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
export const BoolEnv = z.preprocess((val) => {
const logger = new SimpleStructuredLogger("env-util");
const baseBoolEnv = z.preprocess((val) => {
if (typeof val !== "string") {
return val;
}
@@ -8,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<typeof baseBoolEnv, "default"> & {
default: (value: boolean) => z.ZodDefault<typeof baseBoolEnv>;
};
export const AdditionalEnvVars = z.preprocess((val) => {
if (typeof val !== "string") {
return val;
@@ -33,7 +41,7 @@ export const AdditionalEnvVars = z.preprocess((val) => {
// Return undefined if no valid key-value pairs were found
return Object.keys(result).length === 0 ? undefined : result;
} catch (error) {
console.warn("Failed to parse additional env vars", { error, val });
logger.warn("Failed to parse additional env vars", { error, val });
return undefined;
}
}, z.record(z.string(), z.string()).optional());
+57 -47
View File
@@ -9,6 +9,7 @@ import { type DequeuedMessage } from "@trigger.dev/core/v3";
import {
DockerResourceMonitor,
KubernetesResourceMonitor,
NoopResourceMonitor,
type ResourceMonitor,
} from "./resourceMonitor.js";
import { KubernetesWorkloadManager } from "./workloadManager/kubernetes.js";
@@ -23,6 +24,7 @@ import { collectDefaultMetrics } from "prom-client";
import { register } from "./metrics.js";
import { PodCleaner } from "./services/podCleaner.js";
import { FailedPodHandler } from "./services/failedPodHandler.js";
import { getWorkerToken } from "./workerToken.js";
if (env.METRICS_COLLECT_DEFAULTS) {
collectDefaultMetrics({ register });
@@ -33,7 +35,7 @@ class ManagedSupervisor {
private readonly metricsServer?: HttpServer;
private readonly workloadServer: WorkloadServer;
private readonly workloadManager: WorkloadManager;
private readonly logger = new SimpleStructuredLogger("managed-worker");
private readonly logger = new SimpleStructuredLogger("managed-supervisor");
private readonly resourceMonitor: ResourceMonitor;
private readonly checkpointClient?: CheckpointClient;
@@ -47,11 +49,11 @@ class ManagedSupervisor {
const { TRIGGER_WORKER_TOKEN, MANAGED_WORKER_SECRET, ...envWithoutSecrets } = env;
if (env.DEBUG) {
console.debug("[ManagedSupervisor] Starting up", { envWithoutSecrets });
this.logger.debug("Starting up", { envWithoutSecrets });
}
if (this.warmStartUrl) {
this.logger.log("[ManagedWorker] 🔥 Warm starts enabled", {
this.logger.log("🔥 Warm starts enabled", {
warmStartUrl: this.warmStartUrl,
});
}
@@ -66,12 +68,22 @@ class ManagedSupervisor {
heartbeatIntervalSeconds: env.RUNNER_HEARTBEAT_INTERVAL_SECONDS,
snapshotPollIntervalSeconds: env.RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS,
additionalEnvVars: env.RUNNER_ADDITIONAL_ENV_VARS,
dockerAutoremove: env.RUNNER_DOCKER_AUTOREMOVE,
dockerAutoremove: env.DOCKER_AUTOREMOVE_EXITED_CONTAINERS,
} satisfies WorkloadManagerOptions;
this.resourceMonitor = env.RESOURCE_MONITOR_ENABLED
? this.isKubernetes
? new KubernetesResourceMonitor(createK8sApi(), env.TRIGGER_WORKER_INSTANCE_NAME)
: new DockerResourceMonitor(new Docker())
: new NoopResourceMonitor();
this.workloadManager = this.isKubernetes
? new KubernetesWorkloadManager(workloadManagerOptions)
: new DockerWorkloadManager(workloadManagerOptions);
if (this.isKubernetes) {
if (env.POD_CLEANER_ENABLED) {
this.logger.log("[ManagedWorker] 🧹 Pod cleaner enabled", {
this.logger.log("🧹 Pod cleaner enabled", {
namespace: env.KUBERNETES_NAMESPACE,
batchSize: env.POD_CLEANER_BATCH_SIZE,
intervalMs: env.POD_CLEANER_INTERVAL_MS,
@@ -83,11 +95,11 @@ class ManagedSupervisor {
intervalMs: env.POD_CLEANER_INTERVAL_MS,
});
} else {
this.logger.warn("[ManagedWorker] Pod cleaner disabled");
this.logger.warn("Pod cleaner disabled");
}
if (env.FAILED_POD_HANDLER_ENABLED) {
this.logger.log("[ManagedWorker] 🔁 Failed pod handler enabled", {
this.logger.log("🔁 Failed pod handler enabled", {
namespace: env.KUBERNETES_NAMESPACE,
reconnectIntervalMs: env.FAILED_POD_HANDLER_RECONNECT_INTERVAL_MS,
});
@@ -97,35 +109,41 @@ class ManagedSupervisor {
reconnectIntervalMs: env.FAILED_POD_HANDLER_RECONNECT_INTERVAL_MS,
});
} else {
this.logger.warn("[ManagedWorker] Failed pod handler disabled");
this.logger.warn("Failed pod handler disabled");
}
}
this.resourceMonitor = new KubernetesResourceMonitor(
createK8sApi(),
env.TRIGGER_WORKER_INSTANCE_NAME
if (env.TRIGGER_DEQUEUE_INTERVAL_MS > env.TRIGGER_DEQUEUE_IDLE_INTERVAL_MS) {
this.logger.warn(
`⚠️ TRIGGER_DEQUEUE_INTERVAL_MS (${env.TRIGGER_DEQUEUE_INTERVAL_MS}) is greater than TRIGGER_DEQUEUE_IDLE_INTERVAL_MS (${env.TRIGGER_DEQUEUE_IDLE_INTERVAL_MS}) - did you mix them up?`
);
this.workloadManager = new KubernetesWorkloadManager(workloadManagerOptions);
} else {
this.resourceMonitor = new DockerResourceMonitor(new Docker());
this.workloadManager = new DockerWorkloadManager(workloadManagerOptions);
}
this.workerSession = new SupervisorSession({
workerToken: env.TRIGGER_WORKER_TOKEN,
workerToken: getWorkerToken(),
apiUrl: env.TRIGGER_API_URL,
instanceName: env.TRIGGER_WORKER_INSTANCE_NAME,
managedWorkerSecret: env.MANAGED_WORKER_SECRET,
dequeueIntervalMs: env.TRIGGER_DEQUEUE_INTERVAL_MS,
dequeueIdleIntervalMs: env.TRIGGER_DEQUEUE_IDLE_INTERVAL_MS,
queueConsumerEnabled: env.TRIGGER_DEQUEUE_ENABLED,
maxRunCount: env.TRIGGER_DEQUEUE_MAX_RUN_COUNT,
maxConsumerCount: env.TRIGGER_DEQUEUE_MAX_CONSUMER_COUNT,
runNotificationsEnabled: env.TRIGGER_WORKLOAD_API_ENABLED,
heartbeatIntervalSeconds: env.TRIGGER_WORKER_HEARTBEAT_INTERVAL_SECONDS,
sendRunDebugLogs: env.SEND_RUN_DEBUG_LOGS,
preDequeue: async () => {
if (!env.RESOURCE_MONITOR_ENABLED) {
return {};
}
if (this.isKubernetes) {
// Not used in k8s for now
return {};
}
const resources = await this.resourceMonitor.getNodeResources();
return {
maxResources: {
cpu: resources.cpuAvailable,
@@ -141,7 +159,7 @@ class ManagedSupervisor {
});
if (env.TRIGGER_CHECKPOINT_URL) {
this.logger.log("[ManagedWorker] 🥶 Checkpoints enabled", {
this.logger.log("🥶 Checkpoints enabled", {
checkpointUrl: env.TRIGGER_CHECKPOINT_URL,
});
@@ -152,43 +170,34 @@ class ManagedSupervisor {
});
}
// setInterval(async () => {
// const resources = await this.resourceMonitor.getNodeResources(true);
// this.logger.debug("[ManagedWorker] Current resources", { resources });
// }, 1000);
this.workerSession.on("runNotification", async ({ time, run }) => {
this.logger.log("[ManagedWorker] runNotification", { time, run });
this.logger.log("runNotification", { time, run });
this.workloadServer.notifyRun({ run });
});
this.workerSession.on("runQueueMessage", async ({ time, message }) => {
this.logger.log(
`[ManagedWorker] Received message with timestamp ${time.toLocaleString()}`,
message
);
this.logger.log(`Received message with timestamp ${time.toLocaleString()}`, message);
if (message.completedWaitpoints.length > 0) {
this.logger.debug("[ManagedWorker] Run has completed waitpoints", {
this.logger.debug("Run has completed waitpoints", {
runId: message.run.id,
completedWaitpoints: message.completedWaitpoints.length,
});
// TODO: Do something with them or if we don't need the data here, maybe we shouldn't even send it
}
if (!message.image) {
this.logger.error("[ManagedWorker] Run has no image", { runId: message.run.id });
this.logger.error("Run has no image", { runId: message.run.id });
return;
}
const { checkpoint, ...rest } = message;
if (checkpoint) {
this.logger.log("[ManagedWorker] Restoring run", { runId: message.run.id });
this.logger.log("Restoring run", { runId: message.run.id });
if (!this.checkpointClient) {
this.logger.error("[ManagedWorker] No checkpoint client", { runId: message.run.id });
this.logger.error("No checkpoint client", { runId: message.run.id });
return;
}
@@ -203,23 +212,23 @@ class ManagedSupervisor {
});
if (didRestore) {
this.logger.log("[ManagedWorker] Restore successful", { runId: message.run.id });
this.logger.log("Restore successful", { runId: message.run.id });
} else {
this.logger.error("[ManagedWorker] Restore failed", { runId: message.run.id });
this.logger.error("Restore failed", { runId: message.run.id });
}
} catch (error) {
this.logger.error("[ManagedWorker] Failed to restore run", { error });
this.logger.error("Failed to restore run", { error });
}
return;
}
this.logger.log("[ManagedWorker] Scheduling run", { runId: message.run.id });
this.logger.log("Scheduling run", { runId: message.run.id });
const didWarmStart = await this.tryWarmStart(message);
if (didWarmStart) {
this.logger.log("[ManagedWorker] Warm start successful", { runId: message.run.id });
this.logger.log("Warm start successful", { runId: message.run.id });
return;
}
@@ -238,6 +247,7 @@ class ManagedSupervisor {
nextAttemptNumber: message.run.attemptNumber,
snapshotId: message.snapshot.id,
snapshotFriendlyId: message.snapshot.friendlyId,
placementTags: message.placementTags,
});
// Disabled for now
@@ -246,7 +256,7 @@ class ManagedSupervisor {
// memory: message.run.machine.memory,
// });
} catch (error) {
this.logger.error("[ManagedWorker] Failed to create workload", { error });
this.logger.error("Failed to create workload", { error });
}
});
@@ -274,12 +284,12 @@ class ManagedSupervisor {
}
async onRunConnected({ run }: { run: { friendlyId: string } }) {
this.logger.debug("[ManagedWorker] Run connected", { run });
this.logger.debug("Run connected", { run });
this.workerSession.subscribeToRunNotifications([run.friendlyId]);
}
async onRunDisconnected({ run }: { run: { friendlyId: string } }) {
this.logger.debug("[ManagedWorker] Run disconnected", { run });
this.logger.debug("Run disconnected", { run });
this.workerSession.unsubscribeFromRunNotifications([run.friendlyId]);
}
@@ -300,7 +310,7 @@ class ManagedSupervisor {
});
if (!res.ok) {
this.logger.error("[ManagedWorker] Warm start failed", {
this.logger.error("Warm start failed", {
runId: dequeuedMessage.run.id,
});
return false;
@@ -310,7 +320,7 @@ class ManagedSupervisor {
const parsedData = z.object({ didWarmStart: z.boolean() }).safeParse(data);
if (!parsedData.success) {
this.logger.error("[ManagedWorker] Warm start response invalid", {
this.logger.error("Warm start response invalid", {
runId: dequeuedMessage.run.id,
data,
});
@@ -319,7 +329,7 @@ class ManagedSupervisor {
return parsedData.data.didWarmStart;
} catch (error) {
this.logger.error("[ManagedWorker] Warm start error", {
this.logger.error("Warm start error", {
runId: dequeuedMessage.run.id,
error,
});
@@ -328,7 +338,7 @@ class ManagedSupervisor {
}
async start() {
this.logger.log("[ManagedWorker] Starting up");
this.logger.log("Starting up");
// Optional services
await this.podCleaner?.start();
@@ -336,21 +346,21 @@ class ManagedSupervisor {
await this.metricsServer?.start();
if (env.TRIGGER_WORKLOAD_API_ENABLED) {
this.logger.log("[ManagedWorker] Workload API enabled", {
this.logger.log("Workload API enabled", {
protocol: env.TRIGGER_WORKLOAD_API_PROTOCOL,
domain: env.TRIGGER_WORKLOAD_API_DOMAIN,
port: env.TRIGGER_WORKLOAD_API_PORT_INTERNAL,
});
await this.workloadServer.start();
} else {
this.logger.warn("[ManagedWorker] Workload API disabled");
this.logger.warn("Workload API disabled");
}
await this.workerSession.start();
}
async stop() {
this.logger.log("[ManagedWorker] Shutting down");
this.logger.log("Shutting down");
await this.workerSession.stop();
// Optional services
+40 -8
View File
@@ -1,5 +1,4 @@
import type Docker from "dockerode";
import type * as TDocker from "docker-api-ts";
import type { MachineResources } from "@trigger.dev/core/v3";
import { SimpleStructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import { env } from "./env.js";
@@ -71,18 +70,21 @@ export abstract class ResourceMonitor {
}
protected applyOverrides(resources: NodeResources): NodeResources {
if (!env.OVERRIDE_CPU_TOTAL && !env.OVERRIDE_MEMORY_TOTAL_GB) {
if (
!env.RESOURCE_MONITOR_OVERRIDE_CPU_TOTAL &&
!env.RESOURCE_MONITOR_OVERRIDE_MEMORY_TOTAL_GB
) {
return resources;
}
logger.debug("[ResourceMonitor] 🛡️ Applying resource overrides", {
cpuTotal: env.OVERRIDE_CPU_TOTAL,
memoryTotalGb: env.OVERRIDE_MEMORY_TOTAL_GB,
cpuTotal: env.RESOURCE_MONITOR_OVERRIDE_CPU_TOTAL,
memoryTotalGb: env.RESOURCE_MONITOR_OVERRIDE_MEMORY_TOTAL_GB,
});
const cpuTotal = env.OVERRIDE_CPU_TOTAL ?? resources.cpuTotal;
const memoryTotal = env.OVERRIDE_MEMORY_TOTAL_GB
? this.gbToBytes(env.OVERRIDE_MEMORY_TOTAL_GB)
const cpuTotal = env.RESOURCE_MONITOR_OVERRIDE_CPU_TOTAL ?? resources.cpuTotal;
const memoryTotal = env.RESOURCE_MONITOR_OVERRIDE_MEMORY_TOTAL_GB
? this.gbToBytes(env.RESOURCE_MONITOR_OVERRIDE_MEMORY_TOTAL_GB)
: resources.memoryTotal;
const cpuDiff = cpuTotal - resources.cpuTotal;
@@ -100,6 +102,11 @@ export abstract class ResourceMonitor {
}
}
type SystemInfo = {
NCPU: number | undefined;
MemTotal: number | undefined;
};
export class DockerResourceMonitor extends ResourceMonitor {
private docker: Docker;
@@ -114,7 +121,7 @@ export class DockerResourceMonitor extends ResourceMonitor {
return this.cachedResources;
}
const info: TDocker.SystemInfo = await this.docker.info();
const info: SystemInfo = await this.docker.info();
const stats = await this.docker.listContainers({ all: true });
// Get system-wide resources
@@ -208,6 +215,21 @@ export class KubernetesResourceMonitor extends ResourceMonitor {
}
}
export class NoopResourceMonitor extends ResourceMonitor {
constructor() {
super(NoopResourceParser);
}
async getNodeResources(): Promise<NodeResources> {
return {
cpuTotal: 0,
cpuAvailable: Infinity,
memoryTotal: 0,
memoryAvailable: Infinity,
};
}
}
abstract class ResourceParser {
abstract cpu(cpu: number | string): number;
abstract memory(memory: number | string): number;
@@ -244,3 +266,13 @@ class KubernetesResourceParser extends ResourceParser {
return parseInt(memory);
}
}
class NoopResourceParser extends ResourceParser {
cpu(cpu: number): number {
return cpu;
}
memory(memory: number): number {
return memory;
}
}
+13 -4
View File
@@ -1,8 +1,17 @@
export function getDockerHostDomain() {
const isMacOs = process.platform === "darwin";
const isWindows = process.platform === "win32";
import { isMacOS, isWindows } from "std-env";
return isMacOs || isWindows ? "host.docker.internal" : "localhost";
export function normalizeDockerHostUrl(url: string) {
const $url = new URL(url);
if ($url.hostname === "localhost") {
$url.hostname = getDockerHostDomain();
}
return $url.toString();
}
export function getDockerHostDomain() {
return isMacOS || isWindows ? "host.docker.internal" : "localhost";
}
export function getRunnerId(runId: string, attemptNumber?: number) {
+29
View File
@@ -0,0 +1,29 @@
import { readFileSync } from "fs";
import { env } from "./env.js";
export function getWorkerToken() {
if (!env.TRIGGER_WORKER_TOKEN.startsWith("file://")) {
return env.TRIGGER_WORKER_TOKEN;
}
const tokenPath = env.TRIGGER_WORKER_TOKEN.replace("file://", "");
console.debug(
JSON.stringify({
message: "🔑 Reading worker token from file",
tokenPath,
})
);
try {
const token = readFileSync(tokenPath, "utf8").trim();
return token;
} catch (error) {
console.error(`Failed to read worker token from file: ${tokenPath}`, error);
throw new Error(
`Unable to read worker token from file: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
}
+225 -44
View File
@@ -4,88 +4,269 @@ import {
type WorkloadManagerCreateOptions,
type WorkloadManagerOptions,
} from "./types.js";
import { x } from "tinyexec";
import { env } from "../env.js";
import { getDockerHostDomain, getRunnerId } from "../util.js";
import { getDockerHostDomain, getRunnerId, normalizeDockerHostUrl } from "../util.js";
import Docker from "dockerode";
import { tryCatch } from "@trigger.dev/core";
export class DockerWorkloadManager implements WorkloadManager {
private readonly logger = new SimpleStructuredLogger("docker-workload-provider");
private readonly logger = new SimpleStructuredLogger("docker-workload-manager");
private readonly docker: Docker;
private readonly runnerNetworks: string[];
private readonly auth?: Docker.AuthConfig;
private readonly platformOverride?: string;
constructor(private opts: WorkloadManagerOptions) {
this.docker = new Docker({
version: env.DOCKER_API_VERSION,
});
if (opts.workloadApiDomain) {
this.logger.warn("[DockerWorkloadProvider] ⚠️ Custom workload API domain", {
this.logger.warn("⚠️ Custom workload API domain", {
domain: opts.workloadApiDomain,
});
}
this.runnerNetworks = env.DOCKER_RUNNER_NETWORKS.split(",");
this.platformOverride = env.DOCKER_PLATFORM;
if (this.platformOverride) {
this.logger.info("🖥️ Platform override", {
targetPlatform: this.platformOverride,
hostPlatform: process.arch,
});
}
if (env.DOCKER_REGISTRY_USERNAME && env.DOCKER_REGISTRY_PASSWORD && env.DOCKER_REGISTRY_URL) {
this.logger.info("🐋 Using Docker registry credentials", {
username: env.DOCKER_REGISTRY_USERNAME,
url: env.DOCKER_REGISTRY_URL,
});
this.auth = {
username: env.DOCKER_REGISTRY_USERNAME,
password: env.DOCKER_REGISTRY_PASSWORD,
serveraddress: env.DOCKER_REGISTRY_URL,
};
} else {
this.logger.warn("🐋 No Docker registry credentials provided, skipping auth");
}
}
async create(opts: WorkloadManagerCreateOptions) {
this.logger.log("[DockerWorkloadProvider] Creating container", { opts });
this.logger.log("create()", { opts });
const runnerId = getRunnerId(opts.runFriendlyId, opts.nextAttemptNumber);
const runArgs = [
"run",
"--detach",
`--network=${env.DOCKER_NETWORK}`,
`--env=TRIGGER_DEQUEUED_AT_MS=${opts.dequeuedAt.getTime()}`,
`--env=TRIGGER_POD_SCHEDULED_AT_MS=${Date.now()}`,
`--env=TRIGGER_ENV_ID=${opts.envId}`,
`--env=TRIGGER_RUN_ID=${opts.runFriendlyId}`,
`--env=TRIGGER_SNAPSHOT_ID=${opts.snapshotFriendlyId}`,
`--env=TRIGGER_SUPERVISOR_API_PROTOCOL=${this.opts.workloadApiProtocol}`,
`--env=TRIGGER_SUPERVISOR_API_PORT=${this.opts.workloadApiPort}`,
`--env=TRIGGER_SUPERVISOR_API_DOMAIN=${this.opts.workloadApiDomain ?? getDockerHostDomain()}`,
`--env=TRIGGER_WORKER_INSTANCE_NAME=${env.TRIGGER_WORKER_INSTANCE_NAME}`,
`--env=OTEL_EXPORTER_OTLP_ENDPOINT=${env.OTEL_EXPORTER_OTLP_ENDPOINT}`,
`--env=TRIGGER_RUNNER_ID=${runnerId}`,
`--hostname=${runnerId}`,
`--name=${runnerId}`,
// Build environment variables
const envVars: string[] = [
`OTEL_EXPORTER_OTLP_ENDPOINT=${env.OTEL_EXPORTER_OTLP_ENDPOINT}`,
`TRIGGER_DEQUEUED_AT_MS=${opts.dequeuedAt.getTime()}`,
`TRIGGER_POD_SCHEDULED_AT_MS=${Date.now()}`,
`TRIGGER_ENV_ID=${opts.envId}`,
`TRIGGER_RUN_ID=${opts.runFriendlyId}`,
`TRIGGER_SNAPSHOT_ID=${opts.snapshotFriendlyId}`,
`TRIGGER_SUPERVISOR_API_PROTOCOL=${this.opts.workloadApiProtocol}`,
`TRIGGER_SUPERVISOR_API_PORT=${this.opts.workloadApiPort}`,
`TRIGGER_SUPERVISOR_API_DOMAIN=${this.opts.workloadApiDomain ?? getDockerHostDomain()}`,
`TRIGGER_WORKER_INSTANCE_NAME=${env.TRIGGER_WORKER_INSTANCE_NAME}`,
`TRIGGER_RUNNER_ID=${runnerId}`,
`TRIGGER_MACHINE_CPU=${opts.machine.cpu}`,
`TRIGGER_MACHINE_MEMORY=${opts.machine.memory}`,
`PRETTY_LOGS=${env.RUNNER_PRETTY_LOGS}`,
];
if (this.opts.dockerAutoremove) {
runArgs.push("--rm");
}
if (this.opts.warmStartUrl) {
runArgs.push(`--env=TRIGGER_WARM_START_URL=${this.opts.warmStartUrl}`);
envVars.push(`TRIGGER_WARM_START_URL=${normalizeDockerHostUrl(this.opts.warmStartUrl)}`);
}
if (this.opts.metadataUrl) {
runArgs.push(`--env=TRIGGER_METADATA_URL=${this.opts.metadataUrl}`);
envVars.push(`TRIGGER_METADATA_URL=${this.opts.metadataUrl}`);
}
if (this.opts.heartbeatIntervalSeconds) {
runArgs.push(
`--env=TRIGGER_HEARTBEAT_INTERVAL_SECONDS=${this.opts.heartbeatIntervalSeconds}`
);
envVars.push(`TRIGGER_HEARTBEAT_INTERVAL_SECONDS=${this.opts.heartbeatIntervalSeconds}`);
}
if (this.opts.snapshotPollIntervalSeconds) {
runArgs.push(
`--env=TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS=${this.opts.snapshotPollIntervalSeconds}`
envVars.push(
`TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS=${this.opts.snapshotPollIntervalSeconds}`
);
}
if (this.opts.additionalEnvVars) {
Object.entries(this.opts.additionalEnvVars).forEach(([key, value]) => {
runArgs.push(`--env=${key}=${value}`);
envVars.push(`${key}=${value}`);
});
}
if (env.ENFORCE_MACHINE_PRESETS) {
runArgs.push(`--cpus=${opts.machine.cpu}`, `--memory=${opts.machine.memory}G`);
runArgs.push(`--env=TRIGGER_MACHINE_CPU=${opts.machine.cpu}`);
runArgs.push(`--env=TRIGGER_MACHINE_MEMORY=${opts.machine.memory}`);
const hostConfig: Docker.HostConfig = {
AutoRemove: !!this.opts.dockerAutoremove,
};
const [firstNetwork, ...remainingNetworks] = this.runnerNetworks;
// Always attach the first network at container creation time. This has the following benefits:
// - If there is only a single network to attach, this will prevent having to make a separate request.
// - If there are multiple networks to attach, this will ensure the runner won't also be connected to the bridge network
hostConfig.NetworkMode = firstNetwork;
if (env.DOCKER_ENFORCE_MACHINE_PRESETS) {
hostConfig.NanoCpus = opts.machine.cpu * 1e9;
hostConfig.Memory = opts.machine.memory * 1024 * 1024 * 1024;
}
runArgs.push(`${opts.image}`);
let imageRef = opts.image;
try {
const { stdout, stderr } = await x("docker", runArgs);
this.logger.debug("[DockerWorkloadProvider] Create succeeded", { stdout, stderr });
} catch (error) {
this.logger.error("[DockerWorkloadProvider] Create failed:", { opts, error });
if (env.DOCKER_STRIP_IMAGE_DIGEST) {
imageRef = opts.image.split("@")[0]!;
}
const containerCreateOpts: Docker.ContainerCreateOptions = {
name: runnerId,
Hostname: runnerId,
HostConfig: hostConfig,
Image: imageRef,
AttachStdout: false,
AttachStderr: false,
AttachStdin: false,
};
if (this.platformOverride) {
containerCreateOpts.platform = this.platformOverride;
}
const logger = this.logger.child({ opts, containerCreateOpts });
const [inspectError, inspectResult] = await tryCatch(this.docker.getImage(imageRef).inspect());
let shouldPull = !!inspectError;
if (this.platformOverride) {
const imageArchitecture = inspectResult?.Architecture;
// When the image architecture doesn't match the platform, we need to pull the image
if (imageArchitecture && !this.platformOverride.includes(imageArchitecture)) {
shouldPull = true;
}
}
// If the image is not present, try to pull it
if (shouldPull) {
logger.info("Pulling image", {
error: inspectError,
image: opts.image,
targetPlatform: this.platformOverride,
imageArchitecture: inspectResult?.Architecture,
});
// Ensure the image is present
const [createImageError, imageResponseReader] = await tryCatch(
this.docker.createImage(this.auth, {
fromImage: imageRef,
...(this.platformOverride ? { platform: this.platformOverride } : {}),
})
);
if (createImageError) {
logger.error("Failed to pull image", { error: createImageError });
return;
}
const [imageReadError, imageResponse] = await tryCatch(readAllChunks(imageResponseReader));
if (imageReadError) {
logger.error("failed to read image response", { error: imageReadError });
return;
}
logger.debug("pulled image", { image: opts.image, imageResponse });
} else {
// Image is present, so we can use it to create the container
}
// Create container
const [createContainerError, container] = await tryCatch(
this.docker.createContainer({
...containerCreateOpts,
// Add env vars here so they're not logged
Env: envVars,
})
);
if (createContainerError) {
logger.error("Failed to create container", { error: createContainerError });
return;
}
// If there are multiple networks to attach to we need to attach the remaining ones after creation
if (remainingNetworks.length > 0) {
await this.attachContainerToNetworks({
containerId: container.id,
networkNames: remainingNetworks,
});
}
// Start container
const [startError, startResult] = await tryCatch(container.start());
if (startError) {
logger.error("Failed to start container", { error: startError, containerId: container.id });
return;
}
logger.debug("create succeeded", { startResult, containerId: container.id });
}
private async attachContainerToNetworks({
containerId,
networkNames,
}: {
containerId: string;
networkNames: string[];
}) {
this.logger.debug("Attaching container to networks", { containerId, networkNames });
const [error, networkResults] = await tryCatch(
this.docker.listNetworks({
filters: {
// Full name matches only to prevent unexpected results
name: networkNames.map((name) => `^${name}$`),
},
})
);
if (error) {
this.logger.error("Failed to list networks", { networkNames });
return;
}
const results = await Promise.allSettled(
networkResults.map((networkInfo) => {
const network = this.docker.getNetwork(networkInfo.Id);
return network.connect({ Container: containerId });
})
);
if (results.some((r) => r.status === "rejected")) {
this.logger.error("Failed to attach container to some networks", {
containerId,
networkNames,
results,
});
return;
}
this.logger.debug("Attached container to networks", {
containerId,
networkNames,
results,
});
}
}
async function readAllChunks(reader: NodeJS.ReadableStream) {
const chunks = [];
for await (const chunk of reader) {
chunks.push(chunk.toString());
}
return chunks;
}
@@ -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,21 @@ export class KubernetesWorkloadManager implements WorkloadManager {
}
}
private addPlacementTags(
podSpec: Omit<k8s.V1PodSpec, "containers">,
placementTags?: PlacementTag[]
): Omit<k8s.V1PodSpec, "containers"> {
const nodeSelector = this.placementTagProcessor.convertToNodeSelector(
placementTags,
podSpec.nodeSelector
);
return {
...podSpec,
nodeSelector,
};
}
async create(opts: WorkloadManagerCreateOptions) {
this.logger.log("[KubernetesWorkloadManager] Creating container", { opts });
@@ -48,7 +69,7 @@ export class KubernetesWorkloadManager implements WorkloadManager {
},
},
spec: {
...this.#defaultPodSpec,
...this.addPlacementTags(this.#defaultPodSpec, opts.placementTags),
terminationGracePeriodSeconds: 60 * 60,
containers: [
{
@@ -228,21 +249,25 @@ export class KubernetesWorkloadManager implements WorkloadManager {
restartPolicy: "Never",
automountServiceAccountToken: false,
imagePullSecrets: this.getImagePullSecrets(),
nodeSelector: {
nodetype: env.KUBERNETES_WORKER_NODETYPE_LABEL,
},
...(env.KUBERNETES_WORKER_NODETYPE_LABEL
? {
nodeSelector: {
nodetype: env.KUBERNETES_WORKER_NODETYPE_LABEL,
},
}
: {}),
};
}
get #defaultResourceRequests(): ResourceQuantities {
return {
"ephemeral-storage": env.EPHEMERAL_STORAGE_SIZE_REQUEST,
"ephemeral-storage": env.KUBERNETES_EPHEMERAL_STORAGE_SIZE_REQUEST,
};
}
get #defaultResourceLimits(): ResourceQuantities {
return {
"ephemeral-storage": env.EPHEMERAL_STORAGE_SIZE_LIMIT,
"ephemeral-storage": env.KUBERNETES_EPHEMERAL_STORAGE_SIZE_LIMIT,
};
}
+2 -1
View File
@@ -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;
+63 -32
View File
@@ -17,12 +17,14 @@ import {
WorkloadRunAttemptStartRequestBody,
type WorkloadRunAttemptStartResponseBody,
type WorkloadRunLatestSnapshotResponseBody,
WorkloadRunSnapshotsSinceResponseBody,
type WorkloadServerToClientEvents,
type WorkloadSuspendRunResponseBody,
} from "@trigger.dev/core/v3/workers";
import { HttpServer, type CheckpointClient } from "@trigger.dev/core/v3/serverOnly";
import { type IncomingMessage } from "node:http";
import { register } from "../metrics.js";
import { env } from "../env.js";
// Use the official export when upgrading to socket.io@4.8.0
interface DefaultEventsMap {
@@ -62,6 +64,8 @@ type WorkloadServerOptions = {
export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
private checkpointClient?: CheckpointClient;
private readonly logger = new SimpleStructuredLogger("workload-server");
private readonly httpServer: HttpServer;
private readonly websocketServer: Namespace<
WorkloadClientToServerEvents,
@@ -150,7 +154,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
);
if (!startResponse.success) {
console.error("Failed to start run", {
this.logger.error("Failed to start run", {
params,
error: startResponse.error,
});
@@ -170,7 +174,6 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
paramsSchema: WorkloadActionParams,
bodySchema: WorkloadRunAttemptCompleteRequestBody,
handler: async ({ req, reply, params, body }) => {
console.log("headers", req.headers);
const completeResponse = await this.workerClient.completeRunAttempt(
params.runFriendlyId,
params.snapshotFriendlyId,
@@ -179,7 +182,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
);
if (!completeResponse.success) {
console.error("Failed to complete run", {
this.logger.error("Failed to complete run", {
params,
error: completeResponse.error,
});
@@ -207,7 +210,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
);
if (!heartbeatResponse.success) {
console.error("Failed to heartbeat run", {
this.logger.error("Failed to heartbeat run", {
params,
error: heartbeatResponse.error,
});
@@ -227,7 +230,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
{
paramsSchema: WorkloadActionParams,
handler: async ({ reply, params, req }) => {
console.debug("Suspend request", { params, headers: req.headers });
this.logger.debug("Suspend request", { params, headers: req.headers });
if (!this.checkpointClient) {
reply.json(
@@ -246,7 +249,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
const projectRef = this.projectRefFromRequest(req);
if (!runnerId || !deploymentVersion || !projectRef) {
console.error("Invalid headers for suspend request", {
this.logger.error("Invalid headers for suspend request", {
...params,
headers: req.headers,
});
@@ -282,7 +285,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
});
if (!suspendResult) {
console.error("Failed to suspend run", { params });
this.logger.error("Failed to suspend run", { params });
return;
}
},
@@ -294,7 +297,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
{
paramsSchema: WorkloadActionParams,
handler: async ({ req, reply, params }) => {
console.debug("Run continuation request", { params });
this.logger.debug("Run continuation request", { params });
const continuationResult = await this.workerClient.continueRunExecution(
params.runFriendlyId,
@@ -303,7 +306,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
);
if (!continuationResult.success) {
console.error("Failed to continue run execution", { params });
this.logger.error("Failed to continue run execution", { params });
reply.json(
{
ok: false,
@@ -328,7 +331,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
);
if (!latestSnapshotResponse.success) {
console.error("Failed to get latest snapshot", {
this.logger.error("Failed to get latest snapshot", {
runId: params.runFriendlyId,
error: latestSnapshotResponse.error,
});
@@ -341,12 +344,41 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
} satisfies WorkloadRunLatestSnapshotResponseBody);
},
})
.route(
"/api/v1/workload-actions/runs/:runFriendlyId/snapshots/since/:snapshotFriendlyId",
"GET",
{
paramsSchema: WorkloadActionParams,
handler: async ({ req, reply, params }) => {
const sinceSnapshotResponse = await this.workerClient.getSnapshotsSince(
params.runFriendlyId,
params.snapshotFriendlyId,
this.runnerIdFromRequest(req)
);
if (!sinceSnapshotResponse.success) {
this.logger.error("Failed to get snapshots since", {
runId: params.runFriendlyId,
error: sinceSnapshotResponse.error,
});
reply.empty(500);
return;
}
reply.json(sinceSnapshotResponse.data satisfies WorkloadRunSnapshotsSinceResponseBody);
},
}
)
.route("/api/v1/workload-actions/runs/:runFriendlyId/logs/debug", "POST", {
paramsSchema: WorkloadActionParams.pick({ runFriendlyId: true }),
bodySchema: WorkloadDebugLogRequestBody,
handler: async ({ req, reply, params, body }) => {
reply.empty(204);
if (!env.SEND_RUN_DEBUG_LOGS) {
return;
}
await this.workerClient.sendDebugLog(
params.runFriendlyId,
body,
@@ -367,7 +399,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
);
if (!dequeueResponse.success) {
console.error("Failed to get latest snapshot", {
this.logger.error("Failed to get latest snapshot", {
deploymentId: params.deploymentId,
error: dequeueResponse.error,
});
@@ -391,14 +423,14 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
> = io.of("/workload");
websocketServer.on("disconnect", (socket) => {
console.log("[WorkloadSocket] disconnect", socket.id);
this.logger.log("[WS] disconnect", socket.id);
});
websocketServer.use(async (socket, next) => {
function setSocketDataFromHeader(
const setSocketDataFromHeader = (
dataKey: keyof typeof socket.data,
headerName: string,
required: boolean = true
) {
) => {
const value = socket.handshake.headers[headerName];
if (value) {
@@ -414,27 +446,26 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
}
if (required) {
console.error("[WorkloadSocket] missing required header", { headerName });
this.logger.error("[WS] missing required header", { headerName });
throw new Error("missing header");
}
}
};
try {
setSocketDataFromHeader("deploymentId", WORKLOAD_HEADERS.DEPLOYMENT_ID);
setSocketDataFromHeader("runnerId", WORKLOAD_HEADERS.RUNNER_ID);
} catch (error) {
console.error("[WorkloadSocket] setSocketDataFromHeader error", { error });
this.logger.error("[WS] setSocketDataFromHeader error", { error });
socket.disconnect(true);
return;
}
console.debug("[WorkloadSocket] auth success", socket.data);
this.logger.debug("[WS] auth success", socket.data);
next();
});
websocketServer.on("connection", (socket) => {
const logger = new SimpleStructuredLogger("workload-namespace", undefined, {
namespace: "workload",
const socketLogger = this.logger.child({
socketId: socket.id,
socketData: socket.data,
});
@@ -449,11 +480,11 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
};
const runConnected = (friendlyId: string) => {
logger.debug("runConnected", { ...getSocketMetadata() });
socketLogger.debug("runConnected", { ...getSocketMetadata() });
// If there's already a run ID set, we should "disconnect" it from this socket
if (socket.data.runFriendlyId && socket.data.runFriendlyId !== friendlyId) {
logger.debug("runConnected: disconnecting existing run", {
socketLogger.debug("runConnected: disconnecting existing run", {
...getSocketMetadata(),
newRunId: friendlyId,
oldRunId: socket.data.runFriendlyId,
@@ -467,14 +498,14 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
};
const runDisconnected = (friendlyId: string) => {
logger.debug("runDisconnected", { ...getSocketMetadata() });
socketLogger.debug("runDisconnected", { ...getSocketMetadata() });
this.runSockets.delete(friendlyId);
this.emit("runDisconnected", { run: { friendlyId } });
socket.data.runFriendlyId = undefined;
};
logger.log("wsServer socket connected", { ...getSocketMetadata() });
socketLogger.log("wsServer socket connected", { ...getSocketMetadata() });
// FIXME: where does this get set?
if (socket.data.runFriendlyId) {
@@ -482,7 +513,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
}
socket.on("disconnecting", (reason, description) => {
logger.log("Socket disconnecting", { ...getSocketMetadata(), reason, description });
socketLogger.log("Socket disconnecting", { ...getSocketMetadata(), reason, description });
if (socket.data.runFriendlyId) {
runDisconnected(socket.data.runFriendlyId);
@@ -490,11 +521,11 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
});
socket.on("disconnect", (reason, description) => {
logger.log("Socket disconnected", { ...getSocketMetadata(), reason, description });
socketLogger.log("Socket disconnected", { ...getSocketMetadata(), reason, description });
});
socket.on("error", (error) => {
logger.error("Socket error", {
socketLogger.error("Socket error", {
...getSocketMetadata(),
error: {
name: error.name,
@@ -505,7 +536,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
});
socket.on("run:start", async (message) => {
const log = logger.child({
const log = socketLogger.child({
eventName: "run:start",
...getSocketMetadata(),
...message,
@@ -521,7 +552,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
});
socket.on("run:stop", async (message) => {
const log = logger.child({
const log = socketLogger.child({
eventName: "run:stop",
...getSocketMetadata(),
...message,
@@ -545,7 +576,7 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
const runSocket = this.runSockets.get(run.friendlyId);
if (!runSocket) {
console.debug("[WorkloadServer] notifyRun: Run socket not found", { run });
this.logger.debug("notifyRun: Run socket not found", { run });
this.workerClient.sendDebugLog(run.friendlyId, {
time: new Date(),
@@ -556,14 +587,14 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
}
runSocket.emit("run:notify", { version: "1", run });
console.debug("[WorkloadServer] run:notify sent", { run });
this.logger.debug("run:notify sent", { run });
this.workerClient.sendDebugLog(run.friendlyId, {
time: new Date(),
message: "run:notify supervisor -> runner",
});
} catch (error) {
console.error("[WorkloadServer] Error in notifyRun", { run, error });
this.logger.error("Error in notifyRun", { run, error });
this.workerClient.sendDebugLog(run.friendlyId, {
time: new Date(),
-16
View File
@@ -1,16 +0,0 @@
{
"sourceType": "unambiguous",
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": 100
}
}
],
"@babel/preset-typescript",
"@babel/preset-react"
],
"plugins": []
}
+2 -1
View File
@@ -18,4 +18,5 @@ build-storybook.log
storybook-static
/prisma/seed.js
/prisma/populate.js
/prisma/populate.js
.memory-snapshots
+57
View File
@@ -0,0 +1,57 @@
import {
API_VERSION_HEADER_NAME,
API_VERSION as CORE_API_VERSION,
} from "@trigger.dev/core/v3/serverOnly";
import { z } from "zod";
export const CURRENT_API_VERSION = CORE_API_VERSION;
export const NON_SPECIFIC_API_VERSION = "none";
export type API_VERSIONS = typeof CURRENT_API_VERSION | typeof NON_SPECIFIC_API_VERSION;
export function getApiVersion(request: Request): API_VERSIONS {
const apiVersion = request.headers.get(API_VERSION_HEADER_NAME);
if (apiVersion === CURRENT_API_VERSION) {
return apiVersion;
}
return NON_SPECIFIC_API_VERSION;
}
// This has been copied from the core package to allow us to use these types in the webapp
export const RunStatusUnspecifiedApiVersion = z.enum([
/// Task is waiting for a version update because it cannot execute without additional information (task, queue, etc.). Replaces WAITING_FOR_DEPLOY
"PENDING_VERSION",
/// Task hasn't been deployed yet but is waiting to be executed
"WAITING_FOR_DEPLOY",
/// Task is waiting to be executed by a worker
"QUEUED",
/// Task is currently being executed by a worker
"EXECUTING",
/// Task has failed and is waiting to be retried
"REATTEMPTING",
/// Task has been paused by the system, and will be resumed by the system
"FROZEN",
/// Task has been completed successfully
"COMPLETED",
/// Task has been canceled by the user
"CANCELED",
/// Task has been completed with errors
"FAILED",
/// Task has crashed and won't be retried, most likely the worker ran out of resources, e.g. memory or storage
"CRASHED",
/// Task was interrupted during execution, mostly this happens in development environments
"INTERRUPTED",
/// Task has failed to complete, due to an error in the system
"SYSTEM_FAILURE",
/// Task has been scheduled to run at a specific time
"DELAYED",
/// Task has expired and won't be executed
"EXPIRED",
/// Task has reached it's maxDuration and has been stopped
"TIMED_OUT",
]);
export type RunStatusUnspecifiedApiVersion = z.infer<typeof RunStatusUnspecifiedApiVersion>;
+20 -42
View File
@@ -1,53 +1,31 @@
export function AISparkleIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
className={className}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.9806 0.803884C14.8871 0.33646 14.4767 0 14 0C13.5233 0 13.1129 0.33646 13.0194 0.803884L12.7809 1.99644C12.7017 2.3923 12.3923 2.70174 11.9964 2.78091L10.8039 3.01942C10.3365 3.1129 10 3.52332 10 4C10 4.47668 10.3365 4.8871 10.8039 4.98058L11.9964 5.21909C12.3923 5.29826 12.7017 5.6077 12.7809 6.00356L13.0194 7.19612C13.1129 7.66354 13.5233 8 14 8C14.4767 8 14.8871 7.66354 14.9806 7.19612L15.2191 6.00356C15.2983 5.6077 15.6077 5.29826 16.0036 5.21909L17.1961 4.98058C17.6635 4.8871 18 4.47668 18 4C18 3.52332 17.6635 3.1129 17.1961 3.01942L16.0036 2.78091C15.6077 2.70174 15.2983 2.3923 15.2191 1.99644L14.9806 0.803884Z"
fill="url(#paint0_linear_11402_36656)"
fillRule="evenodd"
clipRule="evenodd"
d="M9.14286 4.85718C9.46177 4.85718 9.74205 5.06859 9.82966 5.37523L10.6041 8.08589C10.9431 9.27235 11.8705 10.1998 13.057 10.5388L15.7677 11.3132C16.0743 11.4008 16.2857 11.6811 16.2857 12C16.2857 12.319 16.0743 12.5992 15.7677 12.6868L13.057 13.4613C11.8705 13.8003 10.9431 14.7277 10.6041 15.9142L9.82966 18.6248C9.74205 18.9315 9.46177 19.1429 9.14286 19.1429C8.82394 19.1429 8.54367 18.9315 8.45605 18.6248L7.68158 15.9142C7.34259 14.7277 6.41517 13.8003 5.22871 13.4613L2.51806 12.6868C2.21141 12.5992 2 12.319 2 12C2 11.6811 2.21141 11.4008 2.51806 11.3132L5.22871 10.5388C6.41517 10.1998 7.34259 9.27235 7.68158 8.08589L8.45605 5.37523C8.54367 5.06859 8.82394 4.85718 9.14286 4.85718Z"
fill="#7655FD"
/>
<path
d="M5.94868 4.68377C5.81257 4.27543 5.43043 4 5 4C4.56957 4 4.18743 4.27543 4.05132 4.68377L3.36754 6.73509C3.26801 7.03369 3.03369 7.26801 2.73509 7.36754L0.683772 8.05132C0.27543 8.18743 0 8.56957 0 9C0 9.43043 0.27543 9.81257 0.683772 9.94868L2.73509 10.6325C3.03369 10.732 3.26801 10.9663 3.36754 11.2649L4.05132 13.3162C4.18743 13.7246 4.56957 14 5 14C5.43043 14 5.81257 13.7246 5.94868 13.3162L6.63246 11.2649C6.73199 10.9663 6.96631 10.732 7.26491 10.6325L9.31623 9.94868C9.72457 9.81257 10 9.43043 10 9C10 8.56957 9.72457 8.18743 9.31623 8.05132L7.26491 7.36754C6.96631 7.26801 6.73199 7.03369 6.63246 6.73509L5.94868 4.68377Z"
fill="url(#paint1_linear_11402_36656)"
fillRule="evenodd"
clipRule="evenodd"
d="M17.7143 2C18.0421 2 18.3278 2.22307 18.4072 2.54105L18.6538 3.5272C18.8777 4.42291 19.5771 5.12229 20.4728 5.34622L21.459 5.59276C21.7769 5.67225 22 5.95795 22 6.28571C22 6.61348 21.7769 6.89918 21.459 6.97867L20.4728 7.22521C19.5771 7.44914 18.8777 8.14851 18.6538 9.04423L18.4072 10.0304C18.3278 10.3484 18.0421 10.5714 17.7143 10.5714C17.3865 10.5714 17.1008 10.3484 17.0213 10.0304L16.7748 9.04423C16.5509 8.14852 15.8515 7.44914 14.9558 7.22521L13.9696 6.97867C13.6516 6.89918 13.4286 6.61348 13.4286 6.28571C13.4286 5.95795 13.6516 5.67225 13.9696 5.59276L14.9558 5.34622C15.8515 5.12229 16.5509 4.42291 16.7748 3.5272L17.0213 2.54105C17.1008 2.22307 17.3865 2 17.7143 2Z"
fill="#D946EF"
/>
<path
d="M12.9487 12.6838C12.8126 12.2754 12.4304 12 12 12C11.5696 12 11.1874 12.2754 11.0513 12.6838L10.8675 13.2351C10.768 13.5337 10.5337 13.768 10.2351 13.8675L9.68377 14.0513C9.27543 14.1874 9 14.5696 9 15C9 15.4304 9.27543 15.8126 9.68377 15.9487L10.2351 16.1325C10.5337 16.232 10.768 16.4663 10.8675 16.7649L11.0513 17.3162C11.1874 17.7246 11.5696 18 12 18C12.4304 18 12.8126 17.7246 12.9487 17.3162L13.1325 16.7649C13.232 16.4663 13.4663 16.232 13.7649 16.1325L14.3162 15.9487C14.7246 15.8126 15 15.4304 15 15C15 14.5696 14.7246 14.1874 14.3162 14.0513L13.7649 13.8675C13.4663 13.768 13.232 13.5337 13.1325 13.2351L12.9487 12.6838Z"
fill="url(#paint2_linear_11402_36656)"
fillRule="evenodd"
clipRule="evenodd"
d="M16.2857 14.8572C16.5932 14.8572 16.8661 15.0539 16.9633 15.3456L17.3388 16.472C17.481 16.8986 17.8157 17.2333 18.2423 17.3755L19.3687 17.751C19.6604 17.8482 19.8571 18.1212 19.8571 18.4286C19.8571 18.7361 19.6604 19.009 19.3687 19.1062L18.2423 19.4817C17.8157 19.6239 17.481 19.9586 17.3388 20.3852L16.9633 21.5116C16.8661 21.8033 16.5932 22 16.2857 22C15.9783 22 15.7053 21.8033 15.6081 21.5116L15.2326 20.3852C15.0904 19.9586 14.7557 19.6239 14.3291 19.4817L13.2027 19.1062C12.911 19.009 12.7143 18.7361 12.7143 18.4286C12.7143 18.1212 12.911 17.8482 13.2027 17.751L14.3291 17.3755C14.7557 17.2333 15.0904 16.8986 15.2326 16.472L15.6081 15.3456C15.7053 15.0539 15.9783 14.8572 16.2857 14.8572Z"
fill="#4F46E5"
/>
<defs>
<linearGradient
id="paint0_linear_11402_36656"
x1="9"
y1="0"
x2="9"
y2="18"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#E543FF" />
<stop offset="1" stopColor="#286399" />
</linearGradient>
<linearGradient
id="paint1_linear_11402_36656"
x1="9"
y1="0"
x2="9"
y2="18"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#E543FF" />
<stop offset="1" stopColor="#286399" />
</linearGradient>
<linearGradient
id="paint2_linear_11402_36656"
x1="9"
y1="0"
x2="9"
y2="18"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#E543FF" />
<stop offset="1" stopColor="#286399" />
</linearGradient>
</defs>
</svg>
);
}
@@ -0,0 +1,44 @@
export function ArchiveIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_17398_782)">
<path
d="M19.2293 9C20.1562 9.00001 20.8612 9.83278 20.7088 10.7471L19.2781 19.3291C19.1173 20.2933 18.283 21 17.3055 21H6.69416C5.71662 21 4.88235 20.2933 4.7215 19.3291L3.29084 10.7471C3.13848 9.83289 3.8436 9.00021 4.77033 9H19.2293ZM9.95002 12.5C9.12179 12.5002 8.45002 13.1717 8.45002 14C8.45002 14.8283 9.12179 15.4998 9.95002 15.5H13.95L14.1033 15.4922C14.8597 15.4154 15.45 14.7767 15.45 14C15.45 13.2233 14.8597 12.5846 14.1033 12.5078L13.95 12.5H9.95002Z"
fill="currentColor"
/>
<rect x="2" y="3" width="20" height="4" rx="1" fill="currentColor" />
</g>
<defs>
<clipPath id="clip0_17398_782">
<rect width="24" height="24" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
}
export function UnarchiveIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_17398_66731)">
<path
d="M19.2027 10C20.1385 10 20.8456 10.8478 20.6782 11.7686L19.2984 19.3574C19.1254 20.3084 18.2972 21 17.3306 21H6.66945C5.70287 21 4.87458 20.3084 4.70167 19.3574L3.32179 11.7686C3.15438 10.8478 3.86152 10 4.79738 10H10.9995V16C10.9995 16.5521 11.4475 16.9997 11.9995 17C12.5518 17 12.9995 16.5523 12.9995 16V10H19.2027Z"
fill="currentColor"
/>
<rect x="11" y="4" width="2" height="6" fill="currentColor" />
<path
d="M15.5 6.5L12 3L8.5 6.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<defs>
<clipPath id="clip0_17398_66731">
<rect width="24" height="24" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
}
@@ -0,0 +1,94 @@
export function BunLogoIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 80 70" xmlns="http://www.w3.org/2000/svg">
<path
id="Shadow"
d="M71.09,20.74c-.16-.17-.33-.34-.5-.5s-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5-.33-.34-.5-.5A26.46,26.46,0,0,1,75.5,35.7c0,16.57-16.82,30.05-37.5,30.05-11.58,0-21.94-4.23-28.83-10.86l.5.5.5.5.5.5.5.5.5.5.5.5.5.5C19.55,65.3,30.14,69.75,42,69.75c20.68,0,37.5-13.48,37.5-30C79.5,32.69,76.46,26,71.09,20.74Z"
/>
<g id="Body">
<path
id="Background"
d="M73,35.7c0,15.21-15.67,27.54-35,27.54S3,50.91,3,35.7C3,26.27,9,17.94,18.22,13S33.18,3,38,3s8.94,4.13,19.78,10C67,17.94,73,26.27,73,35.7Z"
style={{ fill: "#fbf0df" }}
/>
<path
id="Bottom_Shadow"
data-name="Bottom Shadow"
d="M73,35.7a21.67,21.67,0,0,0-.8-5.78c-2.73,33.3-43.35,34.9-59.32,24.94A40,40,0,0,0,38,63.24C57.3,63.24,73,50.89,73,35.7Z"
style={{ fill: "#f6dece" }}
/>
<path
id="Light_Shine"
data-name="Light Shine"
d="M24.53,11.17C29,8.49,34.94,3.46,40.78,3.45A9.29,9.29,0,0,0,38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7c0,.4,0,.8,0,1.19C9.06,15.48,20.07,13.85,24.53,11.17Z"
style={{ fill: "#fffefc" }}
/>
<path
id="Top"
d="M35.12,5.53A16.41,16.41,0,0,1,29.49,18c-.28.25-.06.73.3.59,3.37-1.31,7.92-5.23,6-13.14C35.71,5,35.12,5.12,35.12,5.53Zm2.27,0A16.24,16.24,0,0,1,39,19c-.12.35.31.65.55.36C41.74,16.56,43.65,11,37.93,5,37.64,4.74,37.19,5.14,37.39,5.49Zm2.76-.17A16.42,16.42,0,0,1,47,17.12a.33.33,0,0,0,.65.11c.92-3.49.4-9.44-7.17-12.53C40.08,4.54,39.82,5.08,40.15,5.32ZM21.69,15.76a16.94,16.94,0,0,0,10.47-9c.18-.36.75-.22.66.18-1.73,8-7.52,9.67-11.12,9.45C21.32,16.4,21.33,15.87,21.69,15.76Z"
style={{ fill: "#ccbea7", fillRule: "evenodd" }}
/>
<path
id="Outline"
d="M38,65.75C17.32,65.75.5,52.27.5,35.7c0-10,6.18-19.33,16.53-24.92,3-1.6,5.57-3.21,7.86-4.62,1.26-.78,2.45-1.51,3.6-2.19C32,1.89,35,.5,38,.5s5.62,1.2,8.9,3.14c1,.57,2,1.19,3.07,1.87,2.49,1.54,5.3,3.28,9,5.27C69.32,16.37,75.5,25.69,75.5,35.7,75.5,52.27,58.68,65.75,38,65.75ZM38,3c-2.42,0-5,1.25-8.25,3.13-1.13.66-2.3,1.39-3.54,2.15-2.33,1.44-5,3.07-8,4.7C8.69,18.13,3,26.62,3,35.7,3,50.89,18.7,63.25,38,63.25S73,50.89,73,35.7C73,26.62,67.31,18.13,57.78,13,54,11,51.05,9.12,48.66,7.64c-1.09-.67-2.09-1.29-3-1.84C42.63,4,40.42,3,38,3Z"
/>
</g>
<g id="Mouth">
<g id="Background-2" data-name="Background">
<path
d="M45.05,43a8.93,8.93,0,0,1-2.92,4.71,6.81,6.81,0,0,1-4,1.88A6.84,6.84,0,0,1,34,47.71,8.93,8.93,0,0,1,31.12,43a.72.72,0,0,1,.8-.81H44.26A.72.72,0,0,1,45.05,43Z"
style={{ fill: "#b71422" }}
/>
</g>
<g id="Tongue">
<path
id="Background-3"
data-name="Background"
d="M34,47.79a6.91,6.91,0,0,0,4.12,1.9,6.91,6.91,0,0,0,4.11-1.9,10.63,10.63,0,0,0,1-1.07,6.83,6.83,0,0,0-4.9-2.31,6.15,6.15,0,0,0-5,2.78C33.56,47.4,33.76,47.6,34,47.79Z"
style={{ fill: "#ff6164" }}
/>
<path
id="Outline-2"
data-name="Outline"
d="M34.16,47a5.36,5.36,0,0,1,4.19-2.08,6,6,0,0,1,4,1.69c.23-.25.45-.51.66-.77a7,7,0,0,0-4.71-1.93,6.36,6.36,0,0,0-4.89,2.36A9.53,9.53,0,0,0,34.16,47Z"
/>
</g>
<path
id="Outline-3"
data-name="Outline"
d="M38.09,50.19a7.42,7.42,0,0,1-4.45-2,9.52,9.52,0,0,1-3.11-5.05,1.2,1.2,0,0,1,.26-1,1.41,1.41,0,0,1,1.13-.51H44.26a1.44,1.44,0,0,1,1.13.51,1.19,1.19,0,0,1,.25,1h0a9.52,9.52,0,0,1-3.11,5.05A7.42,7.42,0,0,1,38.09,50.19Zm-6.17-7.4c-.16,0-.2.07-.21.09a8.29,8.29,0,0,0,2.73,4.37A6.23,6.23,0,0,0,38.09,49a6.28,6.28,0,0,0,3.65-1.73,8.3,8.3,0,0,0,2.72-4.37.21.21,0,0,0-.2-.09Z"
/>
</g>
<g id="Face">
<ellipse
id="Right_Blush"
data-name="Right Blush"
cx="53.22"
cy="40.18"
rx="5.85"
ry="3.44"
style={{ fill: "#febbd0" }}
/>
<ellipse
id="Left_Bluch"
data-name="Left Bluch"
cx="22.95"
cy="40.18"
rx="5.85"
ry="3.44"
style={{ fill: "#febbd0" }}
/>
<path
id="Eyes"
d="M25.7,38.8a5.51,5.51,0,1,0-5.5-5.51A5.51,5.51,0,0,0,25.7,38.8Zm24.77,0A5.51,5.51,0,1,0,45,33.29,5.5,5.5,0,0,0,50.47,38.8Z"
style={{ fillRule: "evenodd" }}
/>
<path
id="Iris"
d="M24,33.64a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,24,33.64Zm24.77,0a2.07,2.07,0,1,0-2.06-2.07A2.07,2.07,0,0,0,48.75,33.64Z"
style={{ fill: "#fff", fillRule: "evenodd" }}
/>
</g>
</svg>
);
}
@@ -0,0 +1,15 @@
export function ClockRotateLeftIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.01784 10.9999C4.27072 9.07068 5.21806 7.29972 6.68252 6.01856C8.14697 4.73741 10.0282 4.03389 11.9739 4.03971C13.9197 4.04553 15.7966 4.76028 17.2534 6.05017C18.7101 7.34006 19.6469 9.11666 19.8882 11.0474C20.1296 12.9781 19.659 14.9306 18.5645 16.5394C17.4701 18.1482 15.8268 19.303 13.9424 19.7876C12.0579 20.2722 10.0615 20.0534 8.32671 19.1721C6.59196 18.2909 5.23784 16.8076 4.51784 14.9999M4.01784 19.9999L4.01784 14.9999L9.01784 14.9999"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M12 12L12 8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<path d="M12 12L14 14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}
@@ -0,0 +1,76 @@
export function CloudProviderIcon({
provider,
className,
}: {
provider: "aws" | "digitalocean" | (string & {});
className?: string;
}) {
switch (provider) {
case "aws":
return <AWS className={className} />;
case "digitalocean":
return <DigitalOcean className={className} />;
default:
return null;
}
}
export function AWS({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M67.6343 100.132C67.5545 102.527 67.8738 104.921 68.5123 107.236C69.2307 109.232 70.1088 111.147 71.0666 112.983C71.3859 113.542 71.6254 114.181 71.6254 114.819C71.5455 115.777 70.9868 116.735 70.1088 117.214L65.08 120.566C64.4414 120.965 63.723 121.285 63.0046 121.285C62.1266 121.205 61.2486 120.806 60.61 120.167C59.5723 119.05 58.5346 117.773 57.7364 116.416C56.9382 115.059 56.14 113.542 55.2619 111.706C49.0359 119.05 41.2134 122.722 31.7944 122.722C25.0894 122.722 19.7414 120.806 15.8301 116.974C11.9189 113.143 9.92334 108.034 9.92334 101.649C9.92334 94.8638 12.318 89.3561 17.1871 85.2054C22.0562 81.0547 28.5217 78.9793 36.7434 78.9793C39.5371 78.9793 42.4107 79.2188 45.2044 79.6179C48.1578 80.017 51.1911 80.6556 54.3839 81.374V75.547C54.3839 69.4806 53.1068 65.25 50.6323 62.7756C48.1578 60.3011 43.7677 59.1038 37.6214 59.1038C34.7478 59.1038 31.7944 59.4231 29.0007 60.1415C26.0473 60.8598 23.1737 61.7379 20.38 62.8554C19.5019 63.2545 18.5441 63.5738 17.5862 63.8931C17.1871 64.0527 16.7082 64.1325 16.3091 64.1325C15.1916 64.1325 14.6328 63.3343 14.6328 61.6581V57.7468C14.553 56.7889 14.7925 55.8311 15.1916 54.9531C15.8301 54.2347 16.5485 53.6759 17.4266 53.2768C20.6194 51.6804 23.9719 50.483 27.4841 49.6848C31.555 48.6472 35.7855 48.1682 39.9362 48.1682C49.435 48.1682 56.3795 50.3234 60.8495 54.6338C65.3195 58.9441 67.4746 65.4895 67.4746 74.2699V100.132H67.6343ZM35.2268 112.265C38.0205 112.265 40.8143 111.786 43.4484 110.828C46.4018 109.79 48.9561 108.034 51.0314 105.72C52.3086 104.283 53.2664 102.527 53.7453 100.611C54.3041 98.376 54.5436 95.9813 54.5436 93.6665V90.314C52.1489 89.7552 49.6744 89.2763 47.2 88.957C44.7255 88.6377 42.1712 88.4781 39.6968 88.4781C34.3487 88.4781 30.4375 89.5158 27.8034 91.6709C25.1692 93.8261 23.8921 96.8593 23.8921 100.85C23.8921 104.602 24.85 107.396 26.8455 109.312C28.841 111.227 31.555 112.265 35.2268 112.265ZM99.3234 120.886C98.2857 120.965 97.1682 120.726 96.2902 120.087C95.492 119.209 94.8534 118.172 94.614 116.974L75.8559 55.2723C75.4568 54.2347 75.2173 53.197 75.1375 52.0795C75.0577 51.1216 75.6963 50.2436 76.6541 50.0839H84.9556C86.4722 50.0839 87.5098 50.3234 88.0686 50.8822C88.6273 51.4409 89.1861 52.4786 89.665 53.9952L102.995 106.837L115.447 53.9952C115.846 52.3988 116.325 51.3611 116.964 50.8822C117.922 50.3234 119.039 50.0041 120.157 50.0839H126.543C128.059 50.0839 129.097 50.3234 129.735 50.8822C130.534 51.7602 131.092 52.7979 131.252 53.9952L143.864 107.476L157.673 53.9952C157.912 52.7979 158.471 51.7602 159.269 50.8822C160.227 50.3234 161.265 50.0041 162.382 50.0839H169.806C170.764 49.9243 171.642 50.6427 171.801 51.6005V52.0795C171.801 52.4786 171.722 52.9575 171.642 53.3566C171.482 54.075 171.322 54.7136 171.083 55.3522L151.846 117.054C151.367 118.651 150.808 119.688 150.17 120.167C149.292 120.726 148.174 121.045 147.137 120.965H140.272C138.755 120.965 137.718 120.726 137.079 120.167C136.281 119.289 135.722 118.172 135.562 116.974L123.19 65.4895L110.898 116.895C110.738 118.092 110.179 119.209 109.381 120.087C108.423 120.726 107.306 120.965 106.188 120.886H99.3234ZM201.894 123.041C197.743 123.041 193.593 122.562 189.602 121.604C185.61 120.646 182.497 119.608 180.422 118.411C179.384 117.932 178.506 117.134 177.948 116.176C177.628 115.458 177.469 114.739 177.469 113.941V109.87C177.469 108.194 178.107 107.396 179.305 107.396C179.784 107.396 180.262 107.476 180.741 107.635C181.22 107.795 181.939 108.114 182.737 108.433C185.531 109.711 188.484 110.589 191.517 111.227C194.63 111.866 197.823 112.185 201.016 112.185C206.045 112.185 209.956 111.307 212.67 109.551C215.304 107.955 216.901 105.081 216.821 101.968C216.901 99.8926 216.102 97.897 214.665 96.3804C213.229 94.8638 210.515 93.5068 206.604 92.2297L195.029 88.6377C189.202 86.8018 184.892 84.0879 182.258 80.4959C179.704 77.1434 178.267 73.1524 178.267 68.9218C178.187 65.8088 178.905 62.7756 180.422 60.0616C181.859 57.5073 183.775 55.2723 186.169 53.5163C188.644 51.6005 191.437 50.2436 194.471 49.3655C197.743 48.4077 201.096 47.9288 204.528 48.0086C206.284 48.0086 208.12 48.0884 209.876 48.3279C211.712 48.5673 213.388 48.8866 215.065 49.2059C216.741 49.5252 218.178 50.0041 219.614 50.483C220.812 50.8822 221.929 51.3611 222.967 51.9198C223.925 52.3988 224.723 53.0373 225.362 53.9154C225.92 54.7136 226.16 55.5916 226.08 56.5495V60.3011C226.08 61.9773 225.441 62.8554 224.244 62.8554C223.206 62.6957 222.169 62.3765 221.211 61.8975C216.422 59.7423 211.153 58.7047 205.885 58.7845C201.335 58.7845 197.743 59.5029 195.269 61.0195C192.794 62.5361 191.517 64.8509 191.517 68.1236C191.437 70.2788 192.315 72.3541 193.912 73.7909C195.508 75.3075 198.462 76.8241 202.692 78.1811L214.027 81.7731C219.774 83.609 223.925 86.1633 226.399 89.4359C228.874 92.6288 230.151 96.6199 230.071 100.611C230.151 103.804 229.432 106.997 227.996 109.87C226.559 112.584 224.563 114.979 222.169 116.895C219.535 118.97 216.501 120.487 213.309 121.365C209.637 122.482 205.805 123.041 201.894 123.041Z"
fill="white"
/>
<path
d="M216.98 161.834C190.719 181.231 152.564 191.528 119.758 191.528C73.7806 191.528 32.3533 174.526 1.06324 146.269C-1.41123 144.034 0.823772 141.001 3.77717 142.757C37.6215 162.393 79.3681 174.286 122.552 174.286C153.682 174.126 184.493 167.821 213.149 155.768C217.539 153.772 221.291 158.641 216.98 161.834Z"
fill="#FF9900"
/>
<path
d="M227.916 149.382C224.563 145.072 205.726 147.307 197.185 148.344C194.63 148.664 194.231 146.428 196.546 144.752C211.553 134.216 236.217 137.249 239.091 140.761C241.965 144.273 238.293 169.018 224.244 180.832C222.089 182.667 220.014 181.71 220.971 179.315C224.164 171.413 231.268 153.612 227.916 149.382Z"
fill="#FF9900"
/>
</svg>
);
}
export function DigitalOcean({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_18907_83670)">
<path
d="M120.001 218.82V180.552C160.62 180.552 192.015 140.347 176.514 97.6503C170.768 81.8384 158.157 69.2205 142.339 63.4808C99.6439 47.9862 59.4601 79.3824 59.4532 119.988L59.4463 120.002H21.1802C21.1802 55.292 83.6945 4.91045 151.493 26.0827C181.122 35.327 204.667 58.8724 213.918 88.5025C235.089 156.31 184.703 218.82 120.001 218.82Z"
fill="#0069FF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M120.089 180.647H81.9333V142.497L81.9402 142.49H120.082L120.089 142.497V180.647Z"
fill="#0069FF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M81.925 209.964H52.6132L52.6063 209.957V180.644H81.9319V209.957L81.925 209.964Z"
fill="#0069FF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M52.6298 180.647H28.0573L28.0435 180.64V156.081L28.0573 156.06H52.616L52.6298 156.067V180.647Z"
fill="#0069FF"
/>
</g>
<defs>
<clipPath id="clip0_18907_83670">
<rect
width="197.64"
height="197.64"
fill="white"
transform="translate(21.1802 21.1802)"
/>
</clipPath>
</defs>
</svg>
);
}
@@ -75,7 +75,7 @@ export function ProdEnvironmentIcon({ className }: { className?: string }) {
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.5037 7.33603C12.3174 6.88799 11.6827 6.88799 11.4963 7.33603L10.4338 9.89064L7.6759 10.1117C7.1922 10.1505 6.99606 10.7542 7.36459 11.0698L9.46583 12.8698L8.82387 15.561C8.71128 16.033 9.22477 16.4061 9.63888 16.1532L12 14.711L14.3612 16.1532C14.7753 16.4061 15.2888 16.0331 15.1762 15.561L14.5343 12.8698L16.6355 11.0698C17.004 10.7542 16.8079 10.1505 16.3242 10.1117L13.5663 9.89064L12.5037 7.33603Z"
fill="white"
fill="currentColor"
/>
<rect
x="3"
@@ -151,3 +151,28 @@ export function DeployedEnvironmentIconSmall({ className }: { className?: string
</svg>
);
}
export function PreviewEnvironmentIconSmall({ className }: { className?: string }) {
return <BranchEnvironmentIconSmall className={className} />;
}
export function BranchEnvironmentIconSmall({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.23059 7C1.65409 6.99967 1.30545 6.36259 1.61634 5.87695L3.88489 2.33301C4.17195 1.88466 4.82727 1.88472 5.11438 2.33301L7.38294 5.87695C7.69386 6.36265 7.34531 6.99982 6.76868 7L2.23059 7Z"
fill="currentColor"
/>
<path
d="M3.5 6H5.5V15C5.5 15.5523 5.05228 16 4.5 16C3.94772 16 3.5 15.5523 3.5 15V6Z"
fill="currentColor"
/>
<path
d="M13.5 7V7.5C13.5 9.15685 12.1569 10.5 10.5 10.5H7.5C5.84315 10.5 4.5 11.8431 4.5 13.5V15"
stroke="currentColor"
strokeWidth="2"
/>
<circle cx="13.5" cy="5" r="2" stroke="currentColor" strokeWidth="2" />
</svg>
);
}
@@ -1,5 +0,0 @@
import { LogoIcon } from "~/components/LogoIcon";
export function IntegrationIcon() {
return <LogoIcon className="h-3.5 w-3.5 flex-none pb-0.5" />;
}
@@ -0,0 +1,12 @@
export function KeyboardEnterIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 5H18C19.6569 5 21 6.34315 21 8V13C21 14.6569 19.6569 16 18 16H8"
stroke="currentColor"
strokeWidth="2"
/>
<path d="M2 16L8 12L8 20L2 16Z" fill="currentColor" />
</svg>
);
}
@@ -0,0 +1,48 @@
export function ListCheckedIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.85001 3.82759L4.09139 5.06897L6.16035 3"
stroke="currentColor"
strokeWidth="1.65517"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.85001 8.82759L4.09139 10.069L6.16035 8"
stroke="currentColor"
strokeWidth="1.65517"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.85001 13.8276L4.09139 15.069L6.16035 13"
stroke="currentColor"
strokeWidth="1.65517"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9 4H15"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9 9H15"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9 14H15"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
@@ -0,0 +1,221 @@
import { cn } from "~/utils/cn";
export function MachineIcon({ preset, className }: { preset?: string; className?: string }) {
if (!preset) {
return <MachineIconNoMachine className={cn("size-5", className)} />;
}
switch (preset) {
case "no-machine":
return <MachineIconNoMachine className={cn("size-5", className)} />;
case "micro":
return <MachineIconMicro className={cn("size-5", className)} />;
case "small-1x":
return <MachineIconSmall1x className={cn("size-5", className)} />;
case "small-2x":
return <MachineIconSmall2x className={cn("size-5", className)} />;
case "medium-1x":
return <MachineIconMedium1x className={cn("size-5", className)} />;
case "medium-2x":
return <MachineIconMedium2x className={cn("size-5", className)} />;
case "large-1x":
return <MachineIconLarge1x className={cn("size-5", className)} />;
case "large-2x":
return <MachineIconLarge2x className={cn("size-5", className)} />;
default:
return <MachineDefaultIcon className={cn("size-5", className)} />;
}
}
export function MachineDefaultIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="currentColor"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="currentColor"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="currentColor"
/>
</svg>
);
}
function MachineIconNoMachine({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H6.18945C6.25882 16.9453 6.32663 16.8872 6.39062 16.8232L10.2139 13H16.1875Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
<path
d="M4.84961 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H8.84961L4.84961 12ZM16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H11.2139L15.2139 8H16.1875Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
<path
d="M9.85059 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H13.8506L9.85059 7Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
<line
x1="3"
y1="16.9998"
x2="17.0018"
y2="2.99805"
stroke="#D7D9DD"
strokeOpacity="0.4"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);
}
function MachineIconMicro({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
</svg>
);
}
function MachineIconSmall1x({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="#3B82F6"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
</svg>
);
}
function MachineIconSmall2x({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="#3B82F6"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
</svg>
);
}
function MachineIconMedium1x({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="#3B82F6"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="#3B82F6"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
</svg>
);
}
function MachineIconMedium2x({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="#3B82F6"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="#3B82F6"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="#D7D9DD"
fillOpacity="0.4"
/>
</svg>
);
}
function MachineIconLarge1x({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="#3B82F6"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="#3B82F6"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="#3B82F6"
/>
</svg>
);
}
function MachineIconLarge2x({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
fill="#3B82F6"
/>
<path
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
fill="#3B82F6"
/>
<path
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
fill="#3B82F6"
/>
</svg>
);
}
@@ -0,0 +1,15 @@
export function NodejsLogoIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 71 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_337_7891)" fill="#5FA04E">
<path d="M35.625 79.5c-1.081 0-2.09-.288-3.028-.792l-9.59-5.686c-1.442-.792-.721-1.08-.289-1.224 1.947-.648 2.308-.792 4.327-1.944.216-.144.504-.072.72.072l7.356 4.391c.288.144.649.144.865 0l28.77-16.628c.289-.144.433-.431.433-.791V23.714c0-.36-.144-.648-.432-.792L35.986 6.366c-.288-.144-.65-.144-.865 0L6.35 22.922c-.29.144-.434.504-.434.792v33.184c0 .287.145.647.433.791l7.86 4.535c4.254 2.16 6.922-.36 6.922-2.879V26.593c0-.432.36-.864.865-.864h3.678c.432 0 .865.36.865.864v32.752c0 5.687-3.1 8.998-8.509 8.998-1.658 0-2.956 0-6.633-1.8l-7.572-4.319A6.073 6.073 0 0 1 .798 56.97V23.786a6.073 6.073 0 0 1 3.028-5.255l28.77-16.628c1.804-1.008 4.255-1.008 6.058 0l28.77 16.628a6.073 6.073 0 0 1 3.029 5.255V56.97a6.073 6.073 0 0 1-3.029 5.254l-28.77 16.628c-.865.36-1.947.648-3.029.648Z" />
<path d="M44.567 56.682c-12.62 0-15.215-5.759-15.215-10.654 0-.432.36-.864.865-.864h3.75c.433 0 .793.288.793.72.577 3.815 2.235 5.687 9.879 5.687 6.057 0 8.652-1.368 8.652-4.607 0-1.871-.72-3.24-10.167-4.175-7.86-.792-12.762-2.52-12.762-8.782 0-5.83 4.903-9.285 13.123-9.285 9.23 0 13.772 3.167 14.35 10.077 0 .216-.073.432-.217.648-.144.144-.36.288-.577.288h-3.822a.844.844 0 0 1-.793-.648c-.865-3.96-3.1-5.255-9.013-5.255-6.634 0-7.427 2.304-7.427 4.031 0 2.088.937 2.736 9.879 3.887 8.869 1.152 13.05 2.808 13.05 8.998 0 6.335-5.263 9.934-14.348 9.934Z" />
</g>
<defs>
<clipPath id="clip0_337_7891">
<path fill="#fff" d="M0 .5h71v79H0z" />
</clipPath>
</defs>
</svg>
);
}
@@ -0,0 +1,106 @@
export function FlagIcon({
region,
className,
}: {
region: "usa" | "europe" | (string & {});
className?: string;
}) {
switch (region) {
case "usa":
return <FlagUSA className={className} />;
case "europe":
return <FlagEurope className={className} />;
default:
return null;
}
}
export function FlagUSA({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 96 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_18908_83688)">
<rect x="0.09375" width="95.9062" height="71.9297" rx="5.99414" fill="white" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.09375 0V8.0065H96.0937V0H0.09375Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.09375 16.0131V24.0196H96.0937V16.0131H0.09375Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.09375 31.9791V39.9856H96.0937V31.9791H0.09375Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.09375 47.9921V55.9986H96.0937V47.9921H0.09375Z"
fill="#E31D1C"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.09375 63.9584V71.9649H96.0937V63.9584H0.09375Z"
fill="#E31D1C"
/>
<rect x="0.09375" width="60" height="40" fill="#2E52B2" />
<path
d="M50.9424 29.8628H53.0938L51.4033 31.5835L52.0576 34.2974L49.9531 32.772L47.7773 34.2974L48.5117 31.5835L46.5791 29.8628H49.1035L49.9502 27.6479L50.9424 29.8628ZM11.3486 29.2515H13.5L11.8096 30.9712L12.4639 33.686L10.3594 32.1606L8.18359 33.686L8.91797 30.9712L6.98633 29.2515H9.50977L10.3564 27.0366L11.3486 29.2515ZM24.5469 29.2515H26.6982L25.0078 30.9712L25.6621 33.686L23.5566 32.1606L21.3818 33.686L22.1162 30.9712L20.1836 29.2515H22.708L23.5537 27.0366L24.5469 29.2515ZM37.7441 29.2515H39.8955L38.2051 30.9712L38.8604 33.686L36.7549 32.1606L34.5791 33.686L35.3135 30.9712L33.3818 29.2515H35.9053L36.752 27.0366L37.7441 29.2515ZM50.9424 18.9077H53.0938L51.4033 20.6284L52.0576 23.3423L49.9521 21.8169L47.7773 23.3423L48.5117 20.6284L46.5791 18.9077H49.1025L49.9492 16.6929L50.9424 18.9077ZM11.3486 18.6021H13.5L11.8096 20.3218L12.4639 23.0366L10.3594 21.5112L8.18359 23.0366L8.91797 20.3218L6.98535 18.6021H9.50977L10.3555 16.3872L11.3486 18.6021ZM24.5459 18.6021H26.6973L25.0068 20.3218L25.6621 23.0366L23.5566 21.5112L21.3809 23.0366L22.1152 20.3218L20.1836 18.6021H22.707L23.5537 16.3872L24.5459 18.6021ZM37.7441 18.6021H39.8955L38.2051 20.3218L38.8594 23.0366L36.7549 21.5112L34.5791 23.0366L35.3135 20.3218L33.3809 18.6021H35.9053L36.752 16.3872L37.7441 18.6021ZM11.3486 7.95264H13.5L11.8096 9.67334L12.4639 12.3872L10.3594 10.8618L8.18359 12.3872L8.91797 9.67334L6.98535 7.95264H9.50977L10.3555 5.73779L11.3486 7.95264ZM24.5459 7.95264H26.6973L25.0068 9.67334L25.6621 12.3872L23.5566 10.8618L21.3809 12.3872L22.1152 9.67334L20.1836 7.95264H22.707L23.5537 5.73779L24.5459 7.95264ZM37.7441 7.95264H39.8955L38.2051 9.67334L38.8594 12.3872L36.7549 10.8618L34.5791 12.3872L35.3135 9.67334L33.3809 7.95264H35.9053L36.752 5.73779L37.7441 7.95264ZM50.9424 7.95264H53.0938L51.4033 9.67334L52.0576 12.3872L49.9521 10.8618L47.7773 12.3872L48.5117 9.67334L46.5791 7.95264H49.1025L49.9492 5.73779L50.9424 7.95264Z"
fill="white"
/>
</g>
<defs>
<clipPath id="clip0_18908_83688">
<rect x="0.09375" width="95.9062" height="71.9297" rx="5.99414" fill="white" />
</clipPath>
</defs>
</svg>
);
}
export function FlagEurope({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 96 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.09375" width="95.9062" height="71.9297" rx="5.99414" fill="#2E52B2" />
<path
d="M30.1359 9.60876L28.2171 15.508H22.017L27.0358 19.1573L25.1171 25.0565L30.1359 21.4072L35.1547 25.0565L33.236 19.1573L38.2548 15.508H32.0546L30.1359 9.60876Z"
fill="#F6CD46"
/>
<path
d="M48.0469 2.29382L46.1282 8.19302H39.928L44.9468 11.8424L43.0281 17.7416L48.0469 14.0922L53.0657 17.7416L51.147 11.8424L56.1658 8.19302H49.9656L48.0469 2.29382Z"
fill="#F6CD46"
/>
<path
d="M65.9577 10.0177L64.039 15.9169H57.8388L62.8576 19.5663L60.9389 25.4655L65.9577 21.8161L70.9766 25.4655L69.0578 19.5663L74.0766 15.9169H67.8765L65.9577 10.0177Z"
fill="#F6CD46"
/>
<path
d="M73.5861 27.4231L71.6673 33.3223H65.4672L70.486 36.9717L68.5672 42.8709L73.5861 39.2215L78.6049 42.8709L76.6862 36.9717L81.705 33.3223H75.5048L73.5861 27.4231Z"
fill="#F6CD46"
/>
<path
d="M65.9577 46.0563L64.039 51.9555H57.8388L62.8576 55.6048L60.9389 61.504L65.9577 57.8547L70.9766 61.504L69.0578 55.6048L74.0766 51.9555H67.8765L65.9577 46.0563Z"
fill="#F6CD46"
/>
<path
d="M48.0469 53.7802L46.1282 59.6794H39.928L44.9468 63.3287L43.0281 69.2279L48.0469 65.5785L53.0657 69.2279L51.147 63.3287L56.1658 59.6794H49.9656L48.0469 53.7802Z"
fill="#F6CD46"
/>
<path
d="M30.1359 46.0563L28.2171 51.9555H22.017L27.0358 55.6048L25.1171 61.504L30.1359 57.8547L35.1547 61.504L33.236 55.6048L38.2548 51.9555H32.0546L30.1359 46.0563Z"
fill="#F6CD46"
/>
<path
d="M22.5077 27.4231L20.589 33.3223H14.3888L19.4076 36.9717L17.4889 42.8709L22.5077 39.2215L27.5266 42.8709L25.6078 36.9717L30.6266 33.3223H24.4265L22.5077 27.4231Z"
fill="#F6CD46"
/>
</svg>
);
}
@@ -0,0 +1,14 @@
export function SparkleListIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17 11C17.4304 11 17.8126 11.2754 17.9487 11.6838L18.4743 13.2607C18.6734 13.858 19.142 14.3266 19.7393 14.5257L21.3162 15.0513C21.7246 15.1874 22 15.5696 22 16C22 16.4304 21.7246 16.8126 21.3162 16.9487L19.7393 17.4743C19.142 17.6734 18.6734 18.142 18.4743 18.7393L17.9487 20.3162C17.8126 20.7246 17.4304 21 17 21C16.5696 21 16.1874 20.7246 16.0513 20.3162L15.5257 18.7393C15.3266 18.142 14.858 17.6734 14.2607 17.4743L12.6838 16.9487C12.2754 16.8126 12 16.4304 12 16C12 15.5696 12.2754 15.1874 12.6838 15.0513L14.2607 14.5257C14.858 14.3266 15.3266 13.858 15.5257 13.2607L16.0513 11.6838C16.1874 11.2754 16.5696 11 17 11Z"
fill="currentColor"
/>
<rect x="2" y="6" width="20" height="2" rx="1" fill="currentColor" />
<rect x="2" y="15" width="7" height="2" rx="1" fill="currentColor" />
</svg>
);
}
@@ -0,0 +1,41 @@
export function TextInlineIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3 3V21"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M13 21L13 16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M13 8L13 3"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7 12L20 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.5 15.5L21 12L17.5 8.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
@@ -0,0 +1,34 @@
export function TextWrapIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3 3V21"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M21 21V3"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7 7H14C15.6569 7 17 8.34315 17 10V13C17 14.6569 15.6569 16 14 16H9"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11 13L8 16L11 19"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

+75
View File
@@ -0,0 +1,75 @@
import { mkdir, writeFile } from "fs/promises";
import { prisma } from "./db.server";
import { env } from "./env.server";
import { WorkerGroupService } from "./v3/services/worker/workerGroupService.server";
import { dirname } from "path";
import { tryCatch } from "@trigger.dev/core";
export async function bootstrap() {
if (env.TRIGGER_BOOTSTRAP_ENABLED !== "1") {
return;
}
if (env.TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME) {
const [error] = await tryCatch(createWorkerGroup());
if (error) {
console.error("Failed to create worker group", { error });
}
}
}
async function createWorkerGroup() {
const workerGroupName = env.TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME;
const tokenPath = env.TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH;
const existingWorkerGroup = await prisma.workerInstanceGroup.findFirst({
where: {
name: workerGroupName,
},
});
if (existingWorkerGroup) {
console.warn(`[bootstrap] Worker group ${workerGroupName} already exists`);
return;
}
const service = new WorkerGroupService();
const { token, workerGroup } = await service.createWorkerGroup({
name: workerGroupName,
});
console.log(`
==========================
Trigger.dev Bootstrap - Worker Token
WARNING: This will only be shown once. Save it now!
Worker group:
${workerGroup.name}
Token:
${token.plaintext}
If using docker compose, set:
TRIGGER_WORKER_TOKEN=${token.plaintext}
${
tokenPath
? `Or, if using a file:
TRIGGER_WORKER_TOKEN=file://${tokenPath}`
: ""
}
==========================
`);
if (tokenPath) {
const dir = dirname(tokenPath);
await mkdir(dir, { recursive: true });
await writeFile(tokenPath, token.plaintext, {
mode: 0o600,
});
console.log(`[bootstrap] Worker token saved to ${tokenPath}`);
}
}
+535
View File
@@ -0,0 +1,535 @@
import {
ArrowPathIcon,
ArrowUpIcon,
HandThumbDownIcon,
HandThumbUpIcon,
StopIcon,
} from "@heroicons/react/20/solid";
import { type FeedbackComment, KapaProvider, type QA, useChat } from "@kapaai/react-sdk";
import { useSearchParams } from "@remix-run/react";
import DOMPurify from "dompurify";
import { motion } from "framer-motion";
import { marked } from "marked";
import { useCallback, useEffect, useRef, useState } from "react";
import { useTypedRouteLoaderData } from "remix-typedjson";
import { AISparkleIcon } from "~/assets/icons/AISparkleIcon";
import { SparkleListIcon } from "~/assets/icons/SparkleListIcon";
import { useFeatures } from "~/hooks/useFeatures";
import { type loader } from "~/root";
import { Button } from "./primitives/Buttons";
import { Callout } from "./primitives/Callout";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./primitives/Dialog";
import { Header2 } from "./primitives/Headers";
import { Paragraph } from "./primitives/Paragraph";
import { ShortcutKey } from "./primitives/ShortcutKey";
import { Spinner } from "./primitives/Spinner";
import {
SimpleTooltip,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "./primitives/Tooltip";
import { ClientOnly } from "remix-utils/client-only";
function useKapaWebsiteId() {
const routeMatch = useTypedRouteLoaderData<typeof loader>("root");
return routeMatch?.kapa.websiteId;
}
export function AskAI() {
const { isManagedCloud } = useFeatures();
const websiteId = useKapaWebsiteId();
if (!isManagedCloud || !websiteId) {
return null;
}
return (
<ClientOnly
fallback={
<Button
variant="small-menu-item"
data-action="ask-ai"
hideShortcutKey
data-modal-override-open-class-ask-ai="true"
disabled
>
<AISparkleIcon className="size-5" />
</Button>
}
>
{() => <AskAIProvider websiteId={websiteId} />}
</ClientOnly>
);
}
type AskAIProviderProps = {
websiteId: string;
};
function AskAIProvider({ websiteId }: AskAIProviderProps) {
const [isOpen, setIsOpen] = useState(false);
const [initialQuery, setInitialQuery] = useState<string | undefined>();
const [searchParams, setSearchParams] = useSearchParams();
const openAskAI = useCallback((question?: string) => {
if (question) {
setInitialQuery(question);
} else {
setInitialQuery(undefined);
}
setIsOpen(true);
}, []);
const closeAskAI = useCallback(() => {
setIsOpen(false);
setInitialQuery(undefined);
}, []);
// Handle URL param functionality
useEffect(() => {
const aiHelp = searchParams.get("aiHelp");
if (aiHelp) {
// Delay to avoid hCaptcha bot detection
window.setTimeout(() => openAskAI(aiHelp), 1000);
// Clone instead of mutating in place
const next = new URLSearchParams(searchParams);
next.delete("aiHelp");
setSearchParams(next);
}
}, [searchParams, openAskAI]);
return (
<KapaProvider
integrationId={websiteId}
callbacks={{
askAI: {
onQuerySubmit: () => openAskAI(),
onAnswerGenerationCompleted: () => openAskAI(),
},
}}
botProtectionMechanism="hcaptcha"
>
<TooltipProvider disableHoverableContent>
<Tooltip>
<TooltipTrigger asChild>
<div className="inline-flex">
<Button
variant="small-menu-item"
data-action="ask-ai"
shortcut={{ modifiers: ["mod"], key: "/", enabledOnInputElements: true }}
hideShortcutKey
data-modal-override-open-class-ask-ai="true"
onClick={() => openAskAI()}
>
<AISparkleIcon className="size-5" />
</Button>
</div>
</TooltipTrigger>
<TooltipContent side="top" className="flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs">
Ask AI
<ShortcutKey shortcut={{ modifiers: ["mod"], key: "/" }} variant="medium/bright" />
</TooltipContent>
</Tooltip>
</TooltipProvider>
<AskAIDialog
initialQuery={initialQuery}
isOpen={isOpen}
onOpenChange={setIsOpen}
closeAskAI={closeAskAI}
/>
</KapaProvider>
);
}
type AskAIDialogProps = {
initialQuery?: string;
isOpen: boolean;
onOpenChange: (open: boolean) => void;
closeAskAI: () => void;
};
function AskAIDialog({ initialQuery, isOpen, onOpenChange, closeAskAI }: AskAIDialogProps) {
const handleOpenChange = (open: boolean) => {
if (!open) {
closeAskAI();
} else {
onOpenChange(open);
}
};
return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogContent className="animated-gradient-glow flex max-h-[90vh] min-h-fit w-full flex-col justify-between gap-0 px-0 pb-0 pt-0 sm:max-w-prose">
<DialogHeader className="flex h-[2.75rem] items-start justify-center rounded-t-md bg-background-bright pl-3">
<div className="flex items-center gap-1">
<AISparkleIcon className="size-5" />
<DialogTitle className="text-sm font-medium text-text-bright">Ask AI</DialogTitle>
</div>
</DialogHeader>
<ChatInterface initialQuery={initialQuery} />
</DialogContent>
</Dialog>
);
}
function ChatMessages({
conversation,
isPreparingAnswer,
isGeneratingAnswer,
onReset,
onExampleClick,
error,
addFeedback,
}: {
conversation: QA[];
isPreparingAnswer: boolean;
isGeneratingAnswer: boolean;
onReset: () => void;
onExampleClick: (question: string) => void;
error: string | null;
addFeedback: (
questionAnswerId: string,
reaction: "upvote" | "downvote",
comment?: FeedbackComment
) => void;
}) {
const [feedbackGivenForQAs, setFeedbackGivenForQAs] = useState<Set<string>>(new Set());
// Reset feedback state when conversation is reset
useEffect(() => {
if (conversation.length === 0) {
setFeedbackGivenForQAs(new Set());
}
}, [conversation.length]);
// Check if feedback has been given for the latest QA
const latestQA = conversation[conversation.length - 1];
const hasFeedbackForLatestQA = latestQA?.id ? feedbackGivenForQAs.has(latestQA.id) : false;
const exampleQuestions = [
"How do I increase my concurrency limit?",
"How do I debug errors in my task?",
"How do I deploy my task?",
];
return (
<div className="flex-1 overflow-y-auto p-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
{conversation.length === 0 ? (
<motion.div
className="flex flex-col gap-2 pb-2"
initial="hidden"
animate="visible"
variants={{
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
}}
>
<Paragraph className="mb-3 mt-1.5 pl-1">
I'm trained on docs, examples, and other content. Ask me anything about Trigger.dev.
</Paragraph>
{exampleQuestions.map((question, index) => (
<motion.button
key={index}
className="group flex w-fit items-center gap-2 rounded-full border border-dashed border-charcoal-600 px-4 py-2 transition-colors hover:border-solid hover:border-indigo-500"
onClick={() => onExampleClick(question)}
variants={{
hidden: {
opacity: 0,
x: 20,
},
visible: {
opacity: 1,
x: 0,
transition: {
opacity: {
duration: 0.5,
ease: "linear",
},
x: {
type: "spring",
stiffness: 300,
damping: 25,
},
},
},
}}
>
<SparkleListIcon className="size-4 text-text-dimmed transition group-hover:text-indigo-500" />
<Paragraph variant="small" className="transition group-hover:text-text-bright">
{question}
</Paragraph>
</motion.button>
))}
</motion.div>
) : (
conversation.map((qa) => (
<div key={qa.id || `temp-${qa.question}`} className="mb-4">
<Header2 spacing>{qa.question}</Header2>
<div
className="prose prose-invert max-w-none text-text-dimmed"
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(marked(qa.answer)) }}
/>
</div>
))
)}
{conversation.length > 0 &&
!isPreparingAnswer &&
!isGeneratingAnswer &&
!error &&
!latestQA?.id && (
<div className="flex items-center justify-between border-t border-grid-bright pt-3">
<Paragraph variant="small" className="text-text-dimmed">
Answer generation was stopped
</Paragraph>
<Button
variant="minimal/small"
LeadingIcon={<ArrowPathIcon className="size-4" />}
onClick={onReset}
className="w-fit pl-1.5"
iconSpacing="gap-x-1.5"
>
Reset chat
</Button>
</div>
)}
{conversation.length > 0 &&
!isPreparingAnswer &&
!isGeneratingAnswer &&
!error &&
latestQA?.id && (
<div className="flex items-center justify-between border-t border-grid-bright pt-3">
{hasFeedbackForLatestQA ? (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<Paragraph variant="small" className="text-text-dimmed">
Thanks for your feedback!
</Paragraph>
</motion.div>
) : (
<div className="flex items-center gap-2">
<Paragraph variant="small" className="text-text-dimmed">
Was this helpful?
</Paragraph>
<div className="flex items-center">
<Button
variant="minimal/small"
onClick={() => {
const latestQA = conversation[conversation.length - 1];
if (latestQA?.id) {
addFeedback(latestQA.id, "upvote");
setFeedbackGivenForQAs((prev) => new Set(prev).add(latestQA.id));
}
}}
className="size-8 px-1.5"
>
<HandThumbUpIcon className="size-4 text-text-dimmed transition group-hover/button:text-success" />
</Button>
<Button
variant="minimal/small"
onClick={() => {
const latestQA = conversation[conversation.length - 1];
if (latestQA?.id) {
addFeedback(latestQA.id, "downvote");
setFeedbackGivenForQAs((prev) => new Set(prev).add(latestQA.id));
}
}}
className="size-8 px-1.5"
>
<HandThumbDownIcon className="size-4 text-text-dimmed transition group-hover/button:text-error" />
</Button>
</div>
</div>
)}
<Button
variant="minimal/small"
LeadingIcon={<ArrowPathIcon className="size-4" />}
onClick={onReset}
className="w-fit pl-1.5"
iconSpacing="gap-x-1.5"
>
Reset chat
</Button>
</div>
)}
{isPreparingAnswer && (
<div className="flex items-center gap-2">
<Spinner
color={{
background: "rgba(99, 102, 241, 1)",
foreground: "rgba(217, 70, 239, 1)",
}}
className="size-4"
/>
<Paragraph className="text-text-dimmed">Preparing answer</Paragraph>
</div>
)}
{error && (
<div className="flex flex-col">
<Callout variant="error" className="mb-4">
<Paragraph className="font-semibold text-error">Error generating answer:</Paragraph>
<Paragraph className="text-rose-300">
{error} If the problem persists after retrying, please contact support.
</Paragraph>
</Callout>
<div className="flex justify-end">
<Button
variant="secondary/small"
LeadingIcon={<ArrowPathIcon className="size-4" />}
onClick={onReset}
className="w-fit pl-1.5"
iconSpacing="gap-x-1.5"
>
Reset chat
</Button>
</div>
</div>
)}
</div>
);
}
function ChatInterface({ initialQuery }: { initialQuery?: string }) {
const [message, setMessage] = useState("");
const [isExpanded, setIsExpanded] = useState(false);
const hasSubmittedInitialQuery = useRef(false);
const {
conversation,
submitQuery,
isGeneratingAnswer,
isPreparingAnswer,
resetConversation,
stopGeneration,
error,
addFeedback,
} = useChat();
useEffect(() => {
if (initialQuery && !hasSubmittedInitialQuery.current) {
hasSubmittedInitialQuery.current = true;
setIsExpanded(true);
submitQuery(initialQuery);
}
}, [initialQuery, submitQuery]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (message.trim()) {
setIsExpanded(true);
submitQuery(message);
setMessage("");
}
};
const handleExampleClick = (question: string) => {
setIsExpanded(true);
submitQuery(question);
};
const handleReset = () => {
resetConversation();
setIsExpanded(false);
};
return (
<motion.div
className="flex h-full max-h-[90vh] grow flex-col overflow-y-auto rounded-b-md bg-background-bright"
animate={{ height: isExpanded ? "90vh" : "auto" }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
initial={{ height: "auto" }}
>
<ChatMessages
conversation={conversation}
isPreparingAnswer={isPreparingAnswer}
isGeneratingAnswer={isGeneratingAnswer}
onReset={handleReset}
onExampleClick={handleExampleClick}
error={error}
addFeedback={addFeedback}
/>
<form onSubmit={handleSubmit} className="flex-shrink-0 border-t border-grid-bright p-4">
<div className="flex gap-3">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Ask a question..."
disabled={isGeneratingAnswer}
autoFocus
className="flex-1 rounded-md border border-grid-bright bg-background-dimmed px-3 py-2 text-text-bright placeholder:text-text-dimmed focus-visible:focus-custom"
/>
{isGeneratingAnswer ? (
<SimpleTooltip
button={
<span
onClick={() => stopGeneration()}
className="group relative z-10 flex size-10 min-w-10 cursor-pointer items-center justify-center"
>
<StopIcon className="z-10 size-5 text-indigo-500 transition group-hover:text-indigo-400" />
<GradientSpinnerBackground
className="absolute inset-0 animate-spin"
hoverEffect
/>
</span>
}
content="Stop generating"
/>
) : isPreparingAnswer ? (
<GradientSpinnerBackground className="flex size-10 min-w-10 items-center justify-center">
<Spinner
color={{
background: "rgba(99, 102, 241, 1)",
foreground: "rgba(217, 70, 239, 1)",
}}
className="size-5"
/>
</GradientSpinnerBackground>
) : (
<Button
type="submit"
disabled={!message.trim()}
LeadingIcon={<ArrowUpIcon className="size-5 text-text-bright" />}
variant="primary/large"
className="size-10 min-w-10 rounded-full group-disabled/button:border-charcoal-550 group-disabled/button:bg-charcoal-600"
/>
)}
</div>
</form>
</motion.div>
);
}
function GradientSpinnerBackground({
children,
className,
hoverEffect = false,
}: {
children?: React.ReactNode;
className?: string;
hoverEffect?: boolean;
}) {
return (
<div
className={`flex rounded-full bg-gradient-to-br from-indigo-500 via-purple-500 to-fuchsia-500 p-px ${className}`}
>
<div
className={`flex h-full w-full items-center justify-center rounded-full bg-charcoal-600 ${
hoverEffect ? "transition group-hover:bg-charcoal-550" : ""
}`}
>
{children}
</div>
</div>
);
}
@@ -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 (
<div className="relative h-full w-full overflow-hidden">
{/* Left menu top background - fixed width 260px, maintains aspect ratio */}
<div
className="absolute left-0 top-0 w-[260px] bg-contain bg-left-top bg-no-repeat"
style={{
backgroundImage: `url(${blurredDashboardBackgroundMenuTop})`,
aspectRatio: "auto",
height: "100vh",
backgroundSize: "260px auto",
}}
/>
{/* Left menu bottom background - fixed width 260px, maintains aspect ratio */}
<div
className="absolute bottom-0 left-0 w-[260px] bg-contain bg-left-bottom bg-no-repeat"
style={{
backgroundImage: `url(${blurredDashboardBackgroundMenuBottom})`,
aspectRatio: "auto",
height: "100vh",
backgroundSize: "260px auto",
}}
/>
{/* Right table background - fixed width 2000px, positioned next to menu */}
<div
className="absolute top-0 bg-left-top bg-no-repeat"
style={{
left: "260px",
backgroundImage: `url(${blurredDashboardBackgroundTable})`,
width: "100%",
height: "100vh",
backgroundSize: "1200px auto",
backgroundColor: "#101214",
}}
/>
{/* Content layer */}
<div className="relative z-10 h-full w-full">{children}</div>
</div>
);
}
+246 -26
View File
@@ -5,26 +5,36 @@ import {
ChatBubbleLeftRightIcon,
ClockIcon,
PlusIcon,
QuestionMarkCircleIcon,
RectangleGroupIcon,
RectangleStackIcon,
ServerStackIcon,
Squares2X2Icon,
} from "@heroicons/react/20/solid";
import { useLocation } from "react-use";
import { TaskIcon } from "~/assets/icons/TaskIcon";
import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons";
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";
import selectRunsUsingFilters from "~/assets/images/select-runs-using-filters.png";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useFeatures } from "~/hooks/useFeatures";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { type MinimumEnvironment } from "~/presenters/SelectBestEnvironmentPresenter.server";
import { NewBranchPanel } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route";
import {
docsPath,
v3BillingPath,
v3CreateBulkActionPath,
v3EnvironmentPath,
v3EnvironmentVariablesPath,
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";
@@ -33,9 +43,15 @@ 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 { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon";
import { V4Badge } from "./V4Badge";
export function HasNoTasksDev() {
return (
@@ -78,26 +94,60 @@ export function HasNoTasksDev() {
export function HasNoTasksDeployed({ environment }: { environment: MinimumEnvironment }) {
return (
<InfoPanel
title={`You don't have any deployed tasks in ${environmentFullTitle(environment)}`}
icon={TaskIcon}
iconClassName="text-tasks"
panelClassName="max-w-full"
accessory={
<LinkButton
to={docsPath("deployment/overview")}
variant="docs/small"
LeadingIcon={BookOpenIcon}
>
How to deploy tasks
</LinkButton>
}
>
<Paragraph spacing variant="small">
Run the <TextLink to={docsPath("deployment/overview")}>CLI deploy command</TextLink> to
deploy your tasks to the {environmentFullTitle(environment)} environment.
</Paragraph>
</InfoPanel>
<PackageManagerProvider>
<div>
<div className="mb-6 flex items-center justify-between border-b">
<div className="mb-2 flex items-center gap-2">
<EnvironmentIcon environment={environment} className="-ml-1 size-8" />
<Header1>Deploy your tasks to {environmentFullTitle(environment)}</Header1>
</div>
<div className="flex items-center">
<SimpleTooltip
button={
<LinkButton
variant="small-menu-item"
LeadingIcon={BookOpenIcon}
leadingIconClassName="text-blue-500"
to={docsPath("deployment/overview")}
/>
}
content="Deploy docs"
/>
<SimpleTooltip
button={
<LinkButton
variant="small-menu-item"
LeadingIcon={QuestionMarkCircleIcon}
leadingIconClassName="text-blue-500"
to={docsPath("troubleshooting#deployment")}
/>
}
content="Troubleshooting docs"
/>
<AskAI />
</div>
</div>
<StepNumber stepNumber="1a" title="Run the CLI 'deploy' command" />
<StepContentContainer>
<Paragraph spacing>
This will deploy your tasks to the {environmentFullTitle(environment)} environment. Read
the <TextLink to={docsPath("deployment/overview")}>full guide</TextLink>.
</Paragraph>
<TriggerDeployStep environment={environment} />
</StepContentContainer>
<StepNumber stepNumber="1b" title="Or deploy using GitHub Actions" />
<StepContentContainer>
<Paragraph spacing>
Read the <TextLink to={docsPath("github-actions")}>GitHub Actions guide</TextLink> to
get started.
</Paragraph>
</StepContentContainer>
<StepNumber stepNumber="2" title="Waiting for tasks to deploy" displaySpinner />
<StepContentContainer>
<Paragraph>This page will automatically refresh when your tasks are deployed.</Paragraph>
</StepContentContainer>
</div>
</PackageManagerProvider>
);
}
@@ -431,7 +481,124 @@ export function NoWaitpointTokens() {
);
}
function SwitcherPanel() {
export function BranchesNoBranchableEnvironment() {
const { isManagedCloud } = useFeatures();
const organization = useOrganization();
if (!isManagedCloud) {
return (
<InfoPanel
title="Create a preview environment"
icon={BranchEnvironmentIconSmall}
iconClassName="text-preview"
panelClassName="max-w-full"
>
<Paragraph spacing variant="small">
To add branches you need to have a <InlineCode>RuntimeEnvironment</InlineCode> where{" "}
<InlineCode>isBranchableEnvironment</InlineCode> is true. We recommend creating a
dedicated one using the "PREVIEW" type.
</Paragraph>
</InfoPanel>
);
}
return (
<InfoPanel
title="Upgrade to get preview branches"
icon={BranchEnvironmentIconSmall}
iconClassName="text-preview"
panelClassName="max-w-full"
accessory={
<LinkButton variant="primary/small" to={v3BillingPath(organization)}>
Upgrade
</LinkButton>
}
>
<Paragraph spacing variant="small">
Preview branches in Trigger.dev create isolated environments for testing new features before
production.
</Paragraph>
<Paragraph variant="small">
You must be on <V4Badge inline /> to access preview branches. Read our{" "}
<TextLink to={docsPath("upgrade-to-v4")}>upgrade to v4 guide</TextLink> to learn more.
</Paragraph>
</InfoPanel>
);
}
export function BranchesNoBranches({
parentEnvironment,
limits,
canUpgrade,
}: {
parentEnvironment: { id: string };
limits: { used: number; limit: number };
canUpgrade: boolean;
}) {
const organization = useOrganization();
if (limits.used >= limits.limit) {
return (
<InfoPanel
title="Upgrade to get preview branches"
icon={BranchEnvironmentIconSmall}
iconClassName="text-preview"
panelClassName="max-w-full"
accessory={
canUpgrade ? (
<LinkButton variant="primary/small" to={v3BillingPath(organization)}>
Upgrade
</LinkButton>
) : (
<Feedback
button={<Button variant="primary/small">Request more</Button>}
defaultValue="help"
/>
)
}
>
<Paragraph spacing variant="small">
You've reached the limit ({limits.used}/{limits.limit}) of branches for your plan. Upgrade
to get branches.
</Paragraph>
</InfoPanel>
);
}
return (
<InfoPanel
title="Create your first branch"
icon={BranchEnvironmentIconSmall}
iconClassName="text-preview"
panelClassName="max-w-full"
accessory={
<NewBranchPanel
button={
<Button
variant="primary/small"
LeadingIcon={PlusIcon}
leadingIconClassName="text-white"
>
New branch
</Button>
}
parentEnvironment={parentEnvironment}
/>
}
>
<Paragraph spacing variant="small">
Branches are a way to test new features in isolation before merging them into the main
environment.
</Paragraph>
<Paragraph spacing variant="small">
Branches are only available when using <V4Badge inline /> or above. Read our{" "}
<TextLink to={docsPath("upgrade-to-v4")}>v4 upgrade guide</TextLink> to learn more.
</Paragraph>
</InfoPanel>
);
}
export function SwitcherPanel({ title = "Switch to a deployed environment" }: { title?: string }) {
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
@@ -439,7 +606,7 @@ function SwitcherPanel() {
return (
<div className="flex items-center rounded-md border border-grid-bright bg-background-bright p-3">
<Paragraph variant="small" className="grow">
Switch to a deployed environment
{title}
</Paragraph>
<EnvironmentSelector
organization={organization}
@@ -450,3 +617,56 @@ function SwitcherPanel() {
</div>
);
}
export function BulkActionsNone() {
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
return (
<div>
<div className="mb-6 flex items-center justify-between border-b pb-0.5">
<Header1 spacing>Create a bulk action</Header1>
<div className="flex items-center gap-2">
<LinkButton
variant="primary/small"
LeadingIcon={PlusIcon}
to={v3CreateBulkActionPath(organization, project, environment)}
>
New bulk action
</LinkButton>
</div>
</div>
<StepNumber stepNumber="1" title="Select runs individually" />
<StepContentContainer className="mb-4 flex flex-col gap-4">
<Paragraph>Select runs from the runs page individually.</Paragraph>
<div>
<img src={selectRunsIndividually} alt="Select runs individually" />
</div>
</StepContentContainer>
<div className="mb-5 ml-9 flex items-center gap-2">
<div className="h-px w-full bg-grid-bright" />
<Paragraph variant="extra-small" className="text-text-dimmed">
OR
</Paragraph>
<div className="h-px w-full bg-grid-bright" />
</div>
<StepNumber stepNumber="2" title="Select runs using filters" />
<StepContentContainer className="flex flex-col gap-4">
<Paragraph>
Use the filter menu on the runs page to select just the runs you want to bulk action.
</Paragraph>
<div>
<img src={selectRunsUsingFilters} alt="Select runs using filters" />
</div>
</StepContentContainer>
<StepNumber stepNumber="3" title="Open the bulk action panel" />
<StepContentContainer className="flex flex-col gap-4">
<Paragraph>Click the “Bulk actions” button in the top right of the runs page.</Paragraph>
<div>
<img src={openBulkActionsPanel} alt="Open the bulk action panel" />
</div>
</StepContentContainer>
</div>
);
}
@@ -0,0 +1,267 @@
import { z } from "zod";
import {
filterIcon,
filterTitle,
type TaskRunListSearchFilterKey,
type TaskRunListSearchFilters,
} from "./runs/v3/RunFilters";
import { Paragraph } from "./primitives/Paragraph";
import simplur from "simplur";
import { appliedSummary, dateFromString, timeFilterRenderValues } from "./runs/v3/SharedFilters";
import { formatNumber } from "~/utils/numberFormatter";
import { SpinnerWhite } from "./primitives/Spinner";
import { ArrowPathIcon, CheckIcon, XCircleIcon } from "@heroicons/react/20/solid";
import assertNever from "assert-never";
import { AppliedFilter } from "./primitives/AppliedFilter";
import { runStatusTitle } from "./runs/v3/TaskRunStatus";
import { type TaskRunStatus } from "@trigger.dev/database";
export const BulkActionMode = z.union([z.literal("selected"), z.literal("filter")]);
export type BulkActionMode = z.infer<typeof BulkActionMode>;
export const BulkActionAction = z.union([z.literal("cancel"), z.literal("replay")]);
export type BulkActionAction = z.infer<typeof BulkActionAction>;
export function BulkActionFilterSummary({
selected,
final = false,
mode,
action,
filters,
}: {
selected?: number;
final?: boolean;
mode: BulkActionMode;
action: BulkActionAction;
filters: TaskRunListSearchFilters;
}) {
switch (mode) {
case "selected":
return (
<Paragraph variant="small">
You {!final ? "have " : " "}individually selected {simplur`${selected} run[|s]`} to be{" "}
<Action action={action} />.
</Paragraph>
);
case "filter": {
const { label, valueLabel, rangeType } = timeFilterRenderValues({
from: filters.from ? dateFromString(`${filters.from}`) : undefined,
to: filters.to ? dateFromString(`${filters.to}`) : undefined,
period: filters.period,
});
return (
<div className="flex flex-col gap-2">
<Paragraph variant="small">
You {!final ? "have " : " "}selected{" "}
<span className="text-text-bright">
{final ? selected : <EstimatedCount count={selected} />}
</span>{" "}
runs to be <Action action={action} /> using these filters:
</Paragraph>
<div className="flex flex-col gap-2">
<AppliedFilter
variant="minimal/medium"
label={label}
icon={filterIcon("period")}
value={valueLabel}
removable={false}
/>
{Object.entries(filters).map(([key, value]) => {
if (!value && key !== "period") {
return null;
}
const typedKey = key as TaskRunListSearchFilterKey;
switch (typedKey) {
case "cursor":
case "direction":
case "environments":
//We need to handle time differently because we have a default
case "period":
case "from":
case "to": {
return null;
}
case "tasks": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values)}
removable={false}
/>
);
}
case "versions": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values)}
removable={false}
/>
);
}
case "statuses": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values.map((v) => runStatusTitle(v as TaskRunStatus)))}
removable={false}
/>
);
}
case "tags": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values)}
removable={false}
/>
);
}
case "bulkId": {
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={value}
removable={false}
/>
);
}
case "rootOnly": {
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={"Root only"}
icon={filterIcon(key)}
value={
value ? (
<CheckIcon className="size-4" />
) : (
<XCircleIcon className="size-4" />
)
}
removable={false}
/>
);
}
case "runId": {
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={"Run ID"}
icon={filterIcon(key)}
value={value}
removable={false}
/>
);
}
case "batchId": {
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={"Batch ID"}
icon={filterIcon(key)}
value={value}
removable={false}
/>
);
}
case "scheduleId": {
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={"Schedule ID"}
icon={filterIcon(key)}
value={value}
removable={false}
/>
);
}
case "queues": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values.map((v) => v.replace("task/", "")))}
removable={false}
/>
);
}
case "machines": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values)}
removable={false}
/>
);
}
default: {
assertNever(typedKey);
}
}
})}
</div>
</div>
);
}
}
}
function Action({ action }: { action: BulkActionAction }) {
switch (action) {
case "cancel":
return (
<span>
<XCircleIcon className="mb-0.5 inline-block size-4 text-error" />
<span className="ml-0.5 text-text-bright">Canceled</span>
</span>
);
case "replay":
return (
<span>
<ArrowPathIcon className="mb-0.5 inline-block size-4 text-blue-400" />
<span className="ml-0.5 text-text-bright">Replayed</span>
</span>
);
}
}
export function EstimatedCount({ count }: { count?: number }) {
if (typeof count === "number") {
return <>~{formatNumber(count)}</>;
}
return <SpinnerWhite className="mx-0.5 -mt-0.5 inline size-3" />;
}
@@ -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;
}
}
+17 -2
View File
@@ -1,6 +1,6 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { InformationCircleIcon } from "@heroicons/react/20/solid";
import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid";
import { EnvelopeIcon } from "@heroicons/react/24/solid";
import { Form, useActionData, useLocation, useNavigation } from "@remix-run/react";
import { type ReactNode, useEffect, useState } from "react";
@@ -64,7 +64,9 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
How can we help? We read every message and will respond as quickly as we can.
</Paragraph>
</div>
<hr className="border-charcoal-800" />
{!(type === "feature" || type === "help" || type === "concurrency") && (
<hr className="border-grid-dimmed" />
)}
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
<Fieldset className="max-w-full gap-y-3">
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
@@ -97,6 +99,19 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
</Paragraph>
</InfoPanel>
)}
{type === "concurrency" && (
<InfoPanel
icon={ArrowUpCircleIcon}
iconClassName="text-indigo-500"
panelClassName="w-full mb-2"
>
<Paragraph variant="small">
How much extra concurrency do you need? You can add bundles of 50 for
$50/month each. To help us advise you, please let us know what your tasks do,
your typical run volume, and if your workload is spiky (many runs at once).
</Paragraph>
</InfoPanel>
)}
<Select
{...conform.select(feedbackType)}
variant="tertiary/medium"
@@ -0,0 +1,86 @@
import { GitPullRequestIcon, GitCommitIcon, GitBranchIcon } from "lucide-react";
import { type GitMetaLinks } from "~/presenters/v3/BranchesPresenter.server";
import { LinkButton } from "./primitives/Buttons";
import { SimpleTooltip } from "./primitives/Tooltip";
export function GitMetadata({ git }: { git?: GitMetaLinks | null }) {
if (!git) return null;
return (
<>
{git.pullRequestUrl && git.pullRequestNumber && <GitMetadataPullRequest git={git} />}
{git.branchUrl && <GitMetadataBranch git={git} />}
{git.shortSha && <GitMetadataCommit git={git} />}
</>
);
}
export function GitMetadataBranch({
git,
}: {
git: Pick<GitMetaLinks, "branchUrl" | "branchName">;
}) {
return (
<SimpleTooltip
button={
<LinkButton
variant="minimal/small"
LeadingIcon={<GitBranchIcon className="size-4" />}
iconSpacing="gap-x-1"
to={git.branchUrl}
className="pl-1"
>
{git.branchName}
</LinkButton>
}
content="Jump to GitHub branch"
/>
);
}
export function GitMetadataCommit({
git,
}: {
git: Pick<GitMetaLinks, "commitUrl" | "shortSha" | "commitMessage">;
}) {
return (
<SimpleTooltip
button={
<LinkButton
variant="minimal/small"
to={git.commitUrl}
LeadingIcon={<GitCommitIcon className="size-4" />}
iconSpacing="gap-x-1"
className="pl-1"
>
{`${git.shortSha} / ${git.commitMessage}`}
</LinkButton>
}
content="Jump to GitHub commit"
/>
);
}
export function GitMetadataPullRequest({
git,
}: {
git: Pick<GitMetaLinks, "pullRequestUrl" | "pullRequestNumber" | "pullRequestTitle">;
}) {
if (!git.pullRequestUrl || !git.pullRequestNumber) return null;
return (
<SimpleTooltip
button={
<LinkButton
variant="minimal/small"
to={git.pullRequestUrl}
LeadingIcon={<GitPullRequestIcon className="size-4" />}
iconSpacing="gap-x-1"
className="pl-1"
>
#{git.pullRequestNumber} {git.pullRequestTitle}
</LinkButton>
}
content="Jump to GitHub pull request"
/>
);
}
@@ -1,21 +1,29 @@
import { UserMinusIcon } from "@heroicons/react/20/solid";
import { Form } from "@remix-run/react";
import { Button } from "./primitives/Buttons";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./primitives/Tooltip";
export function ImpersonationBanner() {
return (
<div className="w-full">
<Form action="/resources/impersonation" method="delete" reloadDocument className="w-full">
<Button
type="submit"
variant="small-menu-item"
LeadingIcon={UserMinusIcon}
fullWidth
textAlignLeft
className="text-amber-400"
>
Stop impersonating
</Button>
<div>
<Form action="/resources/impersonation" method="delete" reloadDocument>
<TooltipProvider disableHoverableContent={true}>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="submit"
variant="small-menu-item"
LeadingIcon={UserMinusIcon}
fullWidth
textAlignLeft
className="text-amber-400"
/>
</TooltipTrigger>
<TooltipContent side="bottom" className={"text-xs"}>
Stop impersonating
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Form>
</div>
);
+46 -40
View File
@@ -15,10 +15,40 @@ export const DirectionSchema = z.union([z.literal("forward"), z.literal("backwar
export type Direction = z.infer<typeof DirectionSchema>;
export function ListPagination({ list, className }: { list: List; className?: string }) {
const bothDisabled = !list.pagination.previous && !list.pagination.next;
return (
<div className={cn("flex items-center gap-1", className)}>
<div className={cn("flex items-center", className)}>
<PreviousButton cursor={list.pagination.previous} />
<NextButton cursor={list.pagination.next} />
<div
className={cn(
"order-2 h-6 w-px bg-charcoal-600 transition-colors peer-hover/next:bg-charcoal-550 peer-hover/prev:bg-charcoal-550",
bothDisabled && "opacity-30"
)}
/>
</div>
);
}
function PreviousButton({ cursor }: { cursor?: string }) {
const path = useCursorPath(cursor, "backward");
return (
<div className={cn("peer/prev order-1", !path && "pointer-events-none")}>
<LinkButton
to={path ?? "#"}
variant={"secondary/small"}
LeadingIcon={ChevronLeftIcon}
className={cn(
"flex items-center rounded-r-none border-r-0 pl-2 pr-[0.5625rem]",
!path && "cursor-not-allowed opacity-50"
)}
onClick={(e) => !path && e.preventDefault()}
shortcut={{ key: "j" }}
tooltip="Previous"
disabled={!path}
/>
</div>
);
}
@@ -27,45 +57,21 @@ function NextButton({ cursor }: { cursor?: string }) {
const path = useCursorPath(cursor, "forward");
return (
<LinkButton
to={path ?? "#"}
variant={"minimal/small"}
TrailingIcon={ChevronRightIcon}
trailingIconClassName="text-text-dimmed"
className={cn(
"flex items-center",
!path && "cursor-not-allowed opacity-50 group-hover/button:bg-transparent"
)}
onClick={(e) => !path && e.preventDefault()}
shortcut={{ key: "k" }}
tooltip="Next"
disabled={!path}
>
Next
</LinkButton>
);
}
function PreviousButton({ cursor }: { cursor?: string }) {
const path = useCursorPath(cursor, "backward");
return (
<LinkButton
to={path ?? "#"}
variant={"minimal/small"}
LeadingIcon={ChevronLeftIcon}
leadingIconClassName="text-text-dimmed"
className={cn(
"flex items-center",
!path && "cursor-not-allowed opacity-50 group-hover/button:bg-transparent"
)}
onClick={(e) => !path && e.preventDefault()}
shortcut={{ key: "j" }}
tooltip="Previous"
disabled={!path}
>
Prev
</LinkButton>
<div className={cn("peer/next order-3", !path && "pointer-events-none")}>
<LinkButton
to={path ?? "#"}
variant={"secondary/small"}
TrailingIcon={ChevronRightIcon}
className={cn(
"flex items-center rounded-l-none border-l-0 pl-[0.5625rem] pr-2",
!path && "cursor-not-allowed opacity-50"
)}
onClick={(e) => !path && e.preventDefault()}
shortcut={{ key: "k" }}
tooltip="Next"
disabled={!path}
/>
</div>
);
}
@@ -0,0 +1,61 @@
import { MachinePresetName } from "@trigger.dev/core/v3";
import { MachineIcon } from "~/assets/icons/MachineIcon";
import { cn } from "~/utils/cn";
export const machines = Object.values(MachinePresetName.enum);
export function MachineLabelCombo({
preset,
className,
iconClassName,
labelClassName,
}: {
preset?: MachinePresetName | null;
className?: string;
iconClassName?: string;
labelClassName?: string;
}) {
return (
<span className={cn("flex items-center gap-1", className)}>
<MachineIcon preset={preset ?? undefined} className={cn("size-5", iconClassName)} />
<MachineLabel preset={preset} className={labelClassName} />
</span>
);
}
export function MachineLabel({
preset,
className,
}: {
preset?: MachinePresetName | null;
className?: string;
}) {
return (
<span className={cn("text-text-dimmed", className)}>{formatMachinePresetName(preset)}</span>
);
}
export function formatMachinePresetName(preset?: MachinePresetName | null): string {
if (!preset) {
return "No machine yet";
}
switch (preset) {
case "micro":
return "Micro";
case "small-1x":
return "Small 1x";
case "small-2x":
return "Small 2x";
case "medium-1x":
return "Medium 1x";
case "medium-2x":
return "Medium 2x";
case "large-1x":
return "Large 1x";
case "large-2x":
return "Large 2x";
default:
return preset;
}
}
@@ -0,0 +1,63 @@
import { MachineIcon } from "~/assets/icons/MachineIcon";
import { docsPath } from "~/utils/pathBuilder";
import { LinkButton } from "./primitives/Buttons";
import { Header3 } from "./primitives/Headers";
import { Paragraph } from "./primitives/Paragraph";
import { BookOpenIcon } from "@heroicons/react/20/solid";
export function MachineTooltipInfo() {
return (
<div className="flex max-w-xs flex-col gap-4 p-1 pb-2">
<div>
<div className="mb-0.5 flex items-center gap-1.5">
<MachineIcon preset="no-machine" />
<Header3>No machine yet</Header3>
</div>
<Paragraph variant="small" className="text-text-dimmed">
The machine is set at the moment the run is dequeued.
</Paragraph>
</div>
<div>
<div className="mb-0.5 flex items-center gap-1.5">
<MachineIcon preset="micro" />
<Header3>Micro</Header3>
</div>
<Paragraph variant="small" className="text-text-dimmed">
The smallest and cheapest machine available.
</Paragraph>
</div>
<div>
<div className="mb-0.5 flex items-center gap-1.5">
<MachineIcon preset="small-1x" /> <Header3>Small 1x & 2x</Header3>
</div>
<Paragraph variant="small" className="text-text-dimmed">
Smaller machines for basic workloads. Small 1x is the default machine.
</Paragraph>
</div>
<div>
<div className="mb-0.5 flex items-center gap-1.5">
<MachineIcon preset="medium-1x" /> <Header3>Medium 1x & 2x</Header3>
</div>
<Paragraph variant="small" className="text-text-dimmed">
Medium machines for more demanding workloads.
</Paragraph>
</div>
<div>
<div className="mb-0.5 flex items-center gap-1.5">
<MachineIcon preset="large-1x" /> <Header3>Large 1x & 2x</Header3>
</div>
<Paragraph variant="small" className="text-text-dimmed">
Larger machines for the most demanding workloads such as video processing. The larger the
machine, the more expensive it is.
</Paragraph>
</div>
<LinkButton
to={docsPath("machines#machine-configurations")}
variant="docs/small"
LeadingIcon={BookOpenIcon}
>
Read docs
</LinkButton>
</div>
);
}
@@ -0,0 +1,58 @@
import { SimpleTooltip } from "~/components/primitives/Tooltip";
import { BunLogoIcon } from "~/assets/icons/BunLogoIcon";
import { NodejsLogoIcon } from "~/assets/icons/NodejsLogoIcon";
import { parseRuntime, formatRuntimeWithVersion, type NormalizedRuntime } from "~/utils/runtime";
interface RuntimeIconProps {
runtime?: string | null;
runtimeVersion?: string | null;
className?: string;
withLabel?: boolean;
}
const getIcon = (runtime: NormalizedRuntime, className: string) => {
switch (runtime) {
case "bun":
return <BunLogoIcon className={className} />;
case "node":
return <NodejsLogoIcon className={className} />;
default:
return <span className="text-text-dimmed"></span>;
}
};
export function RuntimeIcon({
runtime,
runtimeVersion,
className = "h-4 w-4",
withLabel = false,
}: RuntimeIconProps) {
const parsedRuntime = parseRuntime(runtime);
// Default to Node.js if no runtime is specified
const effectiveRuntime = parsedRuntime || {
runtime: "node" as const,
originalRuntime: "node",
displayName: "Node.js",
};
const icon = getIcon(effectiveRuntime.runtime, className);
const formattedText = formatRuntimeWithVersion(effectiveRuntime.originalRuntime, runtimeVersion);
if (withLabel) {
return (
<span className="flex items-center gap-1">
{icon}
<span>{formattedText}</span>
</span>
);
}
if (typeof icon === "object" && "type" in icon) {
return (
<SimpleTooltip button={icon} content={formattedText} side="top" disableHoverableContent />
);
}
return icon;
}
+72 -9
View File
@@ -1,6 +1,7 @@
import { createContext, useContext, useState } from "react";
import { useAppOrigin } from "~/hooks/useAppOrigin";
import { useProject } from "~/hooks/useProject";
import { useTriggerCliTag } from "~/hooks/useTriggerCliTag";
import {
ClientTabs,
ClientTabsContent,
@@ -35,8 +36,6 @@ function usePackageManager() {
return context;
}
const v3PackageTag = "latest";
function getApiUrlArg() {
const appOrigin = useAppOrigin();
@@ -69,8 +68,9 @@ export function InitCommandV3({ title }: TabsProps) {
const project = useProject();
const projectRef = project.externalRef;
const apiUrlArg = getApiUrlArg();
const triggerCliTag = useTriggerCliTag();
const initCommandParts = [`trigger.dev@${v3PackageTag}`, "init", `-p ${projectRef}`, apiUrlArg];
const initCommandParts = [`trigger.dev@${triggerCliTag}`, "init", `-p ${projectRef}`, apiUrlArg];
const initCommand = initCommandParts.filter(Boolean).join(" ");
const { activePackageManager, setActivePackageManager } = usePackageManager();
@@ -118,6 +118,7 @@ export function InitCommandV3({ title }: TabsProps) {
}
export function TriggerDevStepV3({ title }: TabsProps) {
const triggerCliTag = useTriggerCliTag();
const { activePackageManager, setActivePackageManager } = usePackageManager();
return (
@@ -139,7 +140,7 @@ export function TriggerDevStepV3({ title }: TabsProps) {
variant="secondary/medium"
iconButton
className="mb-4"
value={`npx trigger.dev@${v3PackageTag} dev`}
value={`npx trigger.dev@${triggerCliTag} dev`}
/>
</ClientTabsContent>
<ClientTabsContent value={"pnpm"}>
@@ -147,7 +148,7 @@ export function TriggerDevStepV3({ title }: TabsProps) {
variant="secondary/medium"
iconButton
className="mb-4"
value={`pnpm dlx trigger.dev@${v3PackageTag} dev`}
value={`pnpm dlx trigger.dev@${triggerCliTag} dev`}
/>
</ClientTabsContent>
<ClientTabsContent value={"yarn"}>
@@ -155,7 +156,7 @@ export function TriggerDevStepV3({ title }: TabsProps) {
variant="secondary/medium"
iconButton
className="mb-4"
value={`yarn dlx trigger.dev@${v3PackageTag} dev`}
value={`yarn dlx trigger.dev@${triggerCliTag} dev`}
/>
</ClientTabsContent>
</ClientTabs>
@@ -163,6 +164,7 @@ export function TriggerDevStepV3({ title }: TabsProps) {
}
export function TriggerLoginStepV3({ title }: TabsProps) {
const triggerCliTag = useTriggerCliTag();
const { activePackageManager, setActivePackageManager } = usePackageManager();
return (
@@ -184,7 +186,7 @@ export function TriggerLoginStepV3({ title }: TabsProps) {
variant="secondary/medium"
iconButton
className="mb-4"
value={`npx trigger.dev@${v3PackageTag} login`}
value={`npx trigger.dev@${triggerCliTag} login`}
/>
</ClientTabsContent>
<ClientTabsContent value={"pnpm"}>
@@ -192,7 +194,7 @@ export function TriggerLoginStepV3({ title }: TabsProps) {
variant="secondary/medium"
iconButton
className="mb-4"
value={`pnpm dlx trigger.dev@${v3PackageTag} login`}
value={`pnpm dlx trigger.dev@${triggerCliTag} login`}
/>
</ClientTabsContent>
<ClientTabsContent value={"yarn"}>
@@ -200,7 +202,68 @@ export function TriggerLoginStepV3({ title }: TabsProps) {
variant="secondary/medium"
iconButton
className="mb-4"
value={`yarn dlx trigger.dev@${v3PackageTag} login`}
value={`yarn dlx trigger.dev@${triggerCliTag} login`}
/>
</ClientTabsContent>
</ClientTabs>
);
}
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 (
<ClientTabs
defaultValue="npm"
value={activePackageManager}
onValueChange={setActivePackageManager}
>
<div className="flex items-center gap-4">
{title && <Header3>{title}</Header3>}
<ClientTabsList className={title ? "ml-auto" : ""}>
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
</ClientTabsList>
</div>
<ClientTabsContent value={"npm"}>
<ClipboardField
variant="secondary/medium"
iconButton
className="mb-4"
value={`npx trigger.dev@${triggerCliTag} deploy${environmentFlag}`}
/>
</ClientTabsContent>
<ClientTabsContent value={"pnpm"}>
<ClipboardField
variant="secondary/medium"
iconButton
className="mb-4"
value={`pnpm dlx trigger.dev@${triggerCliTag} deploy${environmentFlag}`}
/>
</ClientTabsContent>
<ClientTabsContent value={"yarn"}>
<ClipboardField
variant="secondary/medium"
iconButton
className="mb-4"
value={`yarn dlx trigger.dev@${triggerCliTag} deploy${environmentFlag}`}
/>
</ClientTabsContent>
</ClientTabs>
+133 -105
View File
@@ -11,6 +11,8 @@ import {
} from "./primitives/SheetV3";
import { ShortcutKey } from "./primitives/ShortcutKey";
import { Button } from "./primitives/Buttons";
import { useState } from "react";
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
export function Shortcuts() {
return (
@@ -23,121 +25,147 @@ export function Shortcuts() {
data-action="shortcuts"
fullWidth
textAlignLeft
shortcut={{ modifiers: ["shift"], key: "?" }}
shortcut={{ modifiers: ["shift"], key: "?", enabled: false }}
className="gap-x-0 pl-0.5"
iconSpacing="gap-x-0.5"
>
Shortcuts
</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>
<div className="flex items-center gap-x-2">
<Keyboard className="size-5 text-indigo-500" />
<span className="font-sans text-base font-medium text-text-bright">
Keyboard shortcuts
</span>
</div>
</SheetTitle>
<div className="space-y-6 px-4 pb-4 pt-2">
<div className="space-y-3">
<Header3>General</Header3>
<Shortcut name="Close">
<ShortcutKey shortcut={{ key: "esc" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Confirm">
<ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "enter" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Filter">
<ShortcutKey shortcut={{ key: "f" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Select filter">
<ShortcutKey shortcut={{ key: "1" }} variant="medium/bright" />
<Paragraph variant="small" className="ml-1.5">
to
</Paragraph>
<ShortcutKey shortcut={{ key: "9" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Previous page">
<ShortcutKey shortcut={{ key: "j" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Next page">
<ShortcutKey shortcut={{ key: "k" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Help & Feedback">
<ShortcutKey shortcut={{ key: "h" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Runs page</Header3>
<Shortcut name="Bulk action: Cancel runs">
<ShortcutKey shortcut={{ key: "c" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Bulk action: Replay runs">
<ShortcutKey shortcut={{ key: "r" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Bulk action: Clear selection">
<ShortcutKey shortcut={{ key: "esc" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Run page</Header3>
<Shortcut name="Replay run">
<ShortcutKey shortcut={{ key: "r" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Overview">
<ShortcutKey shortcut={{ key: "o" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Details">
<ShortcutKey shortcut={{ key: "d" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Context">
<ShortcutKey shortcut={{ key: "c" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Metadata">
<ShortcutKey shortcut={{ key: "m" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Navigate">
<ShortcutKey shortcut={{ key: "arrowup" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "arrowdown" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "arrowleft" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Expand all">
<ShortcutKey shortcut={{ key: "e" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Collapse all">
<ShortcutKey shortcut={{ key: "w" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Toggle level">
<ShortcutKey shortcut={{ key: "0" }} variant="medium/bright" />
<Paragraph variant="small" className="ml-1.5">
to
</Paragraph>
<ShortcutKey shortcut={{ key: "9" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Schedules page</Header3>
<Shortcut name="New schedule">
<ShortcutKey shortcut={{ key: "n" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Alerts page</Header3>
<Shortcut name="New alert">
<ShortcutKey shortcut={{ key: "n" }} variant="medium/bright" />
</Shortcut>
</div>
</div>
</SheetHeader>
</SheetContent>
<ShortcutContent />
</Sheet>
);
}
export function ShortcutsAutoOpen() {
const [isOpen, setIsOpen] = useState(false);
useShortcutKeys({
shortcut: { modifiers: ["shift"], key: "?" },
action: () => {
setIsOpen(true);
},
});
return (
<Sheet open={isOpen} onOpenChange={setIsOpen}>
<ShortcutContent />
</Sheet>
);
}
function ShortcutContent() {
return (
<SheetContent>
<SheetHeader>
<SheetTitle>
<div className="flex items-center gap-x-2">
<Keyboard className="size-5 text-indigo-500" />
<span className="font-sans text-base font-medium text-text-bright">
Keyboard shortcuts
</span>
</div>
</SheetTitle>
<div className="space-y-6 px-4 pb-4 pt-2">
<div className="space-y-3">
<Header3>General</Header3>
<Shortcut name="Close">
<ShortcutKey shortcut={{ key: "esc" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Confirm">
<ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "enter" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Ask AI">
<ShortcutKey shortcut={{ modifiers: ["mod"], key: "/" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Filter">
<ShortcutKey shortcut={{ key: "f" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Select filter">
<ShortcutKey shortcut={{ key: "1" }} variant="medium/bright" />
<Paragraph variant="small" className="ml-1.5">
to
</Paragraph>
<ShortcutKey shortcut={{ key: "9" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Previous page">
<ShortcutKey shortcut={{ key: "j" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Next page">
<ShortcutKey shortcut={{ key: "k" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Help & Feedback">
<ShortcutKey shortcut={{ key: "h" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Runs page</Header3>
<Shortcut name="Bulk action: Cancel runs">
<ShortcutKey shortcut={{ key: "c" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Bulk action: Replay runs">
<ShortcutKey shortcut={{ key: "r" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Bulk action: Clear selection">
<ShortcutKey shortcut={{ key: "esc" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Run page</Header3>
<Shortcut name="Replay run">
<ShortcutKey shortcut={{ key: "r" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Overview">
<ShortcutKey shortcut={{ key: "o" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Details">
<ShortcutKey shortcut={{ key: "d" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Context">
<ShortcutKey shortcut={{ key: "c" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Metadata">
<ShortcutKey shortcut={{ key: "m" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Navigate">
<ShortcutKey shortcut={{ key: "arrowup" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "arrowdown" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "arrowleft" }} variant="medium/bright" />
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Expand all">
<ShortcutKey shortcut={{ key: "e" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Collapse all">
<ShortcutKey shortcut={{ key: "w" }} variant="medium/bright" />
</Shortcut>
<Shortcut name="Toggle level">
<ShortcutKey shortcut={{ key: "0" }} variant="medium/bright" />
<Paragraph variant="small" className="ml-1.5">
to
</Paragraph>
<ShortcutKey shortcut={{ key: "9" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Schedules page</Header3>
<Shortcut name="New schedule">
<ShortcutKey shortcut={{ key: "n" }} variant="medium/bright" />
</Shortcut>
</div>
<div className="space-y-3">
<Header3>Alerts page</Header3>
<Shortcut name="New alert">
<ShortcutKey shortcut={{ key: "n" }} variant="medium/bright" />
</Shortcut>
</div>
</div>
</SheetHeader>
</SheetContent>
);
}
function Shortcut({ children, name }: { children: React.ReactNode; name: string }) {
return (
<div className="flex items-center justify-between gap-x-2">
+26
View File
@@ -0,0 +1,26 @@
import { cn } from "~/utils/cn";
import { Badge } from "./primitives/Badge";
import { SimpleTooltip } from "./primitives/Tooltip";
export function V4Badge({ inline = false, className }: { inline?: boolean; className?: string }) {
return (
<SimpleTooltip
button={
<Badge variant="extra-small" className={cn(inline ? "inline-grid" : "", className)}>
V4
</Badge>
}
content="This feature is only available in V4 and above."
disableHoverableContent
/>
);
}
export function V4Title({ children }: { children: React.ReactNode }) {
return (
<>
<span>{children}</span>
<V4Badge />
</>
);
}
+61 -1
View File
@@ -66,7 +66,15 @@ function DebugRunContent({ friendlyId }: { friendlyId: string }) {
);
}
function DebugRunData({
function DebugRunData(props: UseDataFunctionReturn<typeof loader>) {
if (props.engine === "V1") {
return <DebugRunDataEngineV1 {...props} />;
}
return <DebugRunDataEngineV2 {...props} />;
}
function DebugRunDataEngineV1({
run,
queueConcurrencyLimit,
queueCurrentConcurrency,
@@ -338,3 +346,55 @@ function DebugRunData({
</Property.Table>
);
}
function DebugRunDataEngineV2({
run,
queueConcurrencyLimit,
queueCurrentConcurrency,
envConcurrencyLimit,
envCurrentConcurrency,
keys,
}: UseDataFunctionReturn<typeof loader>) {
return (
<Property.Table>
<Property.Item>
<Property.Label>ID</Property.Label>
<Property.Value className="flex items-center gap-2">
<ClipboardField value={run.id} variant="tertiary/small" iconButton />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Queue current concurrency</Property.Label>
<Property.Value className="flex items-center gap-2">
<span>{queueCurrentConcurrency ?? "0"}</span>
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Queue concurrency limit</Property.Label>
<Property.Value className="flex items-center gap-2">
<span>{queueConcurrencyLimit ?? "Not set"}</span>
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Env current concurrency</Property.Label>
<Property.Value className="flex items-center gap-2">
<span>{envCurrentConcurrency ?? "0"}</span>
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Env concurrency limit</Property.Label>
<Property.Value className="flex items-center gap-2">
<span>{envConcurrencyLimit ?? "Not set"}</span>
</Property.Value>
</Property.Item>
{keys.map((key) => (
<Property.Item>
<Property.Label>{key.label}</Property.Label>
<Property.Value className="flex items-center gap-2">
<ClipboardField value={key.key} variant="tertiary/small" iconButton />
</Property.Value>
</Property.Item>
))}
</Property.Table>
);
}
@@ -30,8 +30,8 @@ export function UpgradePrompt() {
<Icon icon={ExclamationCircleIcon} className="h-5 w-5 text-error" />
<Paragraph variant="small" className="text-error">
You have exceeded the monthly $
{(plan.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100} free credits. No runs
will execute in Prod until{" "}
{(plan.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100} free credits. Existing
runs will be queued and new runs won't be created until{" "}
<DateTime date={nextMonth} includeTime={false} timeZone="utc" />, or you upgrade.
</Paragraph>
</div>
+72 -38
View File
@@ -3,11 +3,13 @@ import { Clipboard, ClipboardCheck } from "lucide-react";
import type { Language, PrismTheme } from "prism-react-renderer";
import { Highlight, Prism } from "prism-react-renderer";
import { forwardRef, ReactNode, useCallback, useEffect, useState } from "react";
import { TextWrapIcon } from "~/assets/icons/TextWrapIcon";
import { cn } from "~/utils/cn";
import { Button } from "../primitives/Buttons";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../primitives/Dialog";
import { Paragraph } from "../primitives/Paragraph";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
import { TextInlineIcon } from "~/assets/icons/TextInlineIcon";
//This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx
//it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting
@@ -31,6 +33,9 @@ type CodeBlockProps = {
/** Show copy to clipboard button */
showCopyButton?: boolean;
/** Show text wrapping button */
showTextWrapping?: boolean;
/** Display line numbers */
showLineNumbers?: boolean;
@@ -81,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: {
@@ -146,12 +157,6 @@ const defaultTheme: PrismTheme = {
color: "#F85149",
},
},
{
types: ["boolean"],
style: {
color: "#9B99FF",
},
},
{
types: ["char"],
style: {
@@ -183,6 +188,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
(
{
showCopyButton = true,
showTextWrapping = false,
showLineNumbers = true,
showOpenInModal = true,
highlightedRanges,
@@ -202,6 +208,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
const [copied, setCopied] = useState(false);
const [modalCopied, setModalCopied] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isWrapped, setIsWrapped] = useState(false);
const onCopied = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
@@ -263,6 +270,25 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
showChrome ? "right-1.5 top-1.5" : "top-2.5"
)}
>
{showTextWrapping && (
<TooltipProvider>
<Tooltip disableHoverableContent>
<TooltipTrigger
onClick={() => setIsWrapped(!isWrapped)}
className="transition-colors focus-custom hover:cursor-pointer hover:text-text-bright"
>
{isWrapped ? (
<TextInlineIcon className="size-4" />
) : (
<TextWrapIcon className="size-4" />
)}
</TooltipTrigger>
<TooltipContent side="left" className="text-xs">
{isWrapped ? "Unwrap" : "Wrap"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{showCopyButton && (
<TooltipProvider>
<Tooltip open={copied || mouseOver} disableHoverableContent>
@@ -311,16 +337,27 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
maxLineWidth={maxLineWidth}
className="px-2 py-3"
preClassName="text-xs"
isWrapped={isWrapped}
/>
) : (
<div
dir="ltr"
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
className={cn(
"px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
!isWrapped && "overflow-x-auto",
isWrapped && "overflow-y-auto"
)}
style={{
maxHeight,
}}
>
<pre className="relative mr-2 p-2 font-mono text-xs leading-relaxed" dir="ltr">
<pre
className={cn(
"relative mr-2 p-2 font-mono text-xs leading-relaxed",
isWrapped && "[&_span]:whitespace-pre-wrap [&_span]:break-words"
)}
dir="ltr"
>
{code}
</pre>
</div>
@@ -355,6 +392,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
maxLineWidth={maxLineWidth}
className="min-h-full"
preClassName="text-sm"
isWrapped={isWrapped}
/>
) : (
<div
@@ -410,6 +448,7 @@ type HighlightCodeProps = {
maxLineWidth?: number;
className?: string;
preClassName?: string;
isWrapped: boolean;
};
function HighlightCode({
@@ -421,11 +460,11 @@ function HighlightCode({
maxLineWidth,
className,
preClassName,
isWrapped,
}: HighlightCodeProps) {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
// This ensures the language definitions are loaded
Promise.all([
//@ts-ignore
import("prismjs/components/prism-json"),
@@ -434,16 +473,23 @@ function HighlightCode({
]).then(() => setIsLoaded(true));
}, []);
const containerClasses = cn(
"px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
!isWrapped && "overflow-x-auto",
isWrapped && "overflow-y-auto",
className
);
const preClasses = cn(
"relative mr-2 font-mono leading-relaxed",
preClassName,
isWrapped && "[&_span]:whitespace-pre-wrap [&_span]:break-words"
);
if (!isLoaded) {
return (
<div
dir="ltr"
className={cn(
"overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
className
)}
>
<pre className={cn("relative mr-2 font-mono leading-relaxed", preClassName)}>{code}</pre>
<div dir="ltr" className={containerClasses}>
<pre className={preClasses}>{code}</pre>
</div>
);
}
@@ -457,22 +503,8 @@ function HighlightCode({
getLineProps,
getTokenProps,
}) => (
<div
dir="ltr"
className={cn(
"overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
className
)}
>
<pre
className={cn(
"relative mr-2 font-mono leading-relaxed",
inheritedClassName,
preClassName
)}
style={inheritedStyle}
dir="ltr"
>
<div dir="ltr" className={containerClasses}>
<pre className={cn(preClasses, inheritedClassName)} style={inheritedStyle} dir="ltr">
{tokens
.map((line, index) => {
if (index === tokens.length - 1 && line.length === 1 && line[0].content === "\n") {
@@ -495,7 +527,8 @@ function HighlightCode({
{...lineProps}
className={cn(
"flex w-full justify-start transition-opacity duration-500",
lineProps.className
lineProps.className,
isWrapped && "flex-wrap"
)}
style={{
opacity: shouldDim ? dimAmount : undefined,
@@ -504,9 +537,10 @@ function HighlightCode({
>
{showLineNumbers && (
<div
className={
"mr-2 flex-none select-none text-right text-charcoal-500 transition-opacity duration-500"
}
className={cn(
"mr-2 flex-none select-none text-right text-charcoal-500 transition-opacity duration-500",
isWrapped && "sticky left-0"
)}
style={{
width: `calc(8 * ${(maxLineWidth as number) / 16}rem)`,
}}
+40 -4
View File
@@ -1,5 +1,5 @@
import { json as jsonLang } from "@codemirror/lang-json";
import type { ViewUpdate } from "@codemirror/view";
import { json as jsonLang, jsonParseLinter } from "@codemirror/lang-json";
import type { EditorView, ViewUpdate } from "@codemirror/view";
import { CheckIcon, ClipboardIcon, TrashIcon } from "@heroicons/react/20/solid";
import type { ReactCodeMirrorProps, UseCodeMirror } from "@uiw/react-codemirror";
import { useCodeMirror } from "@uiw/react-codemirror";
@@ -8,6 +8,7 @@ import { cn } from "~/utils/cn";
import { Button } from "../primitives/Buttons";
import { getEditorSetup } from "./codeMirrorSetup";
import { darkTheme } from "./codeMirrorTheme";
import { linter, lintGutter, type Diagnostic } from "@codemirror/lint";
export interface JSONEditorProps extends Omit<ReactCodeMirrorProps, "onBlur"> {
defaultValue?: string;
@@ -18,18 +19,36 @@ export interface JSONEditorProps extends Omit<ReactCodeMirrorProps, "onBlur"> {
onBlur?: (code: string) => void;
showCopyButton?: boolean;
showClearButton?: boolean;
linterEnabled?: boolean;
allowEmpty?: boolean;
additionalActions?: React.ReactNode;
}
const languages = {
json: jsonLang,
};
function emptyAwareJsonLinter() {
return (view: EditorView): Diagnostic[] => {
const content = view.state.doc.toString().trim();
// return no errors if content is empty
if (!content) {
return [];
}
return jsonParseLinter()(view);
};
}
type JSONEditorDefaultProps = Partial<JSONEditorProps>;
const defaultProps: JSONEditorDefaultProps = {
language: "json",
readOnly: true,
basicSetup: false,
linterEnabled: true,
allowEmpty: true,
};
export function JSONEditor(opts: JSONEditorProps) {
@@ -44,6 +63,9 @@ export function JSONEditor(opts: JSONEditorProps) {
autoFocus,
showCopyButton = true,
showClearButton = true,
linterEnabled,
allowEmpty,
additionalActions,
} = {
...defaultProps,
...opts,
@@ -56,6 +78,19 @@ export function JSONEditor(opts: JSONEditorProps) {
extensions.push(languageExtension());
if (linterEnabled) {
extensions.push(lintGutter());
switch (language) {
case "json": {
extensions.push(allowEmpty ? linter(emptyAwareJsonLinter()) : linter(jsonParseLinter()));
break;
}
default:
language satisfies never;
}
}
const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
@@ -90,13 +125,13 @@ export function JSONEditor(opts: JSONEditorProps) {
}
}, [defaultValue, view]);
const clear = useCallback(() => {
const clear = () => {
if (view === undefined) return;
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: undefined },
});
onChange?.("");
}, [view]);
};
const copy = useCallback(() => {
if (view === undefined) return;
@@ -119,6 +154,7 @@ export function JSONEditor(opts: JSONEditorProps) {
>
{showButtons && (
<div className="mx-3 flex items-center justify-end gap-2 border-b border-grid-dimmed">
{additionalActions && additionalActions}
{showClearButton && (
<Button
type="button"
@@ -1,8 +1,7 @@
import { closeBrackets } from "@codemirror/autocomplete";
import { indentWithTab } from "@codemirror/commands";
import { jsonParseLinter } from "@codemirror/lang-json";
import { bracketMatching } from "@codemirror/language";
import { lintGutter, lintKeymap, linter } from "@codemirror/lint";
import { lintKeymap } from "@codemirror/lint";
import { highlightSelectionMatches } from "@codemirror/search";
import { Prec, type Extension } from "@codemirror/state";
import {
@@ -21,8 +20,6 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
dropCursor(),
bracketMatching(),
closeBrackets(),
lintGutter(),
linter(jsonParseLinter()),
Prec.highest(
keymap.of([
{

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