@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.
Files changed (239) hide show
  1. package/README.md +252 -0
  2. package/bin/prodcheck.mjs +2 -0
  3. package/dist/cli/commands/baseline.d.ts +7 -0
  4. package/dist/cli/commands/baseline.d.ts.map +1 -0
  5. package/dist/cli/commands/baseline.js +22 -0
  6. package/dist/cli/commands/baseline.js.map +1 -0
  7. package/dist/cli/commands/ci.d.ts +14 -0
  8. package/dist/cli/commands/ci.d.ts.map +1 -0
  9. package/dist/cli/commands/ci.js +104 -0
  10. package/dist/cli/commands/ci.js.map +1 -0
  11. package/dist/cli/commands/explain.d.ts +2 -0
  12. package/dist/cli/commands/explain.d.ts.map +1 -0
  13. package/dist/cli/commands/explain.js +20 -0
  14. package/dist/cli/commands/explain.js.map +1 -0
  15. package/dist/cli/commands/init.d.ts +7 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -0
  17. package/dist/cli/commands/init.js +127 -0
  18. package/dist/cli/commands/init.js.map +1 -0
  19. package/dist/cli/commands/rules.d.ts +2 -0
  20. package/dist/cli/commands/rules.d.ts.map +1 -0
  21. package/dist/cli/commands/rules.js +13 -0
  22. package/dist/cli/commands/rules.js.map +1 -0
  23. package/dist/cli/commands/scan.d.ts +10 -0
  24. package/dist/cli/commands/scan.d.ts.map +1 -0
  25. package/dist/cli/commands/scan.js +65 -0
  26. package/dist/cli/commands/scan.js.map +1 -0
  27. package/dist/cli/commands/waive.d.ts +8 -0
  28. package/dist/cli/commands/waive.d.ts.map +1 -0
  29. package/dist/cli/commands/waive.js +34 -0
  30. package/dist/cli/commands/waive.js.map +1 -0
  31. package/dist/cli/index.d.ts +2 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +64 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/engine/baseline.d.ts +11 -0
  36. package/dist/engine/baseline.d.ts.map +1 -0
  37. package/dist/engine/baseline.js +39 -0
  38. package/dist/engine/baseline.js.map +1 -0
  39. package/dist/engine/baseline.test.d.ts +2 -0
  40. package/dist/engine/baseline.test.d.ts.map +1 -0
  41. package/dist/engine/baseline.test.js +135 -0
  42. package/dist/engine/baseline.test.js.map +1 -0
  43. package/dist/engine/config.d.ts +8 -0
  44. package/dist/engine/config.d.ts.map +1 -0
  45. package/dist/engine/config.js +134 -0
  46. package/dist/engine/config.js.map +1 -0
  47. package/dist/engine/config.test.d.ts +2 -0
  48. package/dist/engine/config.test.d.ts.map +1 -0
  49. package/dist/engine/config.test.js +107 -0
  50. package/dist/engine/config.test.js.map +1 -0
  51. package/dist/engine/extensions/load.d.ts +11 -0
  52. package/dist/engine/extensions/load.d.ts.map +1 -0
  53. package/dist/engine/extensions/load.js +26 -0
  54. package/dist/engine/extensions/load.js.map +1 -0
  55. package/dist/engine/extensions/registry.d.ts +5 -0
  56. package/dist/engine/extensions/registry.d.ts.map +1 -0
  57. package/dist/engine/extensions/registry.js +11 -0
  58. package/dist/engine/extensions/registry.js.map +1 -0
  59. package/dist/engine/extensions/types.d.ts +51 -0
  60. package/dist/engine/extensions/types.d.ts.map +1 -0
  61. package/dist/engine/extensions/types.js +2 -0
  62. package/dist/engine/extensions/types.js.map +1 -0
  63. package/dist/engine/license.d.ts +40 -0
  64. package/dist/engine/license.d.ts.map +1 -0
  65. package/dist/engine/license.js +104 -0
  66. package/dist/engine/license.js.map +1 -0
  67. package/dist/engine/report.d.ts +5 -0
  68. package/dist/engine/report.d.ts.map +1 -0
  69. package/dist/engine/report.js +115 -0
  70. package/dist/engine/report.js.map +1 -0
  71. package/dist/engine/run.d.ts +11 -0
  72. package/dist/engine/run.d.ts.map +1 -0
  73. package/dist/engine/run.js +105 -0
  74. package/dist/engine/run.js.map +1 -0
  75. package/dist/engine/sarif.d.ts +3 -0
  76. package/dist/engine/sarif.d.ts.map +1 -0
  77. package/dist/engine/sarif.js +58 -0
  78. package/dist/engine/sarif.js.map +1 -0
  79. package/dist/engine/sarif.test.d.ts +2 -0
  80. package/dist/engine/sarif.test.d.ts.map +1 -0
  81. package/dist/engine/sarif.test.js +152 -0
  82. package/dist/engine/sarif.test.js.map +1 -0
  83. package/dist/engine/score.d.ts +13 -0
  84. package/dist/engine/score.d.ts.map +1 -0
  85. package/dist/engine/score.js +116 -0
  86. package/dist/engine/score.js.map +1 -0
  87. package/dist/engine/score.test.d.ts +2 -0
  88. package/dist/engine/score.test.d.ts.map +1 -0
  89. package/dist/engine/score.test.js +227 -0
  90. package/dist/engine/score.test.js.map +1 -0
  91. package/dist/engine/types.d.ts +123 -0
  92. package/dist/engine/types.d.ts.map +1 -0
  93. package/dist/engine/types.js +2 -0
  94. package/dist/engine/types.js.map +1 -0
  95. package/dist/engine/version.d.ts +5 -0
  96. package/dist/engine/version.d.ts.map +1 -0
  97. package/dist/engine/version.js +15 -0
  98. package/dist/engine/version.js.map +1 -0
  99. package/dist/engine/waivers.d.ts +9 -0
  100. package/dist/engine/waivers.d.ts.map +1 -0
  101. package/dist/engine/waivers.js +55 -0
  102. package/dist/engine/waivers.js.map +1 -0
  103. package/dist/engine/waivers.test.d.ts +2 -0
  104. package/dist/engine/waivers.test.d.ts.map +1 -0
  105. package/dist/engine/waivers.test.js +147 -0
  106. package/dist/engine/waivers.test.js.map +1 -0
  107. package/dist/index.d.ts +14 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +12 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/next/deps.d.ts +4 -0
  112. package/dist/next/deps.d.ts.map +1 -0
  113. package/dist/next/deps.js +118 -0
  114. package/dist/next/deps.js.map +1 -0
  115. package/dist/next/deps.test.d.ts +2 -0
  116. package/dist/next/deps.test.d.ts.map +1 -0
  117. package/dist/next/deps.test.js +249 -0
  118. package/dist/next/deps.test.js.map +1 -0
  119. package/dist/next/detect.d.ts +10 -0
  120. package/dist/next/detect.d.ts.map +1 -0
  121. package/dist/next/detect.js +57 -0
  122. package/dist/next/detect.js.map +1 -0
  123. package/dist/next/detect.test.d.ts +2 -0
  124. package/dist/next/detect.test.d.ts.map +1 -0
  125. package/dist/next/detect.test.js +74 -0
  126. package/dist/next/detect.test.js.map +1 -0
  127. package/dist/next/index.d.ts +5 -0
  128. package/dist/next/index.d.ts.map +1 -0
  129. package/dist/next/index.js +59 -0
  130. package/dist/next/index.js.map +1 -0
  131. package/dist/next/middleware.d.ts +3 -0
  132. package/dist/next/middleware.d.ts.map +1 -0
  133. package/dist/next/middleware.js +48 -0
  134. package/dist/next/middleware.js.map +1 -0
  135. package/dist/next/middleware.test.d.ts +2 -0
  136. package/dist/next/middleware.test.d.ts.map +1 -0
  137. package/dist/next/middleware.test.js +203 -0
  138. package/dist/next/middleware.test.js.map +1 -0
  139. package/dist/next/routes.d.ts +10 -0
  140. package/dist/next/routes.d.ts.map +1 -0
  141. package/dist/next/routes.js +172 -0
  142. package/dist/next/routes.js.map +1 -0
  143. package/dist/next/routes.test.d.ts +2 -0
  144. package/dist/next/routes.test.d.ts.map +1 -0
  145. package/dist/next/routes.test.js +175 -0
  146. package/dist/next/routes.test.js.map +1 -0
  147. package/dist/next/server-actions.d.ts +4 -0
  148. package/dist/next/server-actions.d.ts.map +1 -0
  149. package/dist/next/server-actions.js +107 -0
  150. package/dist/next/server-actions.js.map +1 -0
  151. package/dist/next/server-actions.test.d.ts +2 -0
  152. package/dist/next/server-actions.test.d.ts.map +1 -0
  153. package/dist/next/server-actions.test.js +138 -0
  154. package/dist/next/server-actions.test.js.map +1 -0
  155. package/dist/next/trpc.d.ts +3 -0
  156. package/dist/next/trpc.d.ts.map +1 -0
  157. package/dist/next/trpc.js +312 -0
  158. package/dist/next/trpc.js.map +1 -0
  159. package/dist/next/types.d.ts +144 -0
  160. package/dist/next/types.d.ts.map +1 -0
  161. package/dist/next/types.js +2 -0
  162. package/dist/next/types.js.map +1 -0
  163. package/dist/next/wrappers.d.ts +10 -0
  164. package/dist/next/wrappers.d.ts.map +1 -0
  165. package/dist/next/wrappers.js +536 -0
  166. package/dist/next/wrappers.js.map +1 -0
  167. package/dist/next/wrappers.test.d.ts +2 -0
  168. package/dist/next/wrappers.test.d.ts.map +1 -0
  169. package/dist/next/wrappers.test.js +361 -0
  170. package/dist/next/wrappers.test.js.map +1 -0
  171. package/dist/rules/auth-boundary-missing.d.ts +5 -0
  172. package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
  173. package/dist/rules/auth-boundary-missing.js +463 -0
  174. package/dist/rules/auth-boundary-missing.js.map +1 -0
  175. package/dist/rules/auth-boundary-missing.test.d.ts +2 -0
  176. package/dist/rules/auth-boundary-missing.test.d.ts.map +1 -0
  177. package/dist/rules/auth-boundary-missing.test.js +492 -0
  178. package/dist/rules/auth-boundary-missing.test.js.map +1 -0
  179. package/dist/rules/index.d.ts +12 -0
  180. package/dist/rules/index.d.ts.map +1 -0
  181. package/dist/rules/index.js +95 -0
  182. package/dist/rules/index.js.map +1 -0
  183. package/dist/rules/input-validation-missing.d.ts +5 -0
  184. package/dist/rules/input-validation-missing.d.ts.map +1 -0
  185. package/dist/rules/input-validation-missing.js +272 -0
  186. package/dist/rules/input-validation-missing.js.map +1 -0
  187. package/dist/rules/input-validation-missing.test.d.ts +2 -0
  188. package/dist/rules/input-validation-missing.test.d.ts.map +1 -0
  189. package/dist/rules/input-validation-missing.test.js +449 -0
  190. package/dist/rules/input-validation-missing.test.js.map +1 -0
  191. package/dist/rules/rate-limit-missing.d.ts +5 -0
  192. package/dist/rules/rate-limit-missing.d.ts.map +1 -0
  193. package/dist/rules/rate-limit-missing.js +316 -0
  194. package/dist/rules/rate-limit-missing.js.map +1 -0
  195. package/dist/rules/rate-limit-missing.test.d.ts +2 -0
  196. package/dist/rules/rate-limit-missing.test.d.ts.map +1 -0
  197. package/dist/rules/rate-limit-missing.test.js +381 -0
  198. package/dist/rules/rate-limit-missing.test.js.map +1 -0
  199. package/dist/rules/tenancy-scope-missing.d.ts +5 -0
  200. package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
  201. package/dist/rules/tenancy-scope-missing.js +149 -0
  202. package/dist/rules/tenancy-scope-missing.js.map +1 -0
  203. package/dist/rules/wrapper-unrecognized.d.ts +5 -0
  204. package/dist/rules/wrapper-unrecognized.d.ts.map +1 -0
  205. package/dist/rules/wrapper-unrecognized.js +81 -0
  206. package/dist/rules/wrapper-unrecognized.js.map +1 -0
  207. package/dist/util/hof.d.ts +22 -0
  208. package/dist/util/hof.d.ts.map +1 -0
  209. package/dist/util/hof.js +99 -0
  210. package/dist/util/hof.js.map +1 -0
  211. package/dist/util/hof.test.d.ts +2 -0
  212. package/dist/util/hof.test.d.ts.map +1 -0
  213. package/dist/util/hof.test.js +79 -0
  214. package/dist/util/hof.test.js.map +1 -0
  215. package/dist/util/monorepo.d.ts +6 -0
  216. package/dist/util/monorepo.d.ts.map +1 -0
  217. package/dist/util/monorepo.js +29 -0
  218. package/dist/util/monorepo.js.map +1 -0
  219. package/dist/util/outbound-fetch.d.ts +14 -0
  220. package/dist/util/outbound-fetch.d.ts.map +1 -0
  221. package/dist/util/outbound-fetch.js +59 -0
  222. package/dist/util/outbound-fetch.js.map +1 -0
  223. package/dist/util/outbound-fetch.test.d.ts +2 -0
  224. package/dist/util/outbound-fetch.test.d.ts.map +1 -0
  225. package/dist/util/outbound-fetch.test.js +83 -0
  226. package/dist/util/outbound-fetch.test.js.map +1 -0
  227. package/dist/util/paths.d.ts +6 -0
  228. package/dist/util/paths.d.ts.map +1 -0
  229. package/dist/util/paths.js +18 -0
  230. package/dist/util/paths.js.map +1 -0
  231. package/dist/util/resolve.d.ts +30 -0
  232. package/dist/util/resolve.d.ts.map +1 -0
  233. package/dist/util/resolve.js +306 -0
  234. package/dist/util/resolve.js.map +1 -0
  235. package/dist/util/resolve.test.d.ts +2 -0
  236. package/dist/util/resolve.test.d.ts.map +1 -0
  237. package/dist/util/resolve.test.js +186 -0
  238. package/dist/util/resolve.test.js.map +1 -0
  239. 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,3 @@
1
+ import type { NextMiddlewareIndex } from "./types.js";
2
+ export declare function analyzeMiddleware(rootDir: string): NextMiddlewareIndex;
3
+ //# sourceMappingURL=middleware.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=middleware.test.d.ts.map
@@ -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