@@ -76,28 +78,16 @@ export default function SetUpAstro() {
-
- Copy your server API Key to your clipboard:
-
- Server}
- />
-
- Now follow this guide:
-
- Manual installation guide
-
-
+
+
+
+
+ You’ll notice a new folder in your project called 'jobs'. We’ve added a very simple
+ example Job in example.ts to help you
+ get started.
+
diff --git a/docs/documentation/quickstarts/astro.mdx b/docs/documentation/quickstarts/astro.mdx
index f9b9968caf..f1d0a92982 100644
--- a/docs/documentation/quickstarts/astro.mdx
+++ b/docs/documentation/quickstarts/astro.mdx
@@ -4,4 +4,81 @@ sidebarTitle: "Astro"
description: "Start creating Jobs in 5 minutes in your Astro project."
---
-
+This quick start guide will get you up and running with Trigger.dev.
+
+
+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
+```
+
+
+
+
+
+
+
+
+
+
+
+
+You can modify your `package.json` to run both the Astro server and the CLI `dev` command together.
+
+1. Install the `concurrently` package:
+
+
+
+```bash npm
+npm install concurrently --save-dev
+```
+
+```bash pnpm
+pnpm install concurrently --save-dev
+```
+
+```bash yarn
+yarn add concurrently --dev
+```
+
+
+
+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",
+ //...
+}
+//...
+```
+
+
+
+
+
+
+
+
+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:
+
+
+
+If you navigate to your Trigger.dev project you will see this Job in the "Jobs" section:
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/documentation/quickstarts/remix.mdx b/docs/documentation/quickstarts/remix.mdx
index 2d8cc21604..4af18372ea 100644
--- a/docs/documentation/quickstarts/remix.mdx
+++ b/docs/documentation/quickstarts/remix.mdx
@@ -67,7 +67,7 @@ yarn add concurrently --dev
-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:
diff --git a/packages/cli/src/frameworks/astro/astro.test.ts b/packages/cli/src/frameworks/astro/astro.test.ts
new file mode 100644
index 0000000000..57bd3cf783
--- /dev/null
+++ b/packages/cli/src/frameworks/astro/astro.test.ts
@@ -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);
+ });
+});
diff --git a/packages/cli/src/frameworks/astro/index.ts b/packages/cli/src/frameworks/astro/index.ts
new file mode 100644
index 0000000000..bdb8ac67cb
--- /dev/null
+++ b/packages/cli/src/frameworks/astro/index.ts
@@ -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 {
+ 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 {
+ 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 {
+ 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 {
+ 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)/;
+}
diff --git a/packages/cli/src/frameworks/index.ts b/packages/cli/src/frameworks/index.ts
index 960791d6ec..69ba77a476 100644
--- a/packages/cli/src/frameworks/index.ts
+++ b/packages/cli/src/frameworks/index.ts
@@ -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";
@@ -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,
diff --git a/packages/cli/src/frameworks/nextjs/index.ts b/packages/cli/src/frameworks/nextjs/index.ts
index 1ec073961d..5d60320737 100644
--- a/packages/cli/src/frameworks/nextjs/index.ts
+++ b/packages/cli/src/frameworks/nextjs/index.ts
@@ -4,13 +4,13 @@ import { Framework } from "..";
import { templatesPath } from "../../paths";
import { InstallPackage } from "../../utils/addDependencies";
import { createFileFromTemplate } from "../../utils/createFileFromTemplate";
-import { pathExists } from "../../utils/fileSystem";
+import { pathExists, someFileExists } from "../../utils/fileSystem";
import { PackageManager } from "../../utils/getUserPkgManager";
import { logger } from "../../utils/logger";
import { getPathAlias } from "../../utils/pathAlias";
import { readPackageJson } from "../../utils/readPackageJson";
-import { detectMiddlewareUsage } from "./middleware";
import { standardWatchFilePaths } from "../watchConfig";
+import { detectMiddlewareUsage } from "./middleware";
export class NextJs implements Framework {
id = "nextjs";
@@ -75,7 +75,14 @@ export class NextJs implements Framework {
}
async function detectNextConfigFile(path: string): Promise {
- return pathExists(pathModule.join(path, "next.config.js"));
+ const configFilenames = [
+ "next.config.js",
+ "next.config.mjs",
+ "next.config.cjs",
+ "next.config.ts",
+ ];
+
+ return someFileExists(path, configFilenames);
}
export async function detectNextDependency(path: string): Promise {
@@ -84,7 +91,10 @@ export async function detectNextDependency(path: string): Promise {
return false;
}
- return packageJsonContent.dependencies?.next !== undefined;
+ if (packageJsonContent.dependencies?.next !== undefined) return true;
+ if (packageJsonContent.devDependencies?.next !== undefined) return true;
+
+ return false;
}
export async function detectUseOfSrcDir(path: string): Promise {
diff --git a/packages/cli/src/frameworks/nextjs/nextjs.test.ts b/packages/cli/src/frameworks/nextjs/nextjs.test.ts
index 63f192eb7d..494a54f273 100644
--- a/packages/cli/src/frameworks/nextjs/nextjs.test.ts
+++ b/packages/cli/src/frameworks/nextjs/nextjs.test.ts
@@ -17,6 +17,15 @@ describe("Next project detection", () => {
expect(framework?.id).toEqual("nextjs");
});
+ test("has dev dependency", async () => {
+ mock({
+ "package.json": JSON.stringify({ devDependencies: { next: "1.0.0" } }),
+ });
+
+ const framework = await getFramework("", "npm");
+ expect(framework?.id).toEqual("nextjs");
+ });
+
test("no dependency, has next.config.js", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
@@ -27,6 +36,16 @@ describe("Next project detection", () => {
expect(framework?.id).toEqual("nextjs");
});
+ test("no dependency, has next.config.mjs", async () => {
+ mock({
+ "package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
+ "next.config.mjs": "module.exports = {}",
+ });
+
+ const framework = await getFramework("", "npm");
+ expect(framework?.id).toEqual("nextjs");
+ });
+
test("no dependency, no next.config.js", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { foo: "1.0.0" } }),
diff --git a/packages/cli/src/templates/astro/apiRoute.js b/packages/cli/src/templates/astro/apiRoute.js
new file mode 100644
index 0000000000..502724ef88
--- /dev/null
+++ b/packages/cli/src/templates/astro/apiRoute.js
@@ -0,0 +1,9 @@
+import { createAstroRoute } from "@trigger.dev/astro";
+//you may need to update this path to point at your trigger.ts file
+import { client } from "${routePathPrefix}trigger";
+
+//import your jobs
+import "${routePathPrefix}jobs";
+
+export const prerender = false;
+export const { POST } = createAstroRoute(client);
diff --git a/packages/cli/src/templates/astro/exampleJob.js b/packages/cli/src/templates/astro/exampleJob.js
new file mode 100644
index 0000000000..ff0fbdc800
--- /dev/null
+++ b/packages/cli/src/templates/astro/exampleJob.js
@@ -0,0 +1,27 @@
+import { eventTrigger } from "@trigger.dev/sdk";
+import { client } from "${jobsPathPrefix}trigger";
+
+// Your first job
+// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline
+client.defineJob({
+ // This is the unique identifier for your Job, it must be unique across all Jobs in your project
+ id: "example-job",
+ name: "Example Job: a joke with a delay",
+ version: "0.0.1",
+ // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction
+ trigger: eventTrigger({
+ name: "example.event",
+ }),
+ run: async (payload, io, ctx) => {
+ // This logs a message to the console
+ await io.logger.info("🧪 Example Job: a joke with a delay");
+ await io.logger.info("How do you comfort a JavaScript bug?");
+ // This waits for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year
+ await io.wait("Wait 5 seconds for the punchline...", 5);
+ await io.logger.info("You console it! 🤦");
+ await io.logger.info(
+ "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨"
+ );
+ // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job
+ },
+});
diff --git a/packages/cli/src/templates/astro/jobsIndex.js b/packages/cli/src/templates/astro/jobsIndex.js
new file mode 100644
index 0000000000..c24223d379
--- /dev/null
+++ b/packages/cli/src/templates/astro/jobsIndex.js
@@ -0,0 +1,3 @@
+// export all your job files here
+
+export * from "./example";
diff --git a/packages/cli/src/templates/astro/trigger.js b/packages/cli/src/templates/astro/trigger.js
new file mode 100644
index 0000000000..ef08f13b48
--- /dev/null
+++ b/packages/cli/src/templates/astro/trigger.js
@@ -0,0 +1,7 @@
+import { TriggerClient } from "@trigger.dev/sdk";
+
+export const client = new TriggerClient({
+ id: "${endpointSlug}",
+ apiKey: import.meta.env.TRIGGER_API_KEY,
+ apiUrl: import.meta.env.TRIGGER_API_URL,
+});
diff --git a/packages/cli/src/utils/fileSystem.ts b/packages/cli/src/utils/fileSystem.ts
index 76c350eb98..f1acf955b0 100644
--- a/packages/cli/src/utils/fileSystem.ts
+++ b/packages/cli/src/utils/fileSystem.ts
@@ -20,6 +20,20 @@ export async function pathExists(path: string): Promise {
}
}
+export async function someFileExists(directory: string, filenames: string[]): Promise {
+ for (let index = 0; index < filenames.length; index++) {
+ const filename = filenames[index];
+ if (!filename) continue;
+
+ const path = pathModule.join(directory, filename);
+ if (await pathExists(path)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
export async function removeFile(path: string) {
await fsModule.unlink(path);
}