Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/perfect-poems-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/cli": patch
---

Added Astro automatic installation
2 changes: 1 addition & 1 deletion apps/webapp/app/components/SetupCommands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function TriggerDevStep() {
</Paragraph>
<TriggerDevCommand />
<Paragraph spacing variant="small">
If you’re not running on port 3000 you can specify the port by adding{" "}
If you’re not running on the default you can specify the port by adding{" "}
<InlineCode variant="extra-small">--port 3001</InlineCode> to the end.
</Paragraph>
<Paragraph spacing variant="small">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export default function SetUpAstro() {
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
invariant(devEnvironment, "Dev environment must be defined");
const appOrigin = useAppOrigin();

return (
<PageGradient>
<div className="mx-auto max-w-3xl">
Expand Down Expand Up @@ -76,28 +78,16 @@ export default function SetUpAstro() {
<div>
<StepNumber
stepNumber="1"
title="Follow the steps from the Astro manual installation guide"
title="Run the CLI 'init' command in an existing Astro project"
/>
<StepContentContainer className="flex flex-col gap-2">
<Paragraph className="mt-2">Copy your server API Key to your clipboard:</Paragraph>
<div className="mb-2 flex w-full items-center justify-between">
<ClipboardField
secure
className="w-fit"
value={devEnvironment.apiKey}
variant={"secondary/medium"}
icon={<Badge variant="outline">Server</Badge>}
/>
</div>
<Paragraph>Now follow this guide:</Paragraph>
<LinkButton
to="https://trigger.dev/docs/documentation/guides/manual/astro"
variant="primary/medium"
TrailingIcon="external-link"
>
Manual installation guide
</LinkButton>
<div className="flex items-start justify-start gap-2"></div>
<StepContentContainer>
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />

<Paragraph spacing variant="small">
You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple
example Job in <InlineCode variant="extra-small">example.ts</InlineCode> to help you
get started.
</Paragraph>
</StepContentContainer>
<StepNumber stepNumber="2" title="Run your Astro app" />
<StepContentContainer>
Expand Down
79 changes: 78 additions & 1 deletion docs/documentation/quickstarts/astro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,81 @@ sidebarTitle: "Astro"
description: "Start creating Jobs in 5 minutes in your Astro project."
---

<Snippet file="manual-setup-astro.mdx" />
This quick start guide will get you up and running with Trigger.dev.

<Accordion title="Need to create a new Astro project to add Trigger.dev to?">
No problem, create a blank project by running the `create-astro` command in your terminal then continue with this quickstart guide as normal:

```bash
npx create-astro@latest
```

</Accordion>

<Steps titleSize="h3">
<Snippet file="quickstart-setup-steps.mdx" />

<Step title="Run the CLI `dev` command">

<Snippet file="quickstart-cli-dev.mdx" />

<AccordionGroup>
<Accordion title="Advanced: Run your Astro server together with the CLI">
You can modify your `package.json` to run both the Astro server and the CLI `dev` command together.

1. Install the `concurrently` package:

<CodeGroup>

```bash npm
npm install concurrently --save-dev
```

```bash pnpm
pnpm install concurrently --save-dev
```

```bash yarn
yarn add concurrently --dev
```

</CodeGroup>

2. Modify your `package.json` file's `dev` script.

```json package.json
//...
"scripts": {
"dev": "concurrently --kill-others npm:dev:*",
//your normal astro dev command would go here
"dev:astro": "astro dev",
"dev:trigger": "npx @trigger.dev/cli dev",
//...
}
//...
```

</Accordion>
</AccordionGroup>

</Step>

<Step title="Your first job">

The CLI init command created a simple Job for you. There will be a new file `src/jobs/example.(ts/js)`.

In there is this Job:

<Snippet file="quickstart-example-job.mdx" />

If you navigate to your Trigger.dev project you will see this Job in the "Jobs" section:

![Your first Job](/images/first-job.png)

</Step>

<Snippet file="quickstart-running-your-job.mdx" />

</Steps>

<Snippet file="quickstart-whats-next.mdx" />
2 changes: 1 addition & 1 deletion docs/documentation/quickstarts/remix.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ yarn add concurrently --dev

<Step title="Your first job">

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

In there is this Job:

Expand Down
81 changes: 81 additions & 0 deletions packages/cli/src/frameworks/astro/astro.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import mock from "mock-fs";
import { Astro } from ".";
import { getFramework } from "..";
import { pathExists } from "../../utils/fileSystem";

afterEach(() => {
mock.restore();
});

describe("Astro project detection", () => {
test("has dependency", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { astro: "1.0.0" } }),
});

const framework = await getFramework("", "npm");
expect(framework?.id).toEqual("astro");
});

test("no dependency, has astro.config.js", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
"astro.config.js": "module.exports = {}",
});

const framework = await getFramework("", "npm");
expect(framework?.id).toEqual("astro");
});

test("no dependency, has astro.config.mjs", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
"astro.config.mjs": "module.exports = {}",
});

const framework = await getFramework("", "npm");
expect(framework?.id).toEqual("astro");
});

test("no dependency, no astro.config.*", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
});

const framework = await getFramework("", "npm");
expect(framework?.id).not.toEqual("astro");
});
});

describe("install", () => {
test("javascript", async () => {
mock({
src: {
pages: {},
},
});

const astro = new Astro();
await astro.install("", { typescript: false, packageManager: "npm", endpointSlug: "foo" });
expect(await pathExists("src/trigger.js")).toEqual(true);
expect(await pathExists("src/pages/api/trigger.js")).toEqual(true);
expect(await pathExists("src/jobs/example.js")).toEqual(true);
expect(await pathExists("src/jobs/index.js")).toEqual(true);
});

test("typescript", async () => {
mock({
app: {
routes: {},
},
"tsconfig.json": JSON.stringify({}),
});

const astro = new Astro();
await astro.install("", { typescript: true, packageManager: "npm", endpointSlug: "foo" });
expect(await pathExists("src/trigger.ts")).toEqual(true);
expect(await pathExists("src/pages/api/trigger.ts")).toEqual(true);
expect(await pathExists("src/jobs/example.ts")).toEqual(true);
expect(await pathExists("src/jobs/index.ts")).toEqual(true);
});
});
128 changes: 128 additions & 0 deletions packages/cli/src/frameworks/astro/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Framework, ProjectInstallOptions } from "..";
import { InstallPackage } from "../../utils/addDependencies";
import { pathExists, someFileExists } from "../../utils/fileSystem";
import { PackageManager } from "../../utils/getUserPkgManager";
import pathModule from "path";
import { getPathAlias } from "../../utils/pathAlias";
import { createFileFromTemplate } from "../../utils/createFileFromTemplate";
import { templatesPath } from "../../paths";
import { logger } from "../../utils/logger";
import { readPackageJson } from "../../utils/readPackageJson";
import { standardWatchFilePaths } from "../watchConfig";

export class Astro implements Framework {
id = "astro";
name = "Astro";

async isMatch(path: string, packageManager: PackageManager): Promise<boolean> {
const configFilenames = [
"astro.config.js",
"astro.config.mjs",
"astro.config.cjs",
"astro.config.ts",
];
//check for astro.config.mjs
const hasConfigFile = await someFileExists(path, configFilenames);
if (hasConfigFile) {
return true;
}

//check for the astro package
const packageJsonContent = await readPackageJson(path);
if (packageJsonContent?.dependencies?.astro) {
return true;
}

return false;
}

async dependencies(): Promise<InstallPackage[]> {
return [
{ name: "@trigger.dev/sdk", tag: "latest" },
{ name: "@trigger.dev/astro", tag: "latest" },
{ name: "@trigger.dev/react", tag: "latest" },
];
}

possibleEnvFilenames(): string[] {
return [".env", ".env.development"];
}

async install(path: string, { typescript, endpointSlug }: ProjectInstallOptions): Promise<void> {
const pathAlias = await getPathAlias({
projectPath: path,
isTypescriptProject: typescript,
extraDirectories: ["src"],
});
const templatesDir = pathModule.join(templatesPath(), "astro");
const srcFolder = pathModule.join(path, "src");
const fileExtension = typescript ? ".ts" : ".js";

//create src/pages/api/trigger.js
const apiRoutePath = pathModule.join(srcFolder, "pages", "api", `trigger${fileExtension}`);
const apiRouteResult = await createFileFromTemplate({
templatePath: pathModule.join(templatesDir, "apiRoute.js"),
replacements: {
routePathPrefix: pathAlias ? pathAlias + "/" : "../../",
},
outputPath: apiRoutePath,
});
if (!apiRouteResult.success) {
throw new Error("Failed to create API route file");
}
logger.success(`✔ Created API route at ${apiRoutePath}`);

//src/trigger.js
const triggerFilePath = pathModule.join(srcFolder, `trigger${fileExtension}`);
const triggerResult = await createFileFromTemplate({
templatePath: pathModule.join(templatesDir, "trigger.js"),
replacements: {
endpointSlug,
},
outputPath: triggerFilePath,
});
if (!triggerResult.success) {
throw new Error("Failed to create trigger file");
}
logger.success(`✔ Created Trigger client at ${triggerFilePath}`);

//src/jobs/example.js
const exampleJobFilePath = pathModule.join(srcFolder, "jobs", `example${fileExtension}`);
const exampleJobResult = await createFileFromTemplate({
templatePath: pathModule.join(templatesDir, "exampleJob.js"),
replacements: {
jobsPathPrefix: pathAlias ? pathAlias + "/" : "../",
},
outputPath: exampleJobFilePath,
});
if (!exampleJobResult.success) {
throw new Error("Failed to create example job file");
}
logger.success(`✔ Created example job at ${exampleJobFilePath}`);

//src/jobs/index.js
const jobsIndexFilePath = pathModule.join(srcFolder, "jobs", `index${fileExtension}`);
const jobsIndexResult = await createFileFromTemplate({
templatePath: pathModule.join(templatesDir, "jobsIndex.js"),
replacements: {
jobsPathPrefix: pathAlias ? pathAlias + "/" : "../",
},
outputPath: jobsIndexFilePath,
});
if (!jobsIndexResult.success) {
throw new Error("Failed to create jobs index file");
}
logger.success(`✔ Created jobs index at ${jobsIndexFilePath}`);
}

async postInstall(path: string, options: ProjectInstallOptions): Promise<void> {
logger.warn(
`⚠︎ Ensure your astro.config output is "server" or "hybrid":\nhttps://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project`
);
}

defaultHostnames = ["localhost", "[::]"];
defaultPorts = [4321, 4322, 4323, 4324];
watchFilePaths = standardWatchFilePaths;
watchIgnoreRegex = /(node_modules)/;
}
3 changes: 2 additions & 1 deletion packages/cli/src/frameworks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InstallPackage } from "../utils/addDependencies";
import { PackageManager } from "../utils/getUserPkgManager";
import { Astro } from "./astro";
import { NextJs } from "./nextjs";
import { Remix } from "./remix";

Expand Down Expand Up @@ -45,7 +46,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()];
const frameworks: Framework[] = [new NextJs(), new Remix(), new Astro()];

export const getFramework = async (
path: string,
Expand Down
Loading