Skip to content

Commit 7760e09

Browse files
committed
Improved CLI init Next.js middleware detection
1 parent 3ca4456 commit 7760e09

File tree

4 files changed

+216
-41
lines changed

4 files changed

+216
-41
lines changed

.changeset/lucky-pants-kneel.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+
Improved CLI init Next.js middleware detection

packages/cli/src/frameworks/nextjs/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { logger } from "../../utils/logger";
1010
import { getPathAlias } from "../../utils/pathAlias";
1111
import { readPackageJson } from "../../utils/readPackageJson";
1212
import { standardWatchFilePaths } from "../watchConfig";
13+
import { telemetryClient } from "../../telemetry/telemetry";
1314
import { detectMiddlewareUsage } from "./middleware";
1415

1516
export class NextJs implements Framework {
@@ -65,7 +66,27 @@ export class NextJs implements Framework {
6566
path: string,
6667
options: { typescript: boolean; packageManager: PackageManager; endpointSlug: string }
6768
): Promise<void> {
68-
await detectMiddlewareUsage(path);
69+
const result = await detectMiddlewareUsage(path, options.typescript);
70+
if (result.hasMiddleware) {
71+
switch (result.conflict) {
72+
case "possible": {
73+
logger.warn(
74+
`⚠️ ⚠️ ⚠️ 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`
75+
);
76+
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
77+
break;
78+
}
79+
case "likely": {
80+
logger.warn(
81+
`🚨 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`
82+
);
83+
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
84+
break;
85+
}
86+
default:
87+
break;
88+
}
89+
}
6990
}
7091

7192
defaultHostnames = ["localhost"];

packages/cli/src/frameworks/nextjs/middleware.ts

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,87 @@ import pathModule from "path";
44
import { logger } from "../../utils/logger";
55
import { telemetryClient } from "../../telemetry/telemetry";
66
import { pathToRegexp } from "path-to-regexp";
7+
import { detectUseOfSrcDir } from ".";
78

8-
export async function detectMiddlewareUsage(path: string, usesSrcDir = false) {
9-
const middlewarePath = pathModule.join(path, usesSrcDir ? "src" : "", "middleware.ts");
9+
type Result =
10+
| {
11+
hasMiddleware: false;
12+
}
13+
| {
14+
hasMiddleware: true;
15+
conflict: "unlikely" | "possible" | "likely";
16+
middlewarePath: string;
17+
};
18+
19+
export async function detectMiddlewareUsage(path: string, typescript: boolean): Promise<Result> {
20+
const usesSrcDir = await detectUseOfSrcDir(path);
21+
const middlewarePath = pathModule.join(
22+
path,
23+
usesSrcDir ? "src" : "",
24+
`middleware.${typescript ? "ts" : "js"}`
25+
);
26+
27+
try {
28+
return await detectMiddleware(path, typescript, middlewarePath);
29+
} catch (e) {
30+
return {
31+
hasMiddleware: true,
32+
conflict: "possible",
33+
middlewarePath: pathModule.relative(process.cwd(), middlewarePath),
34+
};
35+
}
36+
}
1037

38+
async function detectMiddleware(
39+
path: string,
40+
typescript: boolean,
41+
middlewarePath: string
42+
): Promise<Result> {
1143
const middlewareExists = await pathExists(middlewarePath);
12-
1344
if (!middlewareExists) {
14-
return;
45+
return { hasMiddleware: false };
1546
}
1647

48+
const middlewareRelativeFilePath = pathModule.relative(process.cwd(), middlewarePath);
49+
1750
const matcher = await getMiddlewareConfigMatcher(middlewarePath);
1851

1952
if (!matcher || matcher.length === 0) {
20-
logger.warn(
21-
`⚠️ ⚠️ ⚠️ It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
22-
process.cwd(),
23-
middlewarePath
24-
)} which can cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
25-
);
26-
27-
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
28-
return;
53+
return {
54+
hasMiddleware: true,
55+
conflict: "possible",
56+
middlewarePath: middlewareRelativeFilePath,
57+
};
2958
}
3059

3160
if (matcher.length === 0) {
32-
return;
61+
return {
62+
hasMiddleware: true,
63+
conflict: "unlikely",
64+
middlewarePath: middlewareRelativeFilePath,
65+
};
3366
}
3467

35-
if (typeof matcher === "string") {
36-
const matcherRegex = pathToRegexp(matcher);
37-
38-
// 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
39-
if (matcherRegex.test("/api/trigger")) {
40-
logger.warn(
41-
`🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
42-
process.cwd(),
43-
middlewarePath
44-
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
45-
);
46-
telemetryClient.init.warning("middleware_conflict_api_trigger", { projectPath: path });
47-
}
48-
} else if (Array.isArray(matcher) && matcher.every((m) => typeof m === "string")) {
49-
const matcherRegexes = matcher.map((m) => pathToRegexp(m));
50-
51-
if (matcherRegexes.some((r) => r.test("/api/trigger"))) {
52-
logger.warn(
53-
`🚨 It looks like there might be conflicting Next.js middleware in ${pathModule.relative(
54-
process.cwd(),
55-
middlewarePath
56-
)} which will cause issues with Trigger.dev. Please see https://trigger.dev/docs/documentation/guides/platforms/nextjs#middleware`
57-
);
58-
telemetryClient.init.warning("middleware_conflict", { projectPath: path });
59-
}
68+
const matcherRegexes = matcher.map((m) => pathToRegexp(m));
69+
if (matcherRegexes.some((r) => r.test("/api/trigger"))) {
70+
return {
71+
hasMiddleware: true,
72+
conflict: "likely",
73+
middlewarePath: middlewareRelativeFilePath,
74+
};
6075
}
76+
77+
return {
78+
hasMiddleware: true,
79+
conflict: "possible",
80+
middlewarePath: middlewareRelativeFilePath,
81+
};
6182
}
6283

6384
async function getMiddlewareConfigMatcher(path: string): Promise<Array<string>> {
6485
const fileContent = await fs.readFile(path, "utf-8");
6586

66-
const regex = /matcher:\s*(\[.*\]|".*")/s;
87+
const regex = /matcher:\s*(\[.*\]|["'].*["'])/g;
6788
let match = regex.exec(fileContent);
6889

6990
if (!match) {

packages/cli/src/frameworks/nextjs/nextjs.test.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import mock from "mock-fs";
22
import { NextJs, detectPagesOrAppDir, detectUseOfSrcDir } from ".";
33
import { getFramework } from "..";
44
import { pathExists } from "../../utils/fileSystem";
5+
import { detectMiddlewareUsage } from "./middleware";
56

67
afterEach(() => {
78
mock.restore();
@@ -257,3 +258,130 @@ describe("app install", () => {
257258
expect(await pathExists("jobs/examples.ts")).toEqual(true);
258259
});
259260
});
261+
262+
describe("Next middleware detection", () => {
263+
test("no middleware", async () => {
264+
mock({});
265+
266+
const result = await detectMiddlewareUsage("", false);
267+
expect(result.hasMiddleware).toEqual(false);
268+
});
269+
270+
test("Basic middleware", async () => {
271+
mock({
272+
"middleware.js": `import { NextResponse } from 'next/server'
273+
274+
export function middleware(request) {
275+
return NextResponse.redirect(new URL('/home', request.url))
276+
}
277+
278+
// See "Matching Paths" below to learn more
279+
export const config = {
280+
matcher: '/about/:path*',
281+
}`,
282+
});
283+
const result = await detectMiddlewareUsage("", false);
284+
expect(result.hasMiddleware).toEqual(true);
285+
if (!result.hasMiddleware) throw "Should have middleware";
286+
expect(result.middlewarePath).toEqual("middleware.js");
287+
expect(result.conflict).toEqual("possible");
288+
});
289+
290+
test("Wildcard that throws middleware", async () => {
291+
mock({
292+
"middleware.js": `export const config = {
293+
matcher: "*",
294+
}`,
295+
});
296+
297+
const result = await detectMiddlewareUsage("", false);
298+
expect(result.hasMiddleware).toEqual(true);
299+
if (!result.hasMiddleware) throw "Should have middleware";
300+
expect(result.middlewarePath).toEqual("middleware.js");
301+
expect(result.conflict).toEqual("possible");
302+
});
303+
304+
test("Array middleware", async () => {
305+
mock({
306+
"middleware.js": `export const config = {
307+
matcher: ['/about/:path*', "/dashboard/:path*"],
308+
}`,
309+
});
310+
311+
const result = await detectMiddlewareUsage("", false);
312+
expect(result.hasMiddleware).toEqual(true);
313+
if (!result.hasMiddleware) throw "Should have middleware";
314+
expect(result.middlewarePath).toEqual("middleware.js");
315+
expect(result.conflict).toEqual("possible");
316+
});
317+
318+
test("With dashes middleware", async () => {
319+
mock({
320+
"middleware.js": `export const config = {
321+
matcher: ["/configurations-test/:path*", "/projects/:path*"],
322+
};`,
323+
});
324+
325+
const result = await detectMiddlewareUsage("", false);
326+
expect(result.hasMiddleware).toEqual(true);
327+
if (!result.hasMiddleware) throw "Should have middleware";
328+
expect(result.middlewarePath).toEqual("middleware.js");
329+
expect(result.conflict).toEqual("possible");
330+
});
331+
332+
test("Likely double quoted string", async () => {
333+
mock({
334+
"middleware.js": `export const config = {
335+
matcher: "/(.*)",
336+
};`,
337+
});
338+
339+
const result = await detectMiddlewareUsage("", false);
340+
expect(result.hasMiddleware).toEqual(true);
341+
if (!result.hasMiddleware) throw "Should have middleware";
342+
expect(result.middlewarePath).toEqual("middleware.js");
343+
expect(result.conflict).toEqual("likely");
344+
});
345+
346+
test("Likely single quoted string", async () => {
347+
mock({
348+
"middleware.js": `export const config = {
349+
matcher: '/(.*)',
350+
};`,
351+
});
352+
353+
const result = await detectMiddlewareUsage("", false);
354+
expect(result.hasMiddleware).toEqual(true);
355+
if (!result.hasMiddleware) throw "Should have middleware";
356+
expect(result.middlewarePath).toEqual("middleware.js");
357+
expect(result.conflict).toEqual("likely");
358+
});
359+
360+
test("Likely double quoted array", async () => {
361+
mock({
362+
"middleware.js": `export const config = {
363+
matcher: ["/pages/", "/(.*)"],
364+
};`,
365+
});
366+
367+
const result = await detectMiddlewareUsage("", false);
368+
expect(result.hasMiddleware).toEqual(true);
369+
if (!result.hasMiddleware) throw "Should have middleware";
370+
expect(result.middlewarePath).toEqual("middleware.js");
371+
expect(result.conflict).toEqual("likely");
372+
});
373+
374+
test("Likely single quoted array", async () => {
375+
mock({
376+
"middleware.js": `export const config = {
377+
matcher: ['/pages/', '/(.*)'],
378+
};`,
379+
});
380+
381+
const result = await detectMiddlewareUsage("", false);
382+
expect(result.hasMiddleware).toEqual(true);
383+
if (!result.hasMiddleware) throw "Should have middleware";
384+
expect(result.middlewarePath).toEqual("middleware.js");
385+
expect(result.conflict).toEqual("likely");
386+
});
387+
});

0 commit comments

Comments
 (0)