Skip to content

Commit 4578f6b

Browse files
authored
Astro CLI support (#506)
* Astro framework CLI support * Changeset: Added Astro automatic installation * Fixed the package name – was remix, now astro * Fixed the export of the example * Added IPv6 localhost to Astro hostnames * Updated Astro onboarding to show the CLI init command, instead of manual instructions * Astro quickstart * Fix for type in Remix quickstart * Next.js framework detection allows different config file extensions and “next” devDependency * Made the dev command port more general so it works with various frameworks
1 parent 8b25e57 commit 4578f6b

File tree

15 files changed

+400
-29
lines changed

15 files changed

+400
-29
lines changed

.changeset/perfect-poems-reply.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 Astro automatic installation

apps/webapp/app/components/SetupCommands.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export function TriggerDevStep() {
106106
</Paragraph>
107107
<TriggerDevCommand />
108108
<Paragraph spacing variant="small">
109-
If you’re not running on port 3000 you can specify the port by adding{" "}
109+
If you’re not running on the default you can specify the port by adding{" "}
110110
<InlineCode variant="extra-small">--port 3001</InlineCode> to the end.
111111
</Paragraph>
112112
<Paragraph spacing variant="small">

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.setup.astro/route.tsx

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export default function SetUpAstro() {
3939
useProjectSetupComplete();
4040
const devEnvironment = useDevEnvironment();
4141
invariant(devEnvironment, "Dev environment must be defined");
42+
const appOrigin = useAppOrigin();
43+
4244
return (
4345
<PageGradient>
4446
<div className="mx-auto max-w-3xl">
@@ -76,28 +78,16 @@ export default function SetUpAstro() {
7678
<div>
7779
<StepNumber
7880
stepNumber="1"
79-
title="Follow the steps from the Astro manual installation guide"
81+
title="Run the CLI 'init' command in an existing Astro project"
8082
/>
81-
<StepContentContainer className="flex flex-col gap-2">
82-
<Paragraph className="mt-2">Copy your server API Key to your clipboard:</Paragraph>
83-
<div className="mb-2 flex w-full items-center justify-between">
84-
<ClipboardField
85-
secure
86-
className="w-fit"
87-
value={devEnvironment.apiKey}
88-
variant={"secondary/medium"}
89-
icon={<Badge variant="outline">Server</Badge>}
90-
/>
91-
</div>
92-
<Paragraph>Now follow this guide:</Paragraph>
93-
<LinkButton
94-
to="https://trigger.dev/docs/documentation/guides/manual/astro"
95-
variant="primary/medium"
96-
TrailingIcon="external-link"
97-
>
98-
Manual installation guide
99-
</LinkButton>
100-
<div className="flex items-start justify-start gap-2"></div>
83+
<StepContentContainer>
84+
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
85+
86+
<Paragraph spacing variant="small">
87+
You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple
88+
example Job in <InlineCode variant="extra-small">example.ts</InlineCode> to help you
89+
get started.
90+
</Paragraph>
10191
</StepContentContainer>
10292
<StepNumber stepNumber="2" title="Run your Astro app" />
10393
<StepContentContainer>

docs/documentation/quickstarts/astro.mdx

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,81 @@ sidebarTitle: "Astro"
44
description: "Start creating Jobs in 5 minutes in your Astro project."
55
---
66

7-
<Snippet file="manual-setup-astro.mdx" />
7+
This quick start guide will get you up and running with Trigger.dev.
8+
9+
<Accordion title="Need to create a new Astro project to add Trigger.dev to?">
10+
No problem, create a blank project by running the `create-astro` command in your terminal then continue with this quickstart guide as normal:
11+
12+
```bash
13+
npx create-astro@latest
14+
```
15+
16+
</Accordion>
17+
18+
<Steps titleSize="h3">
19+
<Snippet file="quickstart-setup-steps.mdx" />
20+
21+
<Step title="Run the CLI `dev` command">
22+
23+
<Snippet file="quickstart-cli-dev.mdx" />
24+
25+
<AccordionGroup>
26+
<Accordion title="Advanced: Run your Astro server together with the CLI">
27+
You can modify your `package.json` to run both the Astro server and the CLI `dev` command together.
28+
29+
1. Install the `concurrently` package:
30+
31+
<CodeGroup>
32+
33+
```bash npm
34+
npm install concurrently --save-dev
35+
```
36+
37+
```bash pnpm
38+
pnpm install concurrently --save-dev
39+
```
40+
41+
```bash yarn
42+
yarn add concurrently --dev
43+
```
44+
45+
</CodeGroup>
46+
47+
2. Modify your `package.json` file's `dev` script.
48+
49+
```json package.json
50+
//...
51+
"scripts": {
52+
"dev": "concurrently --kill-others npm:dev:*",
53+
//your normal astro dev command would go here
54+
"dev:astro": "astro dev",
55+
"dev:trigger": "npx @trigger.dev/cli dev",
56+
//...
57+
}
58+
//...
59+
```
60+
61+
</Accordion>
62+
</AccordionGroup>
63+
64+
</Step>
65+
66+
<Step title="Your first job">
67+
68+
The CLI init command created a simple Job for you. There will be a new file `src/jobs/example.(ts/js)`.
69+
70+
In there is this Job:
71+
72+
<Snippet file="quickstart-example-job.mdx" />
73+
74+
If you navigate to your Trigger.dev project you will see this Job in the "Jobs" section:
75+
76+
![Your first Job](/images/first-job.png)
77+
78+
</Step>
79+
80+
<Snippet file="quickstart-running-your-job.mdx" />
81+
82+
</Steps>
83+
84+
<Snippet file="quickstart-whats-next.mdx" />

docs/documentation/quickstarts/remix.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ yarn add concurrently --dev
6767

6868
<Step title="Your first job">
6969

70-
The CLI init command created a simple Job for you. There will be a new file either `app/jobs/example.server.(ts/js)`.
70+
The CLI init command created a simple Job for you. There will be a new file `app/jobs/example.server.(ts/js)`.
7171

7272
In there is this Job:
7373

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import mock from "mock-fs";
2+
import { Astro } from ".";
3+
import { getFramework } from "..";
4+
import { pathExists } from "../../utils/fileSystem";
5+
6+
afterEach(() => {
7+
mock.restore();
8+
});
9+
10+
describe("Astro project detection", () => {
11+
test("has dependency", async () => {
12+
mock({
13+
"package.json": JSON.stringify({ dependencies: { astro: "1.0.0" } }),
14+
});
15+
16+
const framework = await getFramework("", "npm");
17+
expect(framework?.id).toEqual("astro");
18+
});
19+
20+
test("no dependency, has astro.config.js", async () => {
21+
mock({
22+
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
23+
"astro.config.js": "module.exports = {}",
24+
});
25+
26+
const framework = await getFramework("", "npm");
27+
expect(framework?.id).toEqual("astro");
28+
});
29+
30+
test("no dependency, has astro.config.mjs", async () => {
31+
mock({
32+
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
33+
"astro.config.mjs": "module.exports = {}",
34+
});
35+
36+
const framework = await getFramework("", "npm");
37+
expect(framework?.id).toEqual("astro");
38+
});
39+
40+
test("no dependency, no astro.config.*", async () => {
41+
mock({
42+
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
43+
});
44+
45+
const framework = await getFramework("", "npm");
46+
expect(framework?.id).not.toEqual("astro");
47+
});
48+
});
49+
50+
describe("install", () => {
51+
test("javascript", async () => {
52+
mock({
53+
src: {
54+
pages: {},
55+
},
56+
});
57+
58+
const astro = new Astro();
59+
await astro.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" });
60+
expect(await pathExists("src/trigger.js")).toEqual(true);
61+
expect(await pathExists("src/pages/api/trigger.js")).toEqual(true);
62+
expect(await pathExists("src/jobs/example.js")).toEqual(true);
63+
expect(await pathExists("src/jobs/index.js")).toEqual(true);
64+
});
65+
66+
test("typescript", async () => {
67+
mock({
68+
app: {
69+
routes: {},
70+
},
71+
"tsconfig.json": JSON.stringify({}),
72+
});
73+
74+
const astro = new Astro();
75+
await astro.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" });
76+
expect(await pathExists("src/trigger.ts")).toEqual(true);
77+
expect(await pathExists("src/pages/api/trigger.ts")).toEqual(true);
78+
expect(await pathExists("src/jobs/example.ts")).toEqual(true);
79+
expect(await pathExists("src/jobs/index.ts")).toEqual(true);
80+
});
81+
});
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { Framework, ProjectInstallOptions } from "..";
2+
import { InstallPackage } from "../../utils/addDependencies";
3+
import { pathExists, someFileExists } from "../../utils/fileSystem";
4+
import { PackageManager } from "../../utils/getUserPkgManager";
5+
import pathModule from "path";
6+
import { getPathAlias } from "../../utils/pathAlias";
7+
import { createFileFromTemplate } from "../../utils/createFileFromTemplate";
8+
import { templatesPath } from "../../paths";
9+
import { logger } from "../../utils/logger";
10+
import { readPackageJson } from "../../utils/readPackageJson";
11+
import { standardWatchFilePaths } from "../watchConfig";
12+
13+
export class Astro implements Framework {
14+
id = "astro";
15+
name = "Astro";
16+
17+
async isMatch(path: string, packageManager: PackageManager): Promise<boolean> {
18+
const configFilenames = [
19+
"astro.config.js",
20+
"astro.config.mjs",
21+
"astro.config.cjs",
22+
"astro.config.ts",
23+
];
24+
//check for astro.config.mjs
25+
const hasConfigFile = await someFileExists(path, configFilenames);
26+
if (hasConfigFile) {
27+
return true;
28+
}
29+
30+
//check for the astro package
31+
const packageJsonContent = await readPackageJson(path);
32+
if (packageJsonContent?.dependencies?.astro) {
33+
return true;
34+
}
35+
36+
return false;
37+
}
38+
39+
async dependencies(): Promise<InstallPackage[]> {
40+
return [
41+
{ name: "@trigger.dev/sdk", tag: "latest" },
42+
{ name: "@trigger.dev/astro", tag: "latest" },
43+
{ name: "@trigger.dev/react", tag: "latest" },
44+
];
45+
}
46+
47+
possibleEnvFilenames(): string[] {
48+
return [".env", ".env.development"];
49+
}
50+
51+
async install(path: string, { typescript, endpointSlug }: ProjectInstallOptions): Promise<void> {
52+
const pathAlias = await getPathAlias({
53+
projectPath: path,
54+
isTypescriptProject: typescript,
55+
extraDirectories: ["src"],
56+
});
57+
const templatesDir = pathModule.join(templatesPath(), "astro");
58+
const srcFolder = pathModule.join(path, "src");
59+
const fileExtension = typescript ? ".ts" : ".js";
60+
61+
//create src/pages/api/trigger.js
62+
const apiRoutePath = pathModule.join(srcFolder, "pages", "api", `trigger${fileExtension}`);
63+
const apiRouteResult = await createFileFromTemplate({
64+
templatePath: pathModule.join(templatesDir, "apiRoute.js"),
65+
replacements: {
66+
routePathPrefix: pathAlias ? pathAlias + "/" : "../../",
67+
},
68+
outputPath: apiRoutePath,
69+
});
70+
if (!apiRouteResult.success) {
71+
throw new Error("Failed to create API route file");
72+
}
73+
logger.success(`✔ Created API route at ${apiRoutePath}`);
74+
75+
//src/trigger.js
76+
const triggerFilePath = pathModule.join(srcFolder, `trigger${fileExtension}`);
77+
const triggerResult = await createFileFromTemplate({
78+
templatePath: pathModule.join(templatesDir, "trigger.js"),
79+
replacements: {
80+
endpointSlug,
81+
},
82+
outputPath: triggerFilePath,
83+
});
84+
if (!triggerResult.success) {
85+
throw new Error("Failed to create trigger file");
86+
}
87+
logger.success(`✔ Created Trigger client at ${triggerFilePath}`);
88+
89+
//src/jobs/example.js
90+
const exampleJobFilePath = pathModule.join(srcFolder, "jobs", `example${fileExtension}`);
91+
const exampleJobResult = await createFileFromTemplate({
92+
templatePath: pathModule.join(templatesDir, "exampleJob.js"),
93+
replacements: {
94+
jobsPathPrefix: pathAlias ? pathAlias + "/" : "../",
95+
},
96+
outputPath: exampleJobFilePath,
97+
});
98+
if (!exampleJobResult.success) {
99+
throw new Error("Failed to create example job file");
100+
}
101+
logger.success(`✔ Created example job at ${exampleJobFilePath}`);
102+
103+
//src/jobs/index.js
104+
const jobsIndexFilePath = pathModule.join(srcFolder, "jobs", `index${fileExtension}`);
105+
const jobsIndexResult = await createFileFromTemplate({
106+
templatePath: pathModule.join(templatesDir, "jobsIndex.js"),
107+
replacements: {
108+
jobsPathPrefix: pathAlias ? pathAlias + "/" : "../",
109+
},
110+
outputPath: jobsIndexFilePath,
111+
});
112+
if (!jobsIndexResult.success) {
113+
throw new Error("Failed to create jobs index file");
114+
}
115+
logger.success(`✔ Created jobs index at ${jobsIndexFilePath}`);
116+
}
117+
118+
async postInstall(path: string, options: ProjectInstallOptions): Promise<void> {
119+
logger.warn(
120+
`⚠︎ Ensure your astro.config output is "server" or "hybrid":\nhttps://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project`
121+
);
122+
}
123+
124+
defaultHostnames = ["localhost", "[::]"];
125+
defaultPorts = [4321, 4322, 4323, 4324];
126+
watchFilePaths = standardWatchFilePaths;
127+
watchIgnoreRegex = /(node_modules)/;
128+
}

packages/cli/src/frameworks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { InstallPackage } from "../utils/addDependencies";
22
import { PackageManager } from "../utils/getUserPkgManager";
3+
import { Astro } from "./astro";
34
import { NextJs } from "./nextjs";
45
import { Remix } from "./remix";
56

@@ -45,7 +46,7 @@ export interface Framework {
4546
}
4647

4748
/** The order of these matters. The first one that matches the folder will be used, so stricter ones should be first. */
48-
const frameworks: Framework[] = [new NextJs(), new Remix()];
49+
const frameworks: Framework[] = [new NextJs(), new Remix(), new Astro()];
4950

5051
export const getFramework = async (
5152
path: string,

0 commit comments

Comments
 (0)