From df3e8d7d5a961feda6d3907e0649d263d235fa42 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 16:16:17 +0100 Subject: [PATCH 1/9] Manual setup docs --- docs/_snippets/manual-setup-express.mdx | 194 +++++++++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/docs/_snippets/manual-setup-express.mdx b/docs/_snippets/manual-setup-express.mdx index 9ea095ca7f..207f7813fa 100644 --- a/docs/_snippets/manual-setup-express.mdx +++ b/docs/_snippets/manual-setup-express.mdx @@ -1 +1,193 @@ -We're in the process of building support for the Express framework. You can follow along with progress or contribute via [this GitHub issue](https://github.com/triggerdotdev/trigger.dev/issues). +## Installing Required Packages + +Start by installing the necessary packages in your Express.js project directory. You can use npm, pnpm, or yarn as your package manager. + + + +```bash npm +npm install @trigger.dev/sdk @trigger.dev/express +``` + +```bash pnpm +pnpm install @trigger.dev/sdk @trigger.dev/express +``` + +```bash yarn +yarn add @trigger.dev/sdk @trigger.dev/express +``` + + + +
+ +Ensure that you execute this command within a Express project. + +## Obtaining the Development Server API Key + +To locate your development Server API key, login to the [Trigger.dev +dashboard](https://cloud.trigger.dev) and select the Project you want to +connect to. Then click on the Environments & API Keys tab in the left menu. +You can copy your development Server API Key from the field at the top of this page. +(Your development key will start with `tr_dev_`). + +## Adding Environment Variables + +Create a `.env` file at the root of your project and include your Trigger API key and URL like this: + +```bash +TRIGGER_API_KEY=ENTER_YOUR_DEVELOPMENT_API_KEY_HERE +TRIGGER_API_URL=https://api.trigger.dev # this is only necessary if you are self-hosting +``` + +Replace `ENTER_YOUR_DEVELOPMENT_API_KEY_HERE` with the actual API key obtained from the previous step. + +## Configuring the Trigger Client + +Create a file for your Trigger client, in this case we create it at `/trigger.(ts/js)` + +```ts trigger.(ts/js) +import { TriggerClient } from "@trigger.dev/sdk"; + +export const client = new TriggerClient({ + id: "my-app", + apiKey: process.env.TRIGGER_API_KEY!, + apiUrl: process.env.TRIGGER_API_URL!, +}); +``` + +Replace **"my-app"** with an appropriate identifier for your project. + +## Adding the API endpoint + +There are a few different options depending on how your Express project is configured. + +- App middleware +- Entire app for Trigger.dev (only relevant if it's the only thing your project is for) + +Select the appropriate code example from below: + + + +```typescript app middleware +//import the client from the other file +import { client } from "./trigger"; +import { createMiddleware } from "@trigger.dev/express"; + +//import your job files +import "./jobs/example"; + +//..your existing Express code +const app: Express = express(); + +//add the middleware +app.use(createMiddleware(client)); + +//..the rest of your Express code +``` + +```typescript entire app +//if the entire app is just for Trigger.dev +import { client } from "./trigger"; +import { createExpressServer } from "@trigger.dev/express"; + +//import your job files +import "./jobs/example"; + +//this creates an app +createExpressServer(client); +``` + + + +## Creating the Example Job + +Create a Job file. In this case created `/jobs/example.(ts/js)` + +```typescript jobs/example.(ts/js) +import { eventTrigger } from "@trigger.dev/sdk"; +import { client } from "../trigger"; + +// your first job +client.defineJob({ + id: "example-job", + name: "Example Job", + version: "0.0.1", + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + await io.logger.info("Hello world!", { payload }); + + return { + message: "Hello world!", + }; + }, +}); +``` + +## Adding Configuration to `package.json` + +Inside the `package.json` file, add the following configuration under the root object: + +```json +"trigger.dev": { + "endpointId": "my-app" +} + +``` + +Replace **"my-app"** with the appropriate identifier you used in the trigger.js configuration file. + +## Running + +### Run your Express app + +Run your Express app locally, like you normally would. For example: + + + +```bash npm +npm run dev +``` + +```bash pnpm +pnpm run dev +``` + +```bash yarn +yarn run dev +``` + + + +You might use `npm run start` instead of dev + +### Run the CLI 'dev' command + +In a **_separate terminal window or tab_** run: + + + +```bash npm +npx @trigger.dev/cli@latest dev +``` + +```bash pnpm +pnpm dlx @trigger.dev/cli@latest dev +``` + +```bash yarn +yarn dlx @trigger.dev/cli@latest dev +``` + + +
+ + You can optionally pass the port if you're not running on 3000 by adding + `--port 3001` to the end + + + + You can optionally pass the hostname if you're not running on localhost by adding + `--hostname `. Example, in case your Remix is running on 0.0.0.0: `--hostname 0.0.0.0`. + From 75dfd88eef2b03e327198ce4b5cff5dfd6728c96 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 16:23:34 +0100 Subject: [PATCH 2/9] Added the onboarding --- .../frameworks/FrameworkSelector.tsx | 2 +- .../route.tsx | 117 ++++++++++++++++-- 2 files changed, 107 insertions(+), 12 deletions(-) diff --git a/apps/webapp/app/components/frameworks/FrameworkSelector.tsx b/apps/webapp/app/components/frameworks/FrameworkSelector.tsx index 1e8fd1adb0..fb25c88a80 100644 --- a/apps/webapp/app/components/frameworks/FrameworkSelector.tsx +++ b/apps/webapp/app/components/frameworks/FrameworkSelector.tsx @@ -51,7 +51,7 @@ export function FrameworkSelector() { - + diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx index f500087cd6..e7fc44528a 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx @@ -1,21 +1,116 @@ -import { ExpressLogo } from "~/assets/logos/ExpressLogo"; -import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon"; +import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid"; +import invariant from "tiny-invariant"; +import { Feedback } from "~/components/Feedback"; +import { PageGradient } from "~/components/PageGradient"; +import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands"; +import { StepContentContainer } from "~/components/StepContentContainer"; +import { InlineCode } from "~/components/code/InlineCode"; import { BreadcrumbLink } from "~/components/navigation/NavBar"; +import { Badge } from "~/components/primitives/Badge"; +import { Button, LinkButton } from "~/components/primitives/Buttons"; +import { Callout } from "~/components/primitives/Callout"; +import { ClipboardField } from "~/components/primitives/ClipboardField"; +import { Header1 } from "~/components/primitives/Headers"; +import { Paragraph } from "~/components/primitives/Paragraph"; +import { StepNumber } from "~/components/primitives/StepNumber"; +import { useAppOrigin } from "~/hooks/useAppOrigin"; +import { useDevEnvironment } from "~/hooks/useEnvironments"; +import { useOrganization } from "~/hooks/useOrganizations"; +import { useProject } from "~/hooks/useProject"; +import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete"; import { Handle } from "~/utils/handle"; -import { trimTrailingSlash } from "~/utils/pathBuilder"; +import { projectSetupPath, trimTrailingSlash } from "~/utils/pathBuilder"; export const handle: Handle = { breadcrumb: (match) => , }; -export default function Page() { +export default function SetUpRemix() { + const organization = useOrganization(); + const project = useProject(); + useProjectSetupComplete(); + const devEnvironment = useDevEnvironment(); + invariant(devEnvironment, "Dev environment must be defined"); + const appOrigin = useAppOrigin(); + return ( - - - + +
+
+ + Get setup in 5 minutes + +
+ + Choose a different framework + + + I'm stuck! + + } + defaultValue="help" + /> +
+
+
+ + Trigger.dev has full support for serverless. We will be adding support for long-running + servers soon. + +
+ + + Copy your server API Key to your clipboard: +
+ Server} + /> +
+ Now follow this guide: + + Manual installation guide + +
+ + + + + You may be using the `start` script instead, in which case substitute `dev` in the + above commands. + + + + + + + + + This page will automatically refresh. + +
+
+
+
); } From 14d59590b4bffa4eb5299ad990002e46db7c9d44 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 16:57:31 +0100 Subject: [PATCH 3/9] The emails package now works with Node > 18 --- packages/emails/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/emails/package.json b/packages/emails/package.json index ccdfd6e96b..8f2ce6a455 100644 --- a/packages/emails/package.json +++ b/packages/emails/package.json @@ -34,6 +34,6 @@ "typescript": "^4.9.4" }, "engines": { - "node": ">=18.0.0 <19.0.0" + "node": ">=18.0.0" } } From a15dfb07bb5d99bac7f83b218cf28041ffdf1473 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 16:57:49 +0100 Subject: [PATCH 4/9] Added CLi support for Express, including custom init command finished messages --- .changeset/strong-lies-burn.md | 5 ++ packages/cli/package.json | 1 + packages/cli/src/commands/init.ts | 12 +++-- .../src/frameworks/express/express.test.ts | 28 +++++++++++ packages/cli/src/frameworks/express/index.ts | 48 +++++++++++++++++++ packages/cli/src/frameworks/index.ts | 6 ++- pnpm-lock.yaml | 16 ++----- 7 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 .changeset/strong-lies-burn.md create mode 100644 packages/cli/src/frameworks/express/express.test.ts create mode 100644 packages/cli/src/frameworks/express/index.ts diff --git a/.changeset/strong-lies-burn.md b/.changeset/strong-lies-burn.md new file mode 100644 index 0000000000..a49945bced --- /dev/null +++ b/.changeset/strong-lies-burn.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/cli": patch +--- + +Added Express support to the CLI diff --git a/packages/cli/package.json b/packages/cli/package.json index 387d55264c..67fd629b64 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -57,6 +57,7 @@ }, "dependencies": { "@types/degit": "^2.8.3", + "boxen": "^7.1.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", "commander": "^9.4.1", diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 226d70c57b..cbbd6ab38b 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -129,18 +129,20 @@ export const initCommand = async (options: InitCommandOptions) => { await addConfigurationToPackageJson(resolvedPath, resolvedOptions); - await printNextSteps(resolvedOptions, authorizedKey, packageManager, framework); + const projectUrl = `${options.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`; + if (framework.printInstallationComplete) { + await framework.printInstallationComplete(projectUrl); + } else { + await printNextSteps(projectUrl, packageManager, framework); + } telemetryClient.init.completed(resolvedOptions); }; async function printNextSteps( - options: ResolvedOptions, - authorizedKey: WhoamiResponse, + projectUrl: string, packageManager: PackageManager, framework: Framework ) { - const projectUrl = `${options.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`; - logger.success(`✔ Successfully initialized Trigger.dev!`); logger.info("Next steps:"); diff --git a/packages/cli/src/frameworks/express/express.test.ts b/packages/cli/src/frameworks/express/express.test.ts new file mode 100644 index 0000000000..bfd7c4dc74 --- /dev/null +++ b/packages/cli/src/frameworks/express/express.test.ts @@ -0,0 +1,28 @@ +import mock from "mock-fs"; +import { Express } from "."; +import { getFramework } from ".."; +import { pathExists } from "../../utils/fileSystem"; + +afterEach(() => { + mock.restore(); +}); + +describe("Express project detection", () => { + test("has dependency", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { express: "1.0.0" } }), + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).toEqual("express"); + }); + + test("no dependency", async () => { + mock({ + "package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }), + }); + + const framework = await getFramework("", "npm"); + expect(framework?.id).not.toEqual("express"); + }); +}); diff --git a/packages/cli/src/frameworks/express/index.ts b/packages/cli/src/frameworks/express/index.ts new file mode 100644 index 0000000000..2e70c8dd07 --- /dev/null +++ b/packages/cli/src/frameworks/express/index.ts @@ -0,0 +1,48 @@ +import { Framework, ProjectInstallOptions } from ".."; +import { InstallPackage } from "../../utils/addDependencies"; +import { PackageManager } from "../../utils/getUserPkgManager"; +import { readPackageJson } from "../../utils/readPackageJson"; +import { standardWatchFilePaths } from "../watchConfig"; +import boxen from "boxen"; + +export class Express implements Framework { + id = "express"; + name = "Express"; + + async isMatch(path: string, packageManager: PackageManager): Promise { + //check for the express package + const packageJsonContent = await readPackageJson(path); + if (packageJsonContent?.dependencies?.express) { + return true; + } + + return false; + } + + async dependencies(): Promise { + return [ + { name: "@trigger.dev/sdk", tag: "latest" }, + { name: "@trigger.dev/express", tag: "latest" }, + ]; + } + + possibleEnvFilenames(): string[] { + return [".env"]; + } + + async install(path: string, { typescript, endpointSlug }: ProjectInstallOptions): Promise {} + + async postInstall(path: string, options: ProjectInstallOptions): Promise {} + + async printInstallationComplete(projectUrl: string): Promise { + boxen( + "Automatic installation isn't currently support for Express. \nFollow the steps in our manual installation guide: https://trigger.dev/docs/documentation/guides/manual/express", + { padding: 1, margin: 1, borderStyle: "double", borderColor: "magenta" } + ); + } + + defaultHostnames = ["localhost", "[::]"]; + defaultPorts = [3000, 8000, 80, 8080]; + watchFilePaths = standardWatchFilePaths; + watchIgnoreRegex = /(node_modules)/; +} diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts index 69ba77a476..00b9831127 100644 --- a/packages/cli/src/frameworks/index.ts +++ b/packages/cli/src/frameworks/index.ts @@ -1,6 +1,7 @@ import { InstallPackage } from "../utils/addDependencies"; import { PackageManager } from "../utils/getUserPkgManager"; import { Astro } from "./astro"; +import { Express } from "./express"; import { NextJs } from "./nextjs"; import { Remix } from "./remix"; @@ -32,6 +33,9 @@ export interface Framework { /** You can check for middleware, add extra instructions, etc */ postInstall(path: string, options: ProjectInstallOptions): Promise; + /** You can (optionally) override the initComplete messages */ + printInstallationComplete?(projectUrl: string): Promise; + /** Used by the dev command, if a hostname isn't passed in */ defaultHostnames: string[]; @@ -46,7 +50,7 @@ export interface Framework { } /** The order of these matters. The first one that matches the folder will be used, so stricter ones should be first. */ -const frameworks: Framework[] = [new NextJs(), new Remix(), new Astro()]; +const frameworks: Framework[] = [new NextJs(), new Remix(), new Astro(), new Express()]; export const getFramework = async ( path: string, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8624a101f8..4add75334c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -641,6 +641,7 @@ importers: '@types/mock-fs': ^4.13.1 '@types/node': '16' '@types/node-fetch': ^2.6.2 + boxen: ^7.1.1 chalk: ^5.2.0 chokidar: ^3.5.3 commander: ^9.4.1 @@ -672,6 +673,7 @@ importers: zod: 3.21.4 dependencies: '@types/degit': 2.8.3 + boxen: 7.1.1 chalk: 5.2.0 chokidar: 3.5.3 commander: 9.5.0 @@ -14641,7 +14643,7 @@ packages: /axios/0.21.4_debug@4.3.2: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2_debug@4.3.2 + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: false @@ -19190,18 +19192,6 @@ packages: debug: optional: true - /follow-redirects/1.15.2_debug@4.3.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dependencies: - debug: 4.3.2 - dev: false - /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: From 2b20683af6e6a0a7339a453c0ceb38dc9dc9c0b9 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 17:14:49 +0100 Subject: [PATCH 5/9] =?UTF-8?q?Need=20to=20actually=20log=20out=20the=20in?= =?UTF-8?q?stallation=20complete=20message=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/frameworks/express/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/frameworks/express/index.ts b/packages/cli/src/frameworks/express/index.ts index 2e70c8dd07..811710739b 100644 --- a/packages/cli/src/frameworks/express/index.ts +++ b/packages/cli/src/frameworks/express/index.ts @@ -1,6 +1,7 @@ import { Framework, ProjectInstallOptions } from ".."; import { InstallPackage } from "../../utils/addDependencies"; import { PackageManager } from "../../utils/getUserPkgManager"; +import { logger } from "../../utils/logger"; import { readPackageJson } from "../../utils/readPackageJson"; import { standardWatchFilePaths } from "../watchConfig"; import boxen from "boxen"; @@ -35,9 +36,11 @@ export class Express implements Framework { async postInstall(path: string, options: ProjectInstallOptions): Promise {} async printInstallationComplete(projectUrl: string): Promise { - boxen( - "Automatic installation isn't currently support for Express. \nFollow the steps in our manual installation guide: https://trigger.dev/docs/documentation/guides/manual/express", - { padding: 1, margin: 1, borderStyle: "double", borderColor: "magenta" } + logger.info( + boxen( + "Automatic installation isn't currently support for Express. \nFollow the steps in our manual installation guide: https://trigger.dev/docs/documentation/guides/manual/express", + { padding: 1, margin: 1, borderStyle: "double", borderColor: "magenta" } + ) ); } From 004ef500abe5f048d9eb6657686e3633f3ad8cb4 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 17:22:23 +0100 Subject: [PATCH 6/9] Fix for an old Remix reference --- docs/_snippets/manual-setup-express.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_snippets/manual-setup-express.mdx b/docs/_snippets/manual-setup-express.mdx index 207f7813fa..339fb5cd91 100644 --- a/docs/_snippets/manual-setup-express.mdx +++ b/docs/_snippets/manual-setup-express.mdx @@ -189,5 +189,5 @@ yarn dlx @trigger.dev/cli@latest dev You can optionally pass the hostname if you're not running on localhost by adding - `--hostname `. Example, in case your Remix is running on 0.0.0.0: `--hostname 0.0.0.0`. + `--hostname `. Example, in case your Express is running on 0.0.0.0: `--hostname 0.0.0.0`. From 866eebea3587b3e2139fd3c20c7fc5991d8301ec Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 17:23:12 +0100 Subject: [PATCH 7/9] Renamed the page export --- .../route.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx index e7fc44528a..b787ef58a3 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.express/route.tsx @@ -25,7 +25,7 @@ export const handle: Handle = { breadcrumb: (match) => , }; -export default function SetUpRemix() { +export default function Page() { const organization = useOrganization(); const project = useProject(); useProjectSetupComplete(); From 1a9c98622f1ab1dc26ba121ab367b7ae668a9333 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 17:25:13 +0100 Subject: [PATCH 8/9] Use resolvedOptions.triggerUrl --- packages/cli/src/commands/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index cbbd6ab38b..53eccb44d3 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -129,7 +129,7 @@ export const initCommand = async (options: InitCommandOptions) => { await addConfigurationToPackageJson(resolvedPath, resolvedOptions); - const projectUrl = `${options.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`; + const projectUrl = `${resolvedOptions.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`; if (framework.printInstallationComplete) { await framework.printInstallationComplete(projectUrl); } else { From 2666e88c713e8e2342ef5b35cbafb637731f3e38 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 27 Sep 2023 17:40:15 +0100 Subject: [PATCH 9/9] Typo in manual instructions --- packages/cli/src/frameworks/express/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/frameworks/express/index.ts b/packages/cli/src/frameworks/express/index.ts index 811710739b..28641f570e 100644 --- a/packages/cli/src/frameworks/express/index.ts +++ b/packages/cli/src/frameworks/express/index.ts @@ -38,7 +38,7 @@ export class Express implements Framework { async printInstallationComplete(projectUrl: string): Promise { logger.info( boxen( - "Automatic installation isn't currently support for Express. \nFollow the steps in our manual installation guide: https://trigger.dev/docs/documentation/guides/manual/express", + "Automatic installation isn't currently supported for Express. \nFollow the steps in our manual installation guide: https://trigger.dev/docs/documentation/guides/manual/express", { padding: 1, margin: 1, borderStyle: "double", borderColor: "magenta" } ) );