Skip to content

Commit 2397fcb

Browse files
authored
Express frameworks docs + CLI (#512)
* Manual setup docs * Added the onboarding * The emails package now works with Node > 18 * Added CLi support for Express, including custom init command finished messages * Need to actually log out the installation complete message… * Fix for an old Remix reference * Renamed the page export * Use resolvedOptions.triggerUrl * Typo in manual instructions
1 parent 35d0c2a commit 2397fcb

File tree

11 files changed

+400
-32
lines changed

11 files changed

+400
-32
lines changed

.changeset/strong-lies-burn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/cli": patch
3+
---
4+
5+
Added Express support to the CLI

apps/webapp/app/components/frameworks/FrameworkSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function FrameworkSelector() {
5151
<FrameworkLink to={projectSetupNextjsPath(organization, project)} supported>
5252
<NextjsLogo className="w-32" />
5353
</FrameworkLink>
54-
<FrameworkLink to={projectSetupExpressPath(organization, project)}>
54+
<FrameworkLink to={projectSetupExpressPath(organization, project)} supported>
5555
<ExpressLogo className="w-36" />
5656
</FrameworkLink>
5757
<FrameworkLink to={projectSetupRemixPath(organization, project)} supported>
Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,116 @@
1-
import { ExpressLogo } from "~/assets/logos/ExpressLogo";
2-
import { FrameworkComingSoon } from "~/components/frameworks/FrameworkComingSoon";
1+
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
2+
import invariant from "tiny-invariant";
3+
import { Feedback } from "~/components/Feedback";
4+
import { PageGradient } from "~/components/PageGradient";
5+
import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
6+
import { StepContentContainer } from "~/components/StepContentContainer";
7+
import { InlineCode } from "~/components/code/InlineCode";
38
import { BreadcrumbLink } from "~/components/navigation/NavBar";
9+
import { Badge } from "~/components/primitives/Badge";
10+
import { Button, LinkButton } from "~/components/primitives/Buttons";
11+
import { Callout } from "~/components/primitives/Callout";
12+
import { ClipboardField } from "~/components/primitives/ClipboardField";
13+
import { Header1 } from "~/components/primitives/Headers";
14+
import { Paragraph } from "~/components/primitives/Paragraph";
15+
import { StepNumber } from "~/components/primitives/StepNumber";
16+
import { useAppOrigin } from "~/hooks/useAppOrigin";
17+
import { useDevEnvironment } from "~/hooks/useEnvironments";
18+
import { useOrganization } from "~/hooks/useOrganizations";
19+
import { useProject } from "~/hooks/useProject";
20+
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
421
import { Handle } from "~/utils/handle";
5-
import { trimTrailingSlash } from "~/utils/pathBuilder";
22+
import { projectSetupPath, trimTrailingSlash } from "~/utils/pathBuilder";
623

724
export const handle: Handle = {
825
breadcrumb: (match) => <BreadcrumbLink to={trimTrailingSlash(match.pathname)} title="Express" />,
926
};
1027

1128
export default function Page() {
29+
const organization = useOrganization();
30+
const project = useProject();
31+
useProjectSetupComplete();
32+
const devEnvironment = useDevEnvironment();
33+
invariant(devEnvironment, "Dev environment must be defined");
34+
const appOrigin = useAppOrigin();
35+
1236
return (
13-
<FrameworkComingSoon
14-
frameworkName="Express"
15-
githubIssueUrl="https://github.com/triggerdotdev/trigger.dev/issues/451"
16-
githubIssueNumber={451}
17-
>
18-
<ExpressLogo className="w-56" />
19-
</FrameworkComingSoon>
37+
<PageGradient>
38+
<div className="mx-auto max-w-3xl">
39+
<div className="flex items-center justify-between">
40+
<Header1 spacing className="text-bright">
41+
Get setup in 5 minutes
42+
</Header1>
43+
<div className="flex items-center gap-2">
44+
<LinkButton
45+
to={projectSetupPath(organization, project)}
46+
variant="tertiary/small"
47+
LeadingIcon={Squares2X2Icon}
48+
>
49+
Choose a different framework
50+
</LinkButton>
51+
<Feedback
52+
button={
53+
<Button variant="tertiary/small" LeadingIcon={ChatBubbleLeftRightIcon}>
54+
I'm stuck!
55+
</Button>
56+
}
57+
defaultValue="help"
58+
/>
59+
</div>
60+
</div>
61+
<div>
62+
<Callout
63+
variant={"info"}
64+
to="https://github.com/triggerdotdev/trigger.dev/discussions/430"
65+
className="mb-8"
66+
>
67+
Trigger.dev has full support for serverless. We will be adding support for long-running
68+
servers soon.
69+
</Callout>
70+
<div>
71+
<StepNumber
72+
stepNumber="1"
73+
title="Manually set up Trigger.dev in your existing Express project"
74+
/>
75+
<StepContentContainer className="flex flex-col gap-2">
76+
<Paragraph className="mt-2">Copy your server API Key to your clipboard:</Paragraph>
77+
<div className="mb-2 flex w-full items-center justify-between">
78+
<ClipboardField
79+
secure
80+
className="w-fit"
81+
value={devEnvironment.apiKey}
82+
variant={"secondary/medium"}
83+
icon={<Badge variant="outline">Server</Badge>}
84+
/>
85+
</div>
86+
<Paragraph>Now follow this guide:</Paragraph>
87+
<LinkButton
88+
to="https://trigger.dev/docs/documentation/guides/manual/express"
89+
variant="primary/medium"
90+
TrailingIcon="external-link"
91+
>
92+
Manual installation guide
93+
</LinkButton>
94+
</StepContentContainer>
95+
<StepNumber stepNumber="2" title="Run your Express app" />
96+
<StepContentContainer>
97+
<RunDevCommand />
98+
<Callout variant="info">
99+
You may be using the `start` script instead, in which case substitute `dev` in the
100+
above commands.
101+
</Callout>
102+
</StepContentContainer>
103+
<StepNumber stepNumber="3" title="Run the CLI 'dev' command" />
104+
<StepContentContainer>
105+
<TriggerDevStep />
106+
</StepContentContainer>
107+
<StepNumber stepNumber="6" title="Wait for Jobs" displaySpinner />
108+
<StepContentContainer>
109+
<Paragraph>This page will automatically refresh.</Paragraph>
110+
</StepContentContainer>
111+
</div>
112+
</div>
113+
</div>
114+
</PageGradient>
20115
);
21116
}
Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,193 @@
1-
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).
1+
## Installing Required Packages
2+
3+
Start by installing the necessary packages in your Express.js project directory. You can use npm, pnpm, or yarn as your package manager.
4+
5+
<CodeGroup>
6+
7+
```bash npm
8+
npm install @trigger.dev/sdk @trigger.dev/express
9+
```
10+
11+
```bash pnpm
12+
pnpm install @trigger.dev/sdk @trigger.dev/express
13+
```
14+
15+
```bash yarn
16+
yarn add @trigger.dev/sdk @trigger.dev/express
17+
```
18+
19+
</CodeGroup>
20+
21+
<br />
22+
23+
<Note>Ensure that you execute this command within a Express project.</Note>
24+
25+
## Obtaining the Development Server API Key
26+
27+
To locate your development Server API key, login to the [Trigger.dev
28+
dashboard](https://cloud.trigger.dev) and select the Project you want to
29+
connect to. Then click on the Environments & API Keys tab in the left menu.
30+
You can copy your development Server API Key from the field at the top of this page.
31+
(Your development key will start with `tr_dev_`).
32+
33+
## Adding Environment Variables
34+
35+
Create a `.env` file at the root of your project and include your Trigger API key and URL like this:
36+
37+
```bash
38+
TRIGGER_API_KEY=ENTER_YOUR_DEVELOPMENT_API_KEY_HERE
39+
TRIGGER_API_URL=https://api.trigger.dev # this is only necessary if you are self-hosting
40+
```
41+
42+
Replace `ENTER_YOUR_DEVELOPMENT_API_KEY_HERE` with the actual API key obtained from the previous step.
43+
44+
## Configuring the Trigger Client
45+
46+
Create a file for your Trigger client, in this case we create it at `<root>/trigger.(ts/js)`
47+
48+
```ts trigger.(ts/js)
49+
import { TriggerClient } from "@trigger.dev/sdk";
50+
51+
export const client = new TriggerClient({
52+
id: "my-app",
53+
apiKey: process.env.TRIGGER_API_KEY!,
54+
apiUrl: process.env.TRIGGER_API_URL!,
55+
});
56+
```
57+
58+
Replace **"my-app"** with an appropriate identifier for your project.
59+
60+
## Adding the API endpoint
61+
62+
There are a few different options depending on how your Express project is configured.
63+
64+
- App middleware
65+
- Entire app for Trigger.dev (only relevant if it's the only thing your project is for)
66+
67+
Select the appropriate code example from below:
68+
69+
<CodeGroup>
70+
71+
```typescript app middleware
72+
//import the client from the other file
73+
import { client } from "./trigger";
74+
import { createMiddleware } from "@trigger.dev/express";
75+
76+
//import your job files
77+
import "./jobs/example";
78+
79+
//..your existing Express code
80+
const app: Express = express();
81+
82+
//add the middleware
83+
app.use(createMiddleware(client));
84+
85+
//..the rest of your Express code
86+
```
87+
88+
```typescript entire app
89+
//if the entire app is just for Trigger.dev
90+
import { client } from "./trigger";
91+
import { createExpressServer } from "@trigger.dev/express";
92+
93+
//import your job files
94+
import "./jobs/example";
95+
96+
//this creates an app
97+
createExpressServer(client);
98+
```
99+
100+
</CodeGroup>
101+
102+
## Creating the Example Job
103+
104+
Create a Job file. In this case created `<root>/jobs/example.(ts/js)`
105+
106+
```typescript jobs/example.(ts/js)
107+
import { eventTrigger } from "@trigger.dev/sdk";
108+
import { client } from "../trigger";
109+
110+
// your first job
111+
client.defineJob({
112+
id: "example-job",
113+
name: "Example Job",
114+
version: "0.0.1",
115+
trigger: eventTrigger({
116+
name: "example.event",
117+
}),
118+
run: async (payload, io, ctx) => {
119+
await io.logger.info("Hello world!", { payload });
120+
121+
return {
122+
message: "Hello world!",
123+
};
124+
},
125+
});
126+
```
127+
128+
## Adding Configuration to `package.json`
129+
130+
Inside the `package.json` file, add the following configuration under the root object:
131+
132+
```json
133+
"trigger.dev": {
134+
"endpointId": "my-app"
135+
}
136+
137+
```
138+
139+
Replace **"my-app"** with the appropriate identifier you used in the trigger.js configuration file.
140+
141+
## Running
142+
143+
### Run your Express app
144+
145+
Run your Express app locally, like you normally would. For example:
146+
147+
<CodeGroup>
148+
149+
```bash npm
150+
npm run dev
151+
```
152+
153+
```bash pnpm
154+
pnpm run dev
155+
```
156+
157+
```bash yarn
158+
yarn run dev
159+
```
160+
161+
</CodeGroup>
162+
163+
<Note>You might use `npm run start` instead of dev</Note>
164+
165+
### Run the CLI 'dev' command
166+
167+
In a **_separate terminal window or tab_** run:
168+
169+
<CodeGroup>
170+
171+
```bash npm
172+
npx @trigger.dev/cli@latest dev
173+
```
174+
175+
```bash pnpm
176+
pnpm dlx @trigger.dev/cli@latest dev
177+
```
178+
179+
```bash yarn
180+
yarn dlx @trigger.dev/cli@latest dev
181+
```
182+
183+
</CodeGroup>
184+
<br />
185+
<Note>
186+
You can optionally pass the port if you're not running on 3000 by adding
187+
`--port 3001` to the end
188+
</Note>
189+
190+
<Note>
191+
You can optionally pass the hostname if you're not running on localhost by adding
192+
`--hostname <host>`. Example, in case your Express is running on 0.0.0.0: `--hostname 0.0.0.0`.
193+
</Note>

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
},
5858
"dependencies": {
5959
"@types/degit": "^2.8.3",
60+
"boxen": "^7.1.1",
6061
"chalk": "^5.2.0",
6162
"chokidar": "^3.5.3",
6263
"commander": "^9.4.1",

packages/cli/src/commands/init.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,20 @@ export const initCommand = async (options: InitCommandOptions) => {
129129

130130
await addConfigurationToPackageJson(resolvedPath, resolvedOptions);
131131

132-
await printNextSteps(resolvedOptions, authorizedKey, packageManager, framework);
132+
const projectUrl = `${resolvedOptions.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`;
133+
if (framework.printInstallationComplete) {
134+
await framework.printInstallationComplete(projectUrl);
135+
} else {
136+
await printNextSteps(projectUrl, packageManager, framework);
137+
}
133138
telemetryClient.init.completed(resolvedOptions);
134139
};
135140

136141
async function printNextSteps(
137-
options: ResolvedOptions,
138-
authorizedKey: WhoamiResponse,
142+
projectUrl: string,
139143
packageManager: PackageManager,
140144
framework: Framework
141145
) {
142-
const projectUrl = `${options.triggerUrl}/orgs/${authorizedKey.organization.slug}/projects/${authorizedKey.project.slug}`;
143-
144146
logger.success(`✔ Successfully initialized Trigger.dev!`);
145147

146148
logger.info("Next steps:");
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import mock from "mock-fs";
2+
import { Express } from ".";
3+
import { getFramework } from "..";
4+
import { pathExists } from "../../utils/fileSystem";
5+
6+
afterEach(() => {
7+
mock.restore();
8+
});
9+
10+
describe("Express project detection", () => {
11+
test("has dependency", async () => {
12+
mock({
13+
"package.json": JSON.stringify({ dependencies: { express: "1.0.0" } }),
14+
});
15+
16+
const framework = await getFramework("", "npm");
17+
expect(framework?.id).toEqual("express");
18+
});
19+
20+
test("no dependency", async () => {
21+
mock({
22+
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
23+
});
24+
25+
const framework = await getFramework("", "npm");
26+
expect(framework?.id).not.toEqual("express");
27+
});
28+
});

0 commit comments

Comments
 (0)