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/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..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
@@ -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() {
+ 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.
+
+
+
+
+
);
}
diff --git a/docs/_snippets/manual-setup-express.mdx b/docs/_snippets/manual-setup-express.mdx
index 9ea095ca7f..339fb5cd91 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 Express is running on 0.0.0.0: `--hostname 0.0.0.0`.
+
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..53eccb44d3 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 = `${resolvedOptions.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..28641f570e
--- /dev/null
+++ b/packages/cli/src/frameworks/express/index.ts
@@ -0,0 +1,51 @@
+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";
+
+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 {
+ logger.info(
+ boxen(
+ "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" }
+ )
+ );
+ }
+
+ 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/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"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5f80835f2f..f9990b2b9a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -639,6 +639,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
@@ -670,6 +671,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
@@ -14639,7 +14641,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
@@ -19183,18 +19185,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: