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: