Feat: add maxDuration setting for API/Trigger route in the CLI Init Command for Next.js (#617)

* add cli init maxDuration to the api/trigger route for nextjs

* refactor createTriggerRoute

* move boxen log to createTriggerRoute function

* refine detectNextVersion and versionNumberPattern regex to match the latest nextjs version

* add tests for the detectNextVersion function

* add changeset file

* If the regex doesn't match, it returns null instead of throwing

* Tweaked message about the max duration

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
This commit is contained in:
Alexandre Costa
2023-10-20 06:16:44 -03:00
committed by GitHub
parent b7f4e40edb
commit 0adf41c7f0
5 changed files with 112 additions and 33 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/cli": patch
---
Added Next.js `maxDuration` commented out to the api/trigger file using CLI init
+59 -32
View File
@@ -1,5 +1,6 @@
import fs from "fs/promises";
import pathModule from "path";
import boxen from "boxen";
import { Framework } from "..";
import { templatesPath } from "../../paths";
import { InstallPackage } from "../../utils/addDependencies";
@@ -50,17 +51,46 @@ export class NextJs implements Framework {
}
const nextJsDir = await detectPagesOrAppDir(path);
const nextJsVersion = await detectNextVersion(path);
const routeDir = pathModule.join(path, usesSrcDir ? "src" : "");
const pathAlias = await getPathAlias({
projectPath: path,
isTypescriptProject: options.typescript,
extraDirectories: usesSrcDir ? ["src"] : undefined,
});
const fileExtension = options.typescript ? ".ts" : ".js";
if (nextJsDir === "pages") {
await createTriggerPageRoute(routeDir, options.endpointSlug, options.typescript, pathAlias);
const apiRoutePath = pathModule.join(routeDir, "pages", "api", `trigger${fileExtension}`);
if (nextJsVersion && nextJsVersion !== 'latest' && nextJsVersion < "13.5") {
await createTriggerRoute({
path: routeDir,
apiRoutePath,
template: "pagesApiRoute.js",
fileExtension,
endpointSlug: options.endpointSlug,
pathAlias
});
} else {
await createTriggerRoute({
path: routeDir,
apiRoutePath,
template: "pagesApiRouteWithConfigObject.js",
fileExtension,
endpointSlug: options.endpointSlug,
pathAlias
});
}
} else {
await createTriggerAppRoute(routeDir, options.endpointSlug, options.typescript, pathAlias);
const apiRoutePath = pathModule.join(routeDir, "app", "api", "trigger", `route${fileExtension}`);
await createTriggerRoute({
path: routeDir,
apiRoutePath,
template: "appApiRoute.js",
fileExtension,
endpointSlug: options.endpointSlug,
pathAlias
});
}
}
@@ -144,45 +174,34 @@ export async function detectPagesOrAppDir(path: string): Promise<"pages" | "app"
return "pages";
}
async function createTriggerPageRoute(
path: string,
endpointSlug: string,
isTypescriptProject: boolean,
pathAlias: string | undefined
) {
const templatesDir = pathModule.join(templatesPath(), "nextjs");
const fileExtension = isTypescriptProject ? ".ts" : ".js";
//pages/api/trigger.js or src/pages/api/trigger.js
const apiRoutePath = pathModule.join(path, "pages", "api", `trigger${fileExtension}`);
const apiRouteResult = await createFileFromTemplate({
templatePath: pathModule.join(templatesDir, "pagesApiRoute.js"),
replacements: {
routePathPrefix: pathAlias ? pathAlias + "/" : "../../",
},
outputPath: apiRoutePath,
});
if (!apiRouteResult.success) {
throw new Error("Failed to create API route file");
export async function detectNextVersion(path: string) {
const packageJsonContent = await readPackageJson(path);
if (!packageJsonContent) {
return null;
}
logger.success(`✔ Created API route at ${apiRoutePath}`);
await createJobsAndTriggerFile(path, endpointSlug, fileExtension, pathAlias, templatesDir);
const versionNumberPattern = /[\d.]+|latest/;
if (packageJsonContent.dependencies?.next !== undefined)
return packageJsonContent.dependencies?.next?.match(versionNumberPattern)?.at(0) ?? null;
if (packageJsonContent.devDependencies?.next !== undefined)
return packageJsonContent.devDependencies?.next?.match(versionNumberPattern)?.at(0) ?? null;
return null;
}
async function createTriggerAppRoute(
async function createTriggerRoute(options: {
path: string,
apiRoutePath: string,
template: string,
fileExtension: string,
endpointSlug: string,
isTypescriptProject: boolean,
pathAlias: string | undefined
) {
const templatesDir = pathModule.join(templatesPath(), "nextjs");
const fileExtension = isTypescriptProject ? ".ts" : ".js";
}) {
const { path, apiRoutePath, template, pathAlias, endpointSlug, fileExtension } = options;
//app/api/trigger/route.js or src/app/api/trigger/route.js
const apiRoutePath = pathModule.join(path, "app", "api", "trigger", `route${fileExtension}`);
const templatesDir = pathModule.join(templatesPath(), "nextjs");
const apiRouteResult = await createFileFromTemplate({
templatePath: pathModule.join(templatesDir, "appApiRoute.js"),
templatePath: pathModule.join(templatesDir, template),
replacements: {
routePathPrefix: pathAlias ? pathAlias + "/" : "../../",
},
@@ -192,6 +211,14 @@ async function createTriggerAppRoute(
throw new Error("Failed to create API route file");
}
logger.success(`✔ Created API route at ${apiRoutePath}`);
logger.info(
boxen(`If you're deploying to Vercel, configure your max duration in ${apiRoutePath}`, {
padding: 1,
margin: 1,
borderStyle: "double",
borderColor: "magenta",
})
);
await createJobsAndTriggerFile(path, endpointSlug, fileExtension, pathAlias, templatesDir);
}
@@ -1,5 +1,5 @@
import mock from "mock-fs";
import { NextJs, detectPagesOrAppDir, detectUseOfSrcDir } from ".";
import { NextJs, detectPagesOrAppDir, detectUseOfSrcDir, detectNextVersion } from ".";
import { getFramework } from "..";
import { pathExists } from "../../utils/fileSystem";
import { detectMiddlewareUsage } from "./middleware";
@@ -57,6 +57,35 @@ describe("Next project detection", () => {
});
});
describe("Next version detection", () => {
test("detect Nextjs latest version", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { next: "latest" } }),
});
const nextJsVersion = await detectNextVersion("");
expect(nextJsVersion).toEqual("latest");
});
test("detect Nextjs 13.0.0 version", async () => {
mock({
"package.json": JSON.stringify({ dependencies: { next: "13.0.0" } }),
});
const nextJsVersion = await detectNextVersion("");
expect(nextJsVersion).toEqual("13.0.0");
});
test("detect Nextjs version as a dev dependency", async () => {
mock({
"package.json": JSON.stringify({ devDependencies: { next: "^12.0.0" } }),
});
const nextJsVersion = await detectNextVersion("");
expect(nextJsVersion).toEqual("12.0.0");
});
});
describe("src directory", () => {
test("has src directory", async () => {
mock({
@@ -5,3 +5,6 @@ import "${routePathPrefix}jobs";
//this route is used to send and receive data with Trigger.dev
export const { POST, dynamic } = createAppRoute(client);
//uncomment this to set a higher max duration (it must be inside your plan limits). Full docs: https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration
//export const maxDuration = 60;
@@ -0,0 +1,15 @@
import { createPagesRoute } from "@trigger.dev/nextjs";
import { client } from "${routePathPrefix}trigger";
import "${routePathPrefix}jobs";
//uncomment this to set a higher max duration (it must be inside your plan limits). Full docs: https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration
//export const config = {
// maxDuration: 60,
//};
//this route is used to send and receive data with Trigger.dev
const { handler, config } = createPagesRoute(client);
export { config };
export default handler;