@fourteensystems/prodcheck 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -0
- package/bin/prodcheck.mjs +2 -0
- package/dist/cli/commands/baseline.d.ts +7 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +22 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +14 -0
- package/dist/cli/commands/ci.d.ts.map +1 -0
- package/dist/cli/commands/ci.js +104 -0
- package/dist/cli/commands/ci.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +2 -0
- package/dist/cli/commands/explain.d.ts.map +1 -0
- package/dist/cli/commands/explain.js +20 -0
- package/dist/cli/commands/explain.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +127 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +2 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +13 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +10 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +65 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/waive.d.ts +8 -0
- package/dist/cli/commands/waive.d.ts.map +1 -0
- package/dist/cli/commands/waive.js +34 -0
- package/dist/cli/commands/waive.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/engine/baseline.d.ts +11 -0
- package/dist/engine/baseline.d.ts.map +1 -0
- package/dist/engine/baseline.js +39 -0
- package/dist/engine/baseline.js.map +1 -0
- package/dist/engine/baseline.test.d.ts +2 -0
- package/dist/engine/baseline.test.d.ts.map +1 -0
- package/dist/engine/baseline.test.js +135 -0
- package/dist/engine/baseline.test.js.map +1 -0
- package/dist/engine/config.d.ts +8 -0
- package/dist/engine/config.d.ts.map +1 -0
- package/dist/engine/config.js +134 -0
- package/dist/engine/config.js.map +1 -0
- package/dist/engine/config.test.d.ts +2 -0
- package/dist/engine/config.test.d.ts.map +1 -0
- package/dist/engine/config.test.js +107 -0
- package/dist/engine/config.test.js.map +1 -0
- package/dist/engine/extensions/load.d.ts +11 -0
- package/dist/engine/extensions/load.d.ts.map +1 -0
- package/dist/engine/extensions/load.js +26 -0
- package/dist/engine/extensions/load.js.map +1 -0
- package/dist/engine/extensions/registry.d.ts +5 -0
- package/dist/engine/extensions/registry.d.ts.map +1 -0
- package/dist/engine/extensions/registry.js +11 -0
- package/dist/engine/extensions/registry.js.map +1 -0
- package/dist/engine/extensions/types.d.ts +51 -0
- package/dist/engine/extensions/types.d.ts.map +1 -0
- package/dist/engine/extensions/types.js +2 -0
- package/dist/engine/extensions/types.js.map +1 -0
- package/dist/engine/license.d.ts +40 -0
- package/dist/engine/license.d.ts.map +1 -0
- package/dist/engine/license.js +104 -0
- package/dist/engine/license.js.map +1 -0
- package/dist/engine/report.d.ts +5 -0
- package/dist/engine/report.d.ts.map +1 -0
- package/dist/engine/report.js +115 -0
- package/dist/engine/report.js.map +1 -0
- package/dist/engine/run.d.ts +11 -0
- package/dist/engine/run.d.ts.map +1 -0
- package/dist/engine/run.js +105 -0
- package/dist/engine/run.js.map +1 -0
- package/dist/engine/sarif.d.ts +3 -0
- package/dist/engine/sarif.d.ts.map +1 -0
- package/dist/engine/sarif.js +58 -0
- package/dist/engine/sarif.js.map +1 -0
- package/dist/engine/sarif.test.d.ts +2 -0
- package/dist/engine/sarif.test.d.ts.map +1 -0
- package/dist/engine/sarif.test.js +152 -0
- package/dist/engine/sarif.test.js.map +1 -0
- package/dist/engine/score.d.ts +13 -0
- package/dist/engine/score.d.ts.map +1 -0
- package/dist/engine/score.js +116 -0
- package/dist/engine/score.js.map +1 -0
- package/dist/engine/score.test.d.ts +2 -0
- package/dist/engine/score.test.d.ts.map +1 -0
- package/dist/engine/score.test.js +227 -0
- package/dist/engine/score.test.js.map +1 -0
- package/dist/engine/types.d.ts +123 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +2 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/version.d.ts +5 -0
- package/dist/engine/version.d.ts.map +1 -0
- package/dist/engine/version.js +15 -0
- package/dist/engine/version.js.map +1 -0
- package/dist/engine/waivers.d.ts +9 -0
- package/dist/engine/waivers.d.ts.map +1 -0
- package/dist/engine/waivers.js +55 -0
- package/dist/engine/waivers.js.map +1 -0
- package/dist/engine/waivers.test.d.ts +2 -0
- package/dist/engine/waivers.test.d.ts.map +1 -0
- package/dist/engine/waivers.test.js +147 -0
- package/dist/engine/waivers.test.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/next/deps.d.ts +4 -0
- package/dist/next/deps.d.ts.map +1 -0
- package/dist/next/deps.js +118 -0
- package/dist/next/deps.js.map +1 -0
- package/dist/next/deps.test.d.ts +2 -0
- package/dist/next/deps.test.d.ts.map +1 -0
- package/dist/next/deps.test.js +249 -0
- package/dist/next/deps.test.js.map +1 -0
- package/dist/next/detect.d.ts +10 -0
- package/dist/next/detect.d.ts.map +1 -0
- package/dist/next/detect.js +57 -0
- package/dist/next/detect.js.map +1 -0
- package/dist/next/detect.test.d.ts +2 -0
- package/dist/next/detect.test.d.ts.map +1 -0
- package/dist/next/detect.test.js +74 -0
- package/dist/next/detect.test.js.map +1 -0
- package/dist/next/index.d.ts +5 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +59 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/middleware.d.ts +3 -0
- package/dist/next/middleware.d.ts.map +1 -0
- package/dist/next/middleware.js +48 -0
- package/dist/next/middleware.js.map +1 -0
- package/dist/next/middleware.test.d.ts +2 -0
- package/dist/next/middleware.test.d.ts.map +1 -0
- package/dist/next/middleware.test.js +203 -0
- package/dist/next/middleware.test.js.map +1 -0
- package/dist/next/routes.d.ts +10 -0
- package/dist/next/routes.d.ts.map +1 -0
- package/dist/next/routes.js +172 -0
- package/dist/next/routes.js.map +1 -0
- package/dist/next/routes.test.d.ts +2 -0
- package/dist/next/routes.test.d.ts.map +1 -0
- package/dist/next/routes.test.js +175 -0
- package/dist/next/routes.test.js.map +1 -0
- package/dist/next/server-actions.d.ts +4 -0
- package/dist/next/server-actions.d.ts.map +1 -0
- package/dist/next/server-actions.js +107 -0
- package/dist/next/server-actions.js.map +1 -0
- package/dist/next/server-actions.test.d.ts +2 -0
- package/dist/next/server-actions.test.d.ts.map +1 -0
- package/dist/next/server-actions.test.js +138 -0
- package/dist/next/server-actions.test.js.map +1 -0
- package/dist/next/trpc.d.ts +3 -0
- package/dist/next/trpc.d.ts.map +1 -0
- package/dist/next/trpc.js +312 -0
- package/dist/next/trpc.js.map +1 -0
- package/dist/next/types.d.ts +144 -0
- package/dist/next/types.d.ts.map +1 -0
- package/dist/next/types.js +2 -0
- package/dist/next/types.js.map +1 -0
- package/dist/next/wrappers.d.ts +10 -0
- package/dist/next/wrappers.d.ts.map +1 -0
- package/dist/next/wrappers.js +536 -0
- package/dist/next/wrappers.js.map +1 -0
- package/dist/next/wrappers.test.d.ts +2 -0
- package/dist/next/wrappers.test.d.ts.map +1 -0
- package/dist/next/wrappers.test.js +361 -0
- package/dist/next/wrappers.test.js.map +1 -0
- package/dist/rules/auth-boundary-missing.d.ts +5 -0
- package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
- package/dist/rules/auth-boundary-missing.js +463 -0
- package/dist/rules/auth-boundary-missing.js.map +1 -0
- package/dist/rules/auth-boundary-missing.test.d.ts +2 -0
- package/dist/rules/auth-boundary-missing.test.d.ts.map +1 -0
- package/dist/rules/auth-boundary-missing.test.js +492 -0
- package/dist/rules/auth-boundary-missing.test.js.map +1 -0
- package/dist/rules/index.d.ts +12 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +95 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/input-validation-missing.d.ts +5 -0
- package/dist/rules/input-validation-missing.d.ts.map +1 -0
- package/dist/rules/input-validation-missing.js +272 -0
- package/dist/rules/input-validation-missing.js.map +1 -0
- package/dist/rules/input-validation-missing.test.d.ts +2 -0
- package/dist/rules/input-validation-missing.test.d.ts.map +1 -0
- package/dist/rules/input-validation-missing.test.js +449 -0
- package/dist/rules/input-validation-missing.test.js.map +1 -0
- package/dist/rules/rate-limit-missing.d.ts +5 -0
- package/dist/rules/rate-limit-missing.d.ts.map +1 -0
- package/dist/rules/rate-limit-missing.js +316 -0
- package/dist/rules/rate-limit-missing.js.map +1 -0
- package/dist/rules/rate-limit-missing.test.d.ts +2 -0
- package/dist/rules/rate-limit-missing.test.d.ts.map +1 -0
- package/dist/rules/rate-limit-missing.test.js +381 -0
- package/dist/rules/rate-limit-missing.test.js.map +1 -0
- package/dist/rules/tenancy-scope-missing.d.ts +5 -0
- package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
- package/dist/rules/tenancy-scope-missing.js +149 -0
- package/dist/rules/tenancy-scope-missing.js.map +1 -0
- package/dist/rules/wrapper-unrecognized.d.ts +5 -0
- package/dist/rules/wrapper-unrecognized.d.ts.map +1 -0
- package/dist/rules/wrapper-unrecognized.js +81 -0
- package/dist/rules/wrapper-unrecognized.js.map +1 -0
- package/dist/util/hof.d.ts +22 -0
- package/dist/util/hof.d.ts.map +1 -0
- package/dist/util/hof.js +99 -0
- package/dist/util/hof.js.map +1 -0
- package/dist/util/hof.test.d.ts +2 -0
- package/dist/util/hof.test.d.ts.map +1 -0
- package/dist/util/hof.test.js +79 -0
- package/dist/util/hof.test.js.map +1 -0
- package/dist/util/monorepo.d.ts +6 -0
- package/dist/util/monorepo.d.ts.map +1 -0
- package/dist/util/monorepo.js +29 -0
- package/dist/util/monorepo.js.map +1 -0
- package/dist/util/outbound-fetch.d.ts +14 -0
- package/dist/util/outbound-fetch.d.ts.map +1 -0
- package/dist/util/outbound-fetch.js +59 -0
- package/dist/util/outbound-fetch.js.map +1 -0
- package/dist/util/outbound-fetch.test.d.ts +2 -0
- package/dist/util/outbound-fetch.test.d.ts.map +1 -0
- package/dist/util/outbound-fetch.test.js +83 -0
- package/dist/util/outbound-fetch.test.js.map +1 -0
- package/dist/util/paths.d.ts +6 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +18 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/resolve.d.ts +30 -0
- package/dist/util/resolve.d.ts.map +1 -0
- package/dist/util/resolve.js +306 -0
- package/dist/util/resolve.js.map +1 -0
- package/dist/util/resolve.test.d.ts +2 -0
- package/dist/util/resolve.test.d.ts.map +1 -0
- package/dist/util/resolve.test.js +186 -0
- package/dist/util/resolve.test.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { detectNextAppRouter } from "./detect.js";
|
|
6
|
+
describe("detectNextAppRouter", () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
tmpDir = mkdtempSync(path.join(os.tmpdir(), "prodcheck-detect-"));
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
it("fails when package.json is missing", () => {
|
|
15
|
+
const result = detectNextAppRouter(tmpDir);
|
|
16
|
+
expect(result.ok).toBe(false);
|
|
17
|
+
expect(result.reason).toContain("package.json not found");
|
|
18
|
+
});
|
|
19
|
+
it("fails when next is not a dependency", () => {
|
|
20
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ dependencies: { react: "18" } }));
|
|
21
|
+
const result = detectNextAppRouter(tmpDir);
|
|
22
|
+
expect(result.ok).toBe(false);
|
|
23
|
+
expect(result.reason).toContain("next dependency not found");
|
|
24
|
+
});
|
|
25
|
+
it("fails when app/ directory is missing", () => {
|
|
26
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ dependencies: { next: "14" } }));
|
|
27
|
+
const result = detectNextAppRouter(tmpDir);
|
|
28
|
+
expect(result.ok).toBe(false);
|
|
29
|
+
expect(result.reason).toContain("app/ directory not found");
|
|
30
|
+
});
|
|
31
|
+
it("detects app/ directory", () => {
|
|
32
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ dependencies: { next: "14" } }));
|
|
33
|
+
mkdirSync(path.join(tmpDir, "app"));
|
|
34
|
+
const result = detectNextAppRouter(tmpDir);
|
|
35
|
+
expect(result.ok).toBe(true);
|
|
36
|
+
expect(result.appDir).toBe("app");
|
|
37
|
+
});
|
|
38
|
+
it("detects src/app/ directory", () => {
|
|
39
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ dependencies: { next: "14" } }));
|
|
40
|
+
mkdirSync(path.join(tmpDir, "src", "app"), { recursive: true });
|
|
41
|
+
const result = detectNextAppRouter(tmpDir);
|
|
42
|
+
expect(result.ok).toBe(true);
|
|
43
|
+
expect(result.appDir).toBe("src/app");
|
|
44
|
+
});
|
|
45
|
+
it("detects next in devDependencies", () => {
|
|
46
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ devDependencies: { next: "14" } }));
|
|
47
|
+
mkdirSync(path.join(tmpDir, "app"));
|
|
48
|
+
const result = detectNextAppRouter(tmpDir);
|
|
49
|
+
expect(result.ok).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it("detects route handlers", () => {
|
|
52
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ dependencies: { next: "14" } }));
|
|
53
|
+
mkdirSync(path.join(tmpDir, "app", "api", "test"), { recursive: true });
|
|
54
|
+
writeFileSync(path.join(tmpDir, "app", "api", "test", "route.ts"), "export async function GET() {}");
|
|
55
|
+
const result = detectNextAppRouter(tmpDir);
|
|
56
|
+
expect(result.ok).toBe(true);
|
|
57
|
+
expect(result.hasRouteHandlers).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
it("detects server actions", () => {
|
|
60
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ dependencies: { next: "14" } }));
|
|
61
|
+
mkdirSync(path.join(tmpDir, "app", "actions"), { recursive: true });
|
|
62
|
+
writeFileSync(path.join(tmpDir, "app", "actions", "create.ts"), '"use server"\nexport async function create() {}');
|
|
63
|
+
const result = detectNextAppRouter(tmpDir);
|
|
64
|
+
expect(result.ok).toBe(true);
|
|
65
|
+
expect(result.hasServerActions).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it("handles malformed package.json", () => {
|
|
68
|
+
writeFileSync(path.join(tmpDir, "package.json"), "not json");
|
|
69
|
+
const result = detectNextAppRouter(tmpDir);
|
|
70
|
+
expect(result.ok).toBe(false);
|
|
71
|
+
expect(result.reason).toContain("Failed to parse package.json");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=detect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.test.js","sourceRoot":"","sources":["../../src/next/detect.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NextIndex } from "./types.js";
|
|
2
|
+
export type { NextIndex } from "./types.js";
|
|
3
|
+
export { detectNextAppRouter } from "./detect.js";
|
|
4
|
+
export declare function buildNextIndex(rootDir: string, exclude: string[], onProgress?: (step: string) => void): Promise<NextIndex>;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU5C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAClC,OAAO,CAAC,SAAS,CAAC,CA6DpB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { detectNextAppRouter } from "./detect.js";
|
|
4
|
+
import { readDeps, defaultHintsFromDeps } from "./deps.js";
|
|
5
|
+
import { analyzeMiddleware } from "./middleware.js";
|
|
6
|
+
import { findRouteHandlers, classifyMutationRoutes } from "./routes.js";
|
|
7
|
+
import { findServerActions, classifyMutationActions } from "./server-actions.js";
|
|
8
|
+
import { buildTrpcIndex } from "./trpc.js";
|
|
9
|
+
import { buildWrapperIndex, computeProtection } from "./wrappers.js";
|
|
10
|
+
import { loadTsconfigPaths } from "../util/resolve.js";
|
|
11
|
+
export { detectNextAppRouter } from "./detect.js";
|
|
12
|
+
export async function buildNextIndex(rootDir, exclude, onProgress) {
|
|
13
|
+
const progress = onProgress ?? (() => { });
|
|
14
|
+
const det = detectNextAppRouter(rootDir);
|
|
15
|
+
if (!det.ok) {
|
|
16
|
+
throw new Error(`Prodcheck v1 supports Next.js App Router only: ${det.reason ?? "unknown reason"}`);
|
|
17
|
+
}
|
|
18
|
+
const { appDir } = det;
|
|
19
|
+
progress("Reading dependencies");
|
|
20
|
+
const deps = readDeps(rootDir);
|
|
21
|
+
// Check for middleware in standard locations
|
|
22
|
+
const hasMiddlewareTs = existsSync(path.join(rootDir, "middleware.ts"))
|
|
23
|
+
|| existsSync(path.join(rootDir, "middleware.js"))
|
|
24
|
+
|| existsSync(path.join(rootDir, "src/middleware.ts"))
|
|
25
|
+
|| existsSync(path.join(rootDir, "src/middleware.js"));
|
|
26
|
+
const hints = defaultHintsFromDeps(deps, hasMiddlewareTs);
|
|
27
|
+
progress("Analyzing middleware");
|
|
28
|
+
const middleware = analyzeMiddleware(rootDir);
|
|
29
|
+
progress("Discovering routes");
|
|
30
|
+
const allRoutes = await findRouteHandlers(rootDir, exclude, appDir);
|
|
31
|
+
const mutationRoutes = classifyMutationRoutes(allRoutes);
|
|
32
|
+
progress("Discovering server actions");
|
|
33
|
+
const allActions = await findServerActions(rootDir, exclude, appDir);
|
|
34
|
+
const mutationActions = classifyMutationActions(allActions);
|
|
35
|
+
progress("Analyzing tRPC procedures");
|
|
36
|
+
const trpc = await buildTrpcIndex(rootDir, appDir, exclude);
|
|
37
|
+
// Wrapper introspection: resolve, analyze, compute protection
|
|
38
|
+
progress("Resolving wrappers");
|
|
39
|
+
const tsconfigPaths = loadTsconfigPaths(rootDir);
|
|
40
|
+
const resolveOpts = { rootDir, tsconfigPaths };
|
|
41
|
+
const wrappers = buildWrapperIndex(allRoutes, rootDir, resolveOpts, hints.auth.functions, hints.rateLimit.wrappers);
|
|
42
|
+
// Compute protection summary for each route
|
|
43
|
+
for (const route of allRoutes) {
|
|
44
|
+
route.protection = computeProtection(route, wrappers, middleware, hints, rootDir);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
version: 1,
|
|
48
|
+
framework: "next-app-router",
|
|
49
|
+
rootDir,
|
|
50
|
+
deps,
|
|
51
|
+
hints,
|
|
52
|
+
middleware,
|
|
53
|
+
wrappers,
|
|
54
|
+
routes: { all: allRoutes, mutationRoutes },
|
|
55
|
+
serverActions: { all: allActions, mutationActions: mutationActions },
|
|
56
|
+
trpc,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/next/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGtD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAiDtE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { findWorkspaceRoot } from "../util/monorepo.js";
|
|
4
|
+
export function analyzeMiddleware(rootDir) {
|
|
5
|
+
// Next.js middleware can be at root or src/
|
|
6
|
+
const candidates = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
|
|
7
|
+
let file;
|
|
8
|
+
let src = "";
|
|
9
|
+
for (const candidate of candidates) {
|
|
10
|
+
const abs = path.join(rootDir, candidate);
|
|
11
|
+
if (existsSync(abs)) {
|
|
12
|
+
file = candidate;
|
|
13
|
+
src = readFileSync(abs, "utf8");
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// In monorepos, middleware may be at the workspace root
|
|
18
|
+
if (!file) {
|
|
19
|
+
const wsRoot = findWorkspaceRoot(rootDir);
|
|
20
|
+
if (wsRoot) {
|
|
21
|
+
for (const candidate of candidates) {
|
|
22
|
+
const abs = path.join(wsRoot, candidate);
|
|
23
|
+
if (existsSync(abs)) {
|
|
24
|
+
file = candidate;
|
|
25
|
+
src = readFileSync(abs, "utf8");
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!file) {
|
|
32
|
+
return { authLikely: false, rateLimitLikely: false, matcherPatterns: [] };
|
|
33
|
+
}
|
|
34
|
+
// Best-effort heuristics (keep conservative)
|
|
35
|
+
const authLikely = /getToken\s*\(|auth\s*\(|clerkMiddleware\s*\(|authMiddleware\s*\(|withAuth\s*\(|getServerSession\s*\(|\.auth\.getUser\s*\(|createMiddlewareClient\s*\(|authkitMiddleware\s*\(|kindeMiddleware\s*\(|withMiddlewareAuthRequired\s*\(|validateRequest\s*\(|getIronSession\s*\(/.test(src);
|
|
36
|
+
const rateLimitLikely = /ratelimit|rateLimit|upstash/i.test(src);
|
|
37
|
+
// Extract matcher config if present
|
|
38
|
+
const matcherPatterns = [];
|
|
39
|
+
const matcherMatch = src.match(/matcher\s*:\s*(\[[\s\S]*?\])/);
|
|
40
|
+
if (matcherMatch) {
|
|
41
|
+
const literals = matcherMatch[1].matchAll(/"([^"]+)"|'([^']+)'/g);
|
|
42
|
+
for (const m of literals) {
|
|
43
|
+
matcherPatterns.push(m[1] ?? m[2]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { file, authLikely, rateLimitLikely, matcherPatterns };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/next/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,4CAA4C;IAC5C,MAAM,UAAU,GAAG,CAAC,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;IAChG,IAAI,IAAwB,CAAC;IAC7B,IAAI,GAAG,GAAG,EAAE,CAAC;IAEb,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,SAAS,CAAC;YACjB,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAChC,MAAM;QACR,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;gBACzC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACpB,IAAI,GAAG,SAAS,CAAC;oBACjB,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;oBAChC,MAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;IAC5E,CAAC;IAED,6CAA6C;IAC7C,MAAM,UAAU,GAAG,4QAA4Q,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1S,MAAM,eAAe,GAAG,8BAA8B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEjE,oCAAoC;IACpC,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC/D,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;QAClE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.test.d.ts","sourceRoot":"","sources":["../../src/next/middleware.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { analyzeMiddleware } from "./middleware.js";
|
|
6
|
+
describe("analyzeMiddleware", () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
tmpDir = mkdtempSync(path.join(os.tmpdir(), "prodcheck-mw-"));
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
it("returns defaults when no middleware file exists", () => {
|
|
15
|
+
const result = analyzeMiddleware(tmpDir);
|
|
16
|
+
expect(result.file).toBeUndefined();
|
|
17
|
+
expect(result.authLikely).toBe(false);
|
|
18
|
+
expect(result.rateLimitLikely).toBe(false);
|
|
19
|
+
expect(result.matcherPatterns).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
it("finds middleware.ts at root", () => {
|
|
22
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), "export function middleware() {}");
|
|
23
|
+
const result = analyzeMiddleware(tmpDir);
|
|
24
|
+
expect(result.file).toBe("middleware.ts");
|
|
25
|
+
});
|
|
26
|
+
it("finds middleware.js at root", () => {
|
|
27
|
+
writeFileSync(path.join(tmpDir, "middleware.js"), "export function middleware() {}");
|
|
28
|
+
const result = analyzeMiddleware(tmpDir);
|
|
29
|
+
expect(result.file).toBe("middleware.js");
|
|
30
|
+
});
|
|
31
|
+
it("finds src/middleware.ts", () => {
|
|
32
|
+
mkdirSync(path.join(tmpDir, "src"));
|
|
33
|
+
writeFileSync(path.join(tmpDir, "src", "middleware.ts"), "export function middleware() {}");
|
|
34
|
+
const result = analyzeMiddleware(tmpDir);
|
|
35
|
+
expect(result.file).toBe("src/middleware.ts");
|
|
36
|
+
});
|
|
37
|
+
it("prefers root middleware.ts over src/middleware.ts", () => {
|
|
38
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), "// root");
|
|
39
|
+
mkdirSync(path.join(tmpDir, "src"));
|
|
40
|
+
writeFileSync(path.join(tmpDir, "src", "middleware.ts"), "// src");
|
|
41
|
+
const result = analyzeMiddleware(tmpDir);
|
|
42
|
+
expect(result.file).toBe("middleware.ts");
|
|
43
|
+
});
|
|
44
|
+
it("detects Clerk auth", () => {
|
|
45
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
46
|
+
import { clerkMiddleware } from "@clerk/nextjs/server";
|
|
47
|
+
export default clerkMiddleware();
|
|
48
|
+
`);
|
|
49
|
+
const result = analyzeMiddleware(tmpDir);
|
|
50
|
+
expect(result.authLikely).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it("detects NextAuth getToken", () => {
|
|
53
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
54
|
+
import { getToken } from "next-auth/jwt";
|
|
55
|
+
const token = await getToken({ req });
|
|
56
|
+
`);
|
|
57
|
+
const result = analyzeMiddleware(tmpDir);
|
|
58
|
+
expect(result.authLikely).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it("detects NextAuth auth()", () => {
|
|
61
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
62
|
+
import { auth } from "./auth";
|
|
63
|
+
export default auth((req) => {});
|
|
64
|
+
`);
|
|
65
|
+
const result = analyzeMiddleware(tmpDir);
|
|
66
|
+
expect(result.authLikely).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("detects Supabase createMiddlewareClient", () => {
|
|
69
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
70
|
+
const supabase = createMiddlewareClient({ req, res });
|
|
71
|
+
`);
|
|
72
|
+
const result = analyzeMiddleware(tmpDir);
|
|
73
|
+
expect(result.authLikely).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it("detects WorkOS authkitMiddleware", () => {
|
|
76
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
77
|
+
import { authkitMiddleware } from "@workos-inc/authkit-nextjs";
|
|
78
|
+
export default authkitMiddleware();
|
|
79
|
+
`);
|
|
80
|
+
const result = analyzeMiddleware(tmpDir);
|
|
81
|
+
expect(result.authLikely).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it("detects Kinde middleware", () => {
|
|
84
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
85
|
+
import { kindeMiddleware } from "@kinde-oss/kinde-auth-nextjs";
|
|
86
|
+
export default kindeMiddleware();
|
|
87
|
+
`);
|
|
88
|
+
const result = analyzeMiddleware(tmpDir);
|
|
89
|
+
expect(result.authLikely).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it("detects Auth0 withMiddlewareAuthRequired", () => {
|
|
92
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
93
|
+
import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0";
|
|
94
|
+
export default withMiddlewareAuthRequired();
|
|
95
|
+
`);
|
|
96
|
+
const result = analyzeMiddleware(tmpDir);
|
|
97
|
+
expect(result.authLikely).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
it("detects iron-session getIronSession", () => {
|
|
100
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
101
|
+
const session = await getIronSession(req, res, config);
|
|
102
|
+
`);
|
|
103
|
+
const result = analyzeMiddleware(tmpDir);
|
|
104
|
+
expect(result.authLikely).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
it("does not flag auth for generic middleware", () => {
|
|
107
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
108
|
+
export function middleware(req) {
|
|
109
|
+
return NextResponse.next();
|
|
110
|
+
}
|
|
111
|
+
`);
|
|
112
|
+
const result = analyzeMiddleware(tmpDir);
|
|
113
|
+
expect(result.authLikely).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
it("detects rate limiting with upstash", () => {
|
|
116
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
117
|
+
import { Ratelimit } from "@upstash/ratelimit";
|
|
118
|
+
const ratelimit = new Ratelimit({ redis });
|
|
119
|
+
`);
|
|
120
|
+
const result = analyzeMiddleware(tmpDir);
|
|
121
|
+
expect(result.rateLimitLikely).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
it("detects rateLimit keyword (camelCase)", () => {
|
|
124
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
125
|
+
const { success } = await rateLimit(req);
|
|
126
|
+
`);
|
|
127
|
+
const result = analyzeMiddleware(tmpDir);
|
|
128
|
+
expect(result.rateLimitLikely).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
it("does not flag rate limit for generic middleware", () => {
|
|
131
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
132
|
+
export function middleware(req) { return NextResponse.next(); }
|
|
133
|
+
`);
|
|
134
|
+
const result = analyzeMiddleware(tmpDir);
|
|
135
|
+
expect(result.rateLimitLikely).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
it("extracts matcher patterns", () => {
|
|
138
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
139
|
+
export const config = {
|
|
140
|
+
matcher: ["/api/:path*", "/dashboard/:path*"]
|
|
141
|
+
};
|
|
142
|
+
`);
|
|
143
|
+
const result = analyzeMiddleware(tmpDir);
|
|
144
|
+
expect(result.matcherPatterns).toEqual(["/api/:path*", "/dashboard/:path*"]);
|
|
145
|
+
});
|
|
146
|
+
it("extracts single-quoted matcher patterns", () => {
|
|
147
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
148
|
+
export const config = {
|
|
149
|
+
matcher: ['/api/:path*']
|
|
150
|
+
};
|
|
151
|
+
`);
|
|
152
|
+
const result = analyzeMiddleware(tmpDir);
|
|
153
|
+
expect(result.matcherPatterns).toEqual(["/api/:path*"]);
|
|
154
|
+
});
|
|
155
|
+
it("returns empty matcher when no config", () => {
|
|
156
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
157
|
+
export function middleware(req) { return NextResponse.next(); }
|
|
158
|
+
`);
|
|
159
|
+
const result = analyzeMiddleware(tmpDir);
|
|
160
|
+
expect(result.matcherPatterns).toEqual([]);
|
|
161
|
+
});
|
|
162
|
+
it("detects auth and rate limit together", () => {
|
|
163
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
164
|
+
import { clerkMiddleware } from "@clerk/nextjs/server";
|
|
165
|
+
import { Ratelimit } from "@upstash/ratelimit";
|
|
166
|
+
export default clerkMiddleware();
|
|
167
|
+
`);
|
|
168
|
+
const result = analyzeMiddleware(tmpDir);
|
|
169
|
+
expect(result.authLikely).toBe(true);
|
|
170
|
+
expect(result.rateLimitLikely).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
it("finds middleware at monorepo workspace root (pnpm-workspace.yaml)", () => {
|
|
173
|
+
// Simulate monorepo: tmpDir/apps/web with middleware at tmpDir
|
|
174
|
+
const webDir = path.join(tmpDir, "apps", "web");
|
|
175
|
+
mkdirSync(webDir, { recursive: true });
|
|
176
|
+
writeFileSync(path.join(tmpDir, "pnpm-workspace.yaml"), "packages:\n - apps/*");
|
|
177
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({}));
|
|
178
|
+
writeFileSync(path.join(webDir, "package.json"), JSON.stringify({}));
|
|
179
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), `
|
|
180
|
+
import { auth } from "./auth";
|
|
181
|
+
export default auth((req) => {});
|
|
182
|
+
`);
|
|
183
|
+
const result = analyzeMiddleware(webDir);
|
|
184
|
+
expect(result.file).toBe("middleware.ts");
|
|
185
|
+
expect(result.authLikely).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
it("prefers local middleware over workspace root middleware", () => {
|
|
188
|
+
const webDir = path.join(tmpDir, "apps", "web");
|
|
189
|
+
mkdirSync(webDir, { recursive: true });
|
|
190
|
+
writeFileSync(path.join(tmpDir, "pnpm-workspace.yaml"), "packages:\n - apps/*");
|
|
191
|
+
writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({}));
|
|
192
|
+
writeFileSync(path.join(webDir, "package.json"), JSON.stringify({}));
|
|
193
|
+
writeFileSync(path.join(tmpDir, "middleware.ts"), "// root middleware");
|
|
194
|
+
writeFileSync(path.join(webDir, "middleware.ts"), `
|
|
195
|
+
import { clerkMiddleware } from "@clerk/nextjs/server";
|
|
196
|
+
export default clerkMiddleware();
|
|
197
|
+
`);
|
|
198
|
+
const result = analyzeMiddleware(webDir);
|
|
199
|
+
expect(result.file).toBe("middleware.ts");
|
|
200
|
+
expect(result.authLikely).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
//# sourceMappingURL=middleware.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.test.js","sourceRoot":"","sources":["../../src/next/middleware.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NextRoute, MutationSignals, PublicIntent, MalformedPublicIntent } from "./types.js";
|
|
2
|
+
export declare function findRouteHandlers(rootDir: string, excludeGlobs: string[], appDir?: string): Promise<NextRoute[]>;
|
|
3
|
+
export declare function classifyMutationRoutes(all: NextRoute[]): NextRoute[];
|
|
4
|
+
export declare function detectMutationSignals(src: string): MutationSignals;
|
|
5
|
+
/**
|
|
6
|
+
* Parse `// prodcheck:public-intent reason="..."` directive from route source.
|
|
7
|
+
* Returns valid PublicIntent, malformed indicator, or null if not present.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parsePublicIntent(src: string): PublicIntent | MalformedPublicIntent | null;
|
|
10
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/next/routes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAkDlG,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EAAE,EACtB,MAAM,GAAE,MAAc,GACrB,OAAO,CAAC,SAAS,EAAE,CAAC,CAwCtB;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAOpE;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CA+ClE;AAYD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,GACV,YAAY,GAAG,qBAAqB,GAAG,IAAI,CAY7C"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
/**
|
|
5
|
+
* Prisma write methods that indicate mutation.
|
|
6
|
+
*/
|
|
7
|
+
const PRISMA_WRITE_METHODS = [
|
|
8
|
+
"create", "createMany", "createManyAndReturn",
|
|
9
|
+
"update", "updateMany",
|
|
10
|
+
"upsert",
|
|
11
|
+
"delete", "deleteMany",
|
|
12
|
+
"insert", "insertMany", // Drizzle, Knex, MongoDB
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Stripe write patterns (method chains that indicate mutation).
|
|
16
|
+
*/
|
|
17
|
+
const STRIPE_WRITE_PATTERNS = [
|
|
18
|
+
/stripe\.\w+\.create\s*\(/,
|
|
19
|
+
/stripe\.\w+\.update\s*\(/,
|
|
20
|
+
/stripe\.\w+\.del\s*\(/,
|
|
21
|
+
/stripe\.checkout\.sessions\.create\s*\(/,
|
|
22
|
+
/stripe\.subscriptions\./,
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Known non-DB objects whose .update()/.delete()/.create() are false positives.
|
|
26
|
+
* Lowercase for case-insensitive matching.
|
|
27
|
+
*/
|
|
28
|
+
const NON_DB_CALLERS = new Set([
|
|
29
|
+
// crypto / hashing
|
|
30
|
+
"crypto", "hmac", "hash", "cipher", "decipher", "sign", "verify",
|
|
31
|
+
"calculatedsignature", "signature", "digest",
|
|
32
|
+
// state / UI
|
|
33
|
+
"state", "setstate", "set", "ref", "context",
|
|
34
|
+
// collections / cache / web APIs
|
|
35
|
+
"cache", "map", "store", "headers", "params", "searchparams",
|
|
36
|
+
"formdata", "cookies", "cookie", "cookiestore", "localstorage", "sessionstorage",
|
|
37
|
+
// streams / events
|
|
38
|
+
"socket", "stream", "emitter", "readable", "writable",
|
|
39
|
+
// DOM
|
|
40
|
+
"document", "element", "node",
|
|
41
|
+
// React / Next
|
|
42
|
+
"router", "response", "nextresponse", "summary",
|
|
43
|
+
]);
|
|
44
|
+
/**
|
|
45
|
+
* Admin-like path segments that suggest privileged operations.
|
|
46
|
+
*/
|
|
47
|
+
const ADMIN_PATH_SEGMENTS = /\/(admin|billing|invite|role|plan|sync|reindex|delete|remove)\//i;
|
|
48
|
+
export async function findRouteHandlers(rootDir, excludeGlobs, appDir = "app") {
|
|
49
|
+
const files = fg.globSync(`${appDir}/**/route.{ts,js,tsx,jsx}`, {
|
|
50
|
+
cwd: rootDir,
|
|
51
|
+
ignore: ["**/node_modules/**", ...excludeGlobs],
|
|
52
|
+
});
|
|
53
|
+
const routes = [];
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const abs = path.join(rootDir, file);
|
|
56
|
+
let src;
|
|
57
|
+
try {
|
|
58
|
+
src = readFileSync(abs, "utf8");
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
continue; // Skip unreadable files
|
|
62
|
+
}
|
|
63
|
+
const signals = detectMutationSignals(src);
|
|
64
|
+
const method = detectExportedMethods(src);
|
|
65
|
+
const pathname = fileToPathname(file, appDir);
|
|
66
|
+
const isApi = pathname.startsWith("/api/") || pathname === "/api";
|
|
67
|
+
const intent = parsePublicIntent(src);
|
|
68
|
+
const publicIntent = intent && "reason" in intent ? intent : undefined;
|
|
69
|
+
const malformedPublicIntent = intent && !("reason" in intent) ? intent : undefined;
|
|
70
|
+
routes.push({
|
|
71
|
+
kind: "route-handler",
|
|
72
|
+
file,
|
|
73
|
+
method,
|
|
74
|
+
pathname,
|
|
75
|
+
isApi,
|
|
76
|
+
isPublic: true, // conservative default; can be overridden by config
|
|
77
|
+
signals,
|
|
78
|
+
...(publicIntent && { publicIntent }),
|
|
79
|
+
...(malformedPublicIntent && { malformedPublicIntent }),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return routes;
|
|
83
|
+
}
|
|
84
|
+
export function classifyMutationRoutes(all) {
|
|
85
|
+
return all.filter((r) => r.signals.hasMutationEvidence ||
|
|
86
|
+
r.signals.hasDbWriteEvidence ||
|
|
87
|
+
r.signals.hasStripeWriteEvidence);
|
|
88
|
+
}
|
|
89
|
+
export function detectMutationSignals(src) {
|
|
90
|
+
const details = [];
|
|
91
|
+
// Prisma / ORM writes
|
|
92
|
+
let hasDbWrite = false;
|
|
93
|
+
for (const method of PRISMA_WRITE_METHODS) {
|
|
94
|
+
const pattern = new RegExp(`(\\w+)\\.${method}\\s*\\(`, "g");
|
|
95
|
+
const matches = [...src.matchAll(pattern)];
|
|
96
|
+
// Filter out known non-DB callers (crypto.update, cache.delete, etc.)
|
|
97
|
+
const dbMatches = matches.filter((m) => !NON_DB_CALLERS.has(m[1].toLowerCase()));
|
|
98
|
+
if (dbMatches.length > 0) {
|
|
99
|
+
hasDbWrite = true;
|
|
100
|
+
details.push(`prisma.${method}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Stripe writes
|
|
104
|
+
let hasStripeWrite = false;
|
|
105
|
+
for (const pattern of STRIPE_WRITE_PATTERNS) {
|
|
106
|
+
if (pattern.test(src)) {
|
|
107
|
+
hasStripeWrite = true;
|
|
108
|
+
details.push("stripe write operation");
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Raw SQL writes
|
|
113
|
+
const rawSqlWrite = /\$executeRaw|query\s*\(\s*["'`](?:INSERT|UPDATE|DELETE)/i.test(src);
|
|
114
|
+
if (rawSqlWrite) {
|
|
115
|
+
hasDbWrite = true;
|
|
116
|
+
details.push("raw SQL write");
|
|
117
|
+
}
|
|
118
|
+
// General mutation signals: request body reading, admin path patterns
|
|
119
|
+
const readBody = /request\.json\s*\(|request\.formData\s*\(|req\.body/.test(src);
|
|
120
|
+
if (readBody) {
|
|
121
|
+
details.push("reads request body");
|
|
122
|
+
}
|
|
123
|
+
const hasMutation = hasDbWrite || hasStripeWrite || readBody;
|
|
124
|
+
return {
|
|
125
|
+
hasMutationEvidence: hasMutation,
|
|
126
|
+
hasDbWriteEvidence: hasDbWrite,
|
|
127
|
+
hasStripeWriteEvidence: hasStripeWrite,
|
|
128
|
+
mutationDetails: details,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function detectExportedMethods(src) {
|
|
132
|
+
const methods = [];
|
|
133
|
+
if (/export\s+(?:async\s+)?function\s+GET/m.test(src))
|
|
134
|
+
methods.push("GET");
|
|
135
|
+
if (/export\s+(?:async\s+)?function\s+POST/m.test(src))
|
|
136
|
+
methods.push("POST");
|
|
137
|
+
if (/export\s+(?:async\s+)?function\s+PUT/m.test(src))
|
|
138
|
+
methods.push("PUT");
|
|
139
|
+
if (/export\s+(?:async\s+)?function\s+PATCH/m.test(src))
|
|
140
|
+
methods.push("PATCH");
|
|
141
|
+
if (/export\s+(?:async\s+)?function\s+DELETE/m.test(src))
|
|
142
|
+
methods.push("DELETE");
|
|
143
|
+
return methods.length > 0 ? methods.join(",") : undefined;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Parse `// prodcheck:public-intent reason="..."` directive from route source.
|
|
147
|
+
* Returns valid PublicIntent, malformed indicator, or null if not present.
|
|
148
|
+
*/
|
|
149
|
+
export function parsePublicIntent(src) {
|
|
150
|
+
const lines = src.split("\n");
|
|
151
|
+
for (let i = 0; i < lines.length; i++) {
|
|
152
|
+
const match = lines[i].match(/\/\/\s*prodcheck:public-intent\b(.*)/);
|
|
153
|
+
if (!match)
|
|
154
|
+
continue;
|
|
155
|
+
const reasonMatch = match[1].match(/reason\s*=\s*["']([^"']+)["']/);
|
|
156
|
+
if (reasonMatch && reasonMatch[1].trim()) {
|
|
157
|
+
return { reason: reasonMatch[1].trim(), line: i + 1 };
|
|
158
|
+
}
|
|
159
|
+
return { line: i + 1, raw: lines[i].trim() };
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function fileToPathname(file, appDir = "app") {
|
|
164
|
+
// app/api/users/[id]/route.ts → /api/users/[id]
|
|
165
|
+
// src/app/api/users/[id]/route.ts → /api/users/[id]
|
|
166
|
+
const prefix = appDir.endsWith("/") ? appDir : appDir + "/";
|
|
167
|
+
return "/" + file
|
|
168
|
+
.replace(new RegExp(`^${prefix.replace(/[/]/g, "\\/")}`), "")
|
|
169
|
+
.replace(/\/route\.\w+$/, "")
|
|
170
|
+
.replace(/\\/g, "/");
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=routes.js.map
|