Improved CLI init Next.js middleware detection
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/cli": patch
|
||||
---
|
||||
|
||||
Improved CLI init Next.js middleware detection
|
||||
@@ -10,6 +10,7 @@ import { logger } from "../../utils/logger";
|
||||
import { getPathAlias } from "../../utils/pathAlias";
|
||||
import { readPackageJson } from "../../utils/readPackageJson";
|
||||
import { standardWatchFilePaths } from "../watchConfig";
|
||||
import { telemetryClient } from "../../telemetry/telemetry";
|
||||
import { detectMiddlewareUsage } from "./middleware";
|
||||
|
||||
export class NextJs implements Framework {
|
||||
@@ -65,7 +66,27 @@ export class NextJs implements Framework {
|
||||
path: string,
|
||||
options: { typescript: boolean; packageManager: PackageManager; endpointSlug: string }
|
||||
): Promise<void> {
|
||||
await detectMiddlewareUsage(path);
|
||||
const result = await detectMiddlewareUsage(path, options.typescript);
|
||||
if (result.hasMiddleware) {
|
||||
switch (result.conflict) {
|
||||
case "possible": {
|
||||
logger.warn(
|
||||
`⚠️ ⚠️ ⚠️ It looks like there might be conflicting Next.js middleware in ${result.middlewarePath} which can cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
|
||||
);
|
||||
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
|
||||
break;
|
||||
}
|
||||
case "likely": {
|
||||
logger.warn(
|
||||
`🚨 It looks like there might be conflicting Next.js middleware in ${result.middlewarePath} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
|
||||
);
|
||||
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultHostnames = ["localhost"];
|
||||
|
||||
@@ -4,66 +4,87 @@ import pathModule from "path";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { telemetryClient } from "../../telemetry/telemetry";
|
||||
import { pathToRegexp } from "path-to-regexp";
|
||||
import { detectUseOfSrcDir } from ".";
|
||||
|
||||
export async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
|
||||
const middlewarePath = pathModule.join(path, usesSrcDir ? "src" : "", "middleware.ts");
|
||||
type Result =
|
||||
| {
|
||||
hasMiddleware: false;
|
||||
}
|
||||
| {
|
||||
hasMiddleware: true;
|
||||
conflict: "unlikely" | "possible" | "likely";
|
||||
middlewarePath: string;
|
||||
};
|
||||
|
||||
const middlewareExists = await pathExists(middlewarePath);
|
||||
export async function detectMiddlewareUsage(path: string, typescript: boolean): Promise<Result> {
|
||||
const usesSrcDir = await detectUseOfSrcDir(path);
|
||||
const middlewarePath = pathModule.join(
|
||||
path,
|
||||
usesSrcDir ? "src" : "",
|
||||
`middleware.${typescript ? "ts" : "js"}`
|
||||
);
|
||||
|
||||
if (!middlewareExists) {
|
||||
return;
|
||||
try {
|
||||
return await detectMiddleware(path, typescript, middlewarePath);
|
||||
} catch (e) {
|
||||
return {
|
||||
hasMiddleware: true,
|
||||
conflict: "possible",
|
||||
middlewarePath: pathModule.relative(process.cwd(), middlewarePath),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function detectMiddleware(
|
||||
path: string,
|
||||
typescript: boolean,
|
||||
middlewarePath: string
|
||||
): Promise<Result> {
|
||||
const middlewareExists = await pathExists(middlewarePath);
|
||||
if (!middlewareExists) {
|
||||
return { hasMiddleware: false };
|
||||
}
|
||||
|
||||
const middlewareRelativeFilePath = pathModule.relative(process.cwd(), middlewarePath);
|
||||
|
||||
const matcher = await getMiddlewareConfigMatcher(middlewarePath);
|
||||
|
||||
if (!matcher || matcher.length === 0) {
|
||||
logger.warn(
|
||||
`⚠️ ⚠️ ⚠️ It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
|
||||
process.cwd(),
|
||||
middlewarePath
|
||||
)} which can cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
|
||||
);
|
||||
|
||||
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
|
||||
return;
|
||||
return {
|
||||
hasMiddleware: true,
|
||||
conflict: "possible",
|
||||
middlewarePath: middlewareRelativeFilePath,
|
||||
};
|
||||
}
|
||||
|
||||
if (matcher.length === 0) {
|
||||
return;
|
||||
return {
|
||||
hasMiddleware: true,
|
||||
conflict: "unlikely",
|
||||
middlewarePath: middlewareRelativeFilePath,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof matcher === "string") {
|
||||
const matcherRegex = pathToRegexp(matcher);
|
||||
|
||||
// Check to see if /api/trigger matches the regex, if it does, then we need to output a warning with a link to the docs to fix it
|
||||
if (matcherRegex.test("/api/trigger")) {
|
||||
logger.warn(
|
||||
`🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
|
||||
process.cwd(),
|
||||
middlewarePath
|
||||
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
|
||||
);
|
||||
telemetryClient.init.warning("middleware_conflict_api_trigger", { projectPath: path });
|
||||
}
|
||||
} else if (Array.isArray(matcher) && matcher.every((m) => typeof m === "string")) {
|
||||
const matcherRegexes = matcher.map((m) => pathToRegexp(m));
|
||||
|
||||
if (matcherRegexes.some((r) => r.test("/api/trigger"))) {
|
||||
logger.warn(
|
||||
`🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
|
||||
process.cwd(),
|
||||
middlewarePath
|
||||
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
|
||||
);
|
||||
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
|
||||
}
|
||||
const matcherRegexes = matcher.map((m) => pathToRegexp(m));
|
||||
if (matcherRegexes.some((r) => r.test("/api/trigger"))) {
|
||||
return {
|
||||
hasMiddleware: true,
|
||||
conflict: "likely",
|
||||
middlewarePath: middlewareRelativeFilePath,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasMiddleware: true,
|
||||
conflict: "possible",
|
||||
middlewarePath: middlewareRelativeFilePath,
|
||||
};
|
||||
}
|
||||
|
||||
async function getMiddlewareConfigMatcher(path: string): Promise<Array<string>> {
|
||||
const fileContent = await fs.readFile(path, "utf-8");
|
||||
|
||||
const regex = /matcher:\s*(\[.*\]|".*")/s;
|
||||
const regex = /matcher:\s*(\[.*\]|["'].*["'])/g;
|
||||
let match = regex.exec(fileContent);
|
||||
|
||||
if (!match) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import mock from "mock-fs";
|
||||
import { NextJs, detectPagesOrAppDir, detectUseOfSrcDir } from ".";
|
||||
import { getFramework } from "..";
|
||||
import { pathExists } from "../../utils/fileSystem";
|
||||
import { detectMiddlewareUsage } from "./middleware";
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
@@ -257,3 +258,130 @@ describe("app install", () => {
|
||||
expect(await pathExists("jobs/examples.ts")).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Next middleware detection", () => {
|
||||
test("no middleware", async () => {
|
||||
mock({});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(false);
|
||||
});
|
||||
|
||||
test("Basic middleware", async () => {
|
||||
mock({
|
||||
"middleware.js": `import { NextResponse } from 'next/server'
|
||||
|
||||
export function middleware(request) {
|
||||
return NextResponse.redirect(new URL('/home', request.url))
|
||||
}
|
||||
|
||||
// See "Matching Paths" below to learn more
|
||||
export const config = {
|
||||
matcher: '/about/:path*',
|
||||
}`,
|
||||
});
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("possible");
|
||||
});
|
||||
|
||||
test("Wildcard that throws middleware", async () => {
|
||||
mock({
|
||||
"middleware.js": `export const config = {
|
||||
matcher: "*",
|
||||
}`,
|
||||
});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("possible");
|
||||
});
|
||||
|
||||
test("Array middleware", async () => {
|
||||
mock({
|
||||
"middleware.js": `export const config = {
|
||||
matcher: ['/about/:path*', "/dashboard/:path*"],
|
||||
}`,
|
||||
});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("possible");
|
||||
});
|
||||
|
||||
test("With dashes middleware", async () => {
|
||||
mock({
|
||||
"middleware.js": `export const config = {
|
||||
matcher: ["/configurations-test/:path*", "/projects/:path*"],
|
||||
};`,
|
||||
});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("possible");
|
||||
});
|
||||
|
||||
test("Likely double quoted string", async () => {
|
||||
mock({
|
||||
"middleware.js": `export const config = {
|
||||
matcher: "/(.*)",
|
||||
};`,
|
||||
});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("likely");
|
||||
});
|
||||
|
||||
test("Likely single quoted string", async () => {
|
||||
mock({
|
||||
"middleware.js": `export const config = {
|
||||
matcher: '/(.*)',
|
||||
};`,
|
||||
});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("likely");
|
||||
});
|
||||
|
||||
test("Likely double quoted array", async () => {
|
||||
mock({
|
||||
"middleware.js": `export const config = {
|
||||
matcher: ["/pages/", "/(.*)"],
|
||||
};`,
|
||||
});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("likely");
|
||||
});
|
||||
|
||||
test("Likely single quoted array", async () => {
|
||||
mock({
|
||||
"middleware.js": `export const config = {
|
||||
matcher: ['/pages/', '/(.*)'],
|
||||
};`,
|
||||
});
|
||||
|
||||
const result = await detectMiddlewareUsage("", false);
|
||||
expect(result.hasMiddleware).toEqual(true);
|
||||
if (!result.hasMiddleware) throw "Should have middleware";
|
||||
expect(result.middlewarePath).toEqual("middleware.js");
|
||||
expect(result.conflict).toEqual("likely");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user