@nifrajs/cli 3.5.0 → 4.0.2

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 (404) hide show
  1. package/README.md +42 -3
  2. package/dist/app-files.d.ts +11 -0
  3. package/dist/app-files.d.ts.map +1 -0
  4. package/dist/app-files.js +34 -0
  5. package/dist/app-files.js.map +1 -0
  6. package/dist/assure-hydration.d.ts +7 -1
  7. package/dist/assure-hydration.d.ts.map +1 -1
  8. package/dist/assure-hydration.js +128 -43
  9. package/dist/assure-hydration.js.map +1 -1
  10. package/dist/assure.d.ts.map +1 -1
  11. package/dist/assure.js +16 -2
  12. package/dist/assure.js.map +1 -1
  13. package/dist/capabilities-tool.d.ts +5 -1
  14. package/dist/capabilities-tool.d.ts.map +1 -1
  15. package/dist/capabilities-tool.js +32 -10
  16. package/dist/capabilities-tool.js.map +1 -1
  17. package/dist/cdn-check.d.ts +41 -0
  18. package/dist/cdn-check.d.ts.map +1 -0
  19. package/dist/cdn-check.js +207 -0
  20. package/dist/cdn-check.js.map +1 -0
  21. package/dist/check-diagnostics.d.ts +4 -0
  22. package/dist/check-diagnostics.d.ts.map +1 -1
  23. package/dist/check-diagnostics.js +13 -0
  24. package/dist/check-diagnostics.js.map +1 -1
  25. package/dist/check-scan.d.ts +49 -7
  26. package/dist/check-scan.d.ts.map +1 -1
  27. package/dist/check-scan.js +291 -62
  28. package/dist/check-scan.js.map +1 -1
  29. package/dist/check.d.ts.map +1 -1
  30. package/dist/check.js +99 -63
  31. package/dist/check.js.map +1 -1
  32. package/dist/cli.d.ts +42 -16
  33. package/dist/cli.d.ts.map +1 -1
  34. package/dist/cli.js +262 -82
  35. package/dist/cli.js.map +1 -1
  36. package/dist/command-catalog.d.ts +62 -5
  37. package/dist/command-catalog.d.ts.map +1 -1
  38. package/dist/command-catalog.js +275 -35
  39. package/dist/command-catalog.js.map +1 -1
  40. package/dist/config-target.d.ts +8 -0
  41. package/dist/config-target.d.ts.map +1 -0
  42. package/dist/config-target.js +45 -0
  43. package/dist/config-target.js.map +1 -0
  44. package/dist/contracts.d.ts.map +1 -1
  45. package/dist/contracts.js +52 -13
  46. package/dist/contracts.js.map +1 -1
  47. package/dist/db-audit.d.ts +37 -0
  48. package/dist/db-audit.d.ts.map +1 -0
  49. package/dist/db-audit.js +99 -0
  50. package/dist/db-audit.js.map +1 -0
  51. package/dist/db-child.d.ts +64 -0
  52. package/dist/db-child.d.ts.map +1 -0
  53. package/dist/db-child.js +213 -0
  54. package/dist/db-child.js.map +1 -0
  55. package/dist/db-config.d.ts +60 -0
  56. package/dist/db-config.d.ts.map +1 -0
  57. package/dist/db-config.js +176 -0
  58. package/dist/db-config.js.map +1 -0
  59. package/dist/db-tool.d.ts +84 -0
  60. package/dist/db-tool.d.ts.map +1 -0
  61. package/dist/db-tool.js +537 -0
  62. package/dist/db-tool.js.map +1 -0
  63. package/dist/dev-bun-config.d.ts +20 -13
  64. package/dist/dev-bun-config.d.ts.map +1 -1
  65. package/dist/dev-bun-config.js +98 -41
  66. package/dist/dev-bun-config.js.map +1 -1
  67. package/dist/dev-feed-tool.d.ts +106 -0
  68. package/dist/dev-feed-tool.d.ts.map +1 -0
  69. package/dist/dev-feed-tool.js +713 -0
  70. package/dist/dev-feed-tool.js.map +1 -0
  71. package/dist/dev-server-client.d.ts +67 -0
  72. package/dist/dev-server-client.d.ts.map +1 -0
  73. package/dist/dev-server-client.js +195 -0
  74. package/dist/dev-server-client.js.map +1 -0
  75. package/dist/diff-tool.d.ts +3 -3
  76. package/dist/diff-tool.d.ts.map +1 -1
  77. package/dist/diff-tool.js +6 -5
  78. package/dist/diff-tool.js.map +1 -1
  79. package/dist/docs-search.d.ts.map +1 -1
  80. package/dist/docs-search.js +12 -4
  81. package/dist/docs-search.js.map +1 -1
  82. package/dist/doctor.d.ts +17 -0
  83. package/dist/doctor.d.ts.map +1 -1
  84. package/dist/doctor.js +76 -15
  85. package/dist/doctor.js.map +1 -1
  86. package/dist/env-file.d.ts +15 -0
  87. package/dist/env-file.d.ts.map +1 -1
  88. package/dist/env-file.js +73 -1
  89. package/dist/env-file.js.map +1 -1
  90. package/dist/fix-recipes.d.ts.map +1 -1
  91. package/dist/fix-recipes.js +48 -22
  92. package/dist/fix-recipes.js.map +1 -1
  93. package/dist/frontend-guidance.js +5 -5
  94. package/dist/frontend-guidance.js.map +1 -1
  95. package/dist/i18n-check.d.ts +16 -0
  96. package/dist/i18n-check.d.ts.map +1 -0
  97. package/dist/i18n-check.js +154 -0
  98. package/dist/i18n-check.js.map +1 -0
  99. package/dist/init-agents.d.ts +49 -13
  100. package/dist/init-agents.d.ts.map +1 -1
  101. package/dist/init-agents.js +239 -34
  102. package/dist/init-agents.js.map +1 -1
  103. package/dist/internal/code-unit-order.d.ts +4 -0
  104. package/dist/internal/code-unit-order.d.ts.map +1 -0
  105. package/dist/internal/code-unit-order.js +6 -0
  106. package/dist/internal/code-unit-order.js.map +1 -0
  107. package/dist/internal/script-kind.d.ts +7 -0
  108. package/dist/internal/script-kind.d.ts.map +1 -0
  109. package/dist/internal/script-kind.js +12 -0
  110. package/dist/internal/script-kind.js.map +1 -0
  111. package/dist/internal/source-facts.d.ts +5 -0
  112. package/dist/internal/source-facts.d.ts.map +1 -1
  113. package/dist/internal/source-facts.js +61 -5
  114. package/dist/internal/source-facts.js.map +1 -1
  115. package/dist/internal/typescript-import.d.ts +49 -24
  116. package/dist/internal/typescript-import.d.ts.map +1 -1
  117. package/dist/internal/typescript-import.js +507 -67
  118. package/dist/internal/typescript-import.js.map +1 -1
  119. package/dist/introspect.d.ts +5 -1
  120. package/dist/introspect.d.ts.map +1 -1
  121. package/dist/introspect.js +92 -27
  122. package/dist/introspect.js.map +1 -1
  123. package/dist/learn.js +3 -3
  124. package/dist/learn.js.map +1 -1
  125. package/dist/levels-tool.d.ts +10 -0
  126. package/dist/levels-tool.d.ts.map +1 -1
  127. package/dist/levels-tool.js +33 -3
  128. package/dist/levels-tool.js.map +1 -1
  129. package/dist/load.d.ts +67 -17
  130. package/dist/load.d.ts.map +1 -1
  131. package/dist/load.js +78 -18
  132. package/dist/load.js.map +1 -1
  133. package/dist/manifest-tool.d.ts.map +1 -1
  134. package/dist/manifest-tool.js +3 -2
  135. package/dist/manifest-tool.js.map +1 -1
  136. package/dist/mcp-app-tool.d.ts.map +1 -1
  137. package/dist/mcp-app-tool.js +2 -0
  138. package/dist/mcp-app-tool.js.map +1 -1
  139. package/dist/mcp-context.js +1 -1
  140. package/dist/mcp-context.js.map +1 -1
  141. package/dist/mcp-delegate.d.ts +57 -0
  142. package/dist/mcp-delegate.d.ts.map +1 -0
  143. package/dist/mcp-delegate.js +130 -0
  144. package/dist/mcp-delegate.js.map +1 -0
  145. package/dist/mcp-docs-tools.d.ts.map +1 -1
  146. package/dist/mcp-docs-tools.js +23 -5
  147. package/dist/mcp-docs-tools.js.map +1 -1
  148. package/dist/mcp-exec.d.ts +2 -0
  149. package/dist/mcp-exec.d.ts.map +1 -1
  150. package/dist/mcp-exec.js +132 -108
  151. package/dist/mcp-exec.js.map +1 -1
  152. package/dist/mcp-http.d.ts.map +1 -1
  153. package/dist/mcp-http.js +17 -5
  154. package/dist/mcp-http.js.map +1 -1
  155. package/dist/mcp-io.d.ts +14 -0
  156. package/dist/mcp-io.d.ts.map +1 -1
  157. package/dist/mcp-io.js +45 -0
  158. package/dist/mcp-io.js.map +1 -1
  159. package/dist/mcp-isolate.d.ts +48 -0
  160. package/dist/mcp-isolate.d.ts.map +1 -0
  161. package/dist/mcp-isolate.js +222 -0
  162. package/dist/mcp-isolate.js.map +1 -0
  163. package/dist/mcp-project-child.d.ts +74 -0
  164. package/dist/mcp-project-child.d.ts.map +1 -0
  165. package/dist/mcp-project-child.js +130 -0
  166. package/dist/mcp-project-child.js.map +1 -0
  167. package/dist/mcp-render.js +2 -2
  168. package/dist/mcp-render.js.map +1 -1
  169. package/dist/mcp-root.d.ts +37 -9
  170. package/dist/mcp-root.d.ts.map +1 -1
  171. package/dist/mcp-root.js +110 -23
  172. package/dist/mcp-root.js.map +1 -1
  173. package/dist/mcp-run.d.ts +18 -1
  174. package/dist/mcp-run.d.ts.map +1 -1
  175. package/dist/mcp-run.js +82 -9
  176. package/dist/mcp-run.js.map +1 -1
  177. package/dist/mcp-ws.js +4 -1
  178. package/dist/mcp-ws.js.map +1 -1
  179. package/dist/mcp.d.ts +16 -3
  180. package/dist/mcp.d.ts.map +1 -1
  181. package/dist/mcp.js +81 -53
  182. package/dist/mcp.js.map +1 -1
  183. package/dist/migrate-layout.d.ts +44 -0
  184. package/dist/migrate-layout.d.ts.map +1 -0
  185. package/dist/migrate-layout.js +1155 -0
  186. package/dist/migrate-layout.js.map +1 -0
  187. package/dist/openapi-tool.d.ts +2 -2
  188. package/dist/openapi-tool.d.ts.map +1 -1
  189. package/dist/openapi-tool.js.map +1 -1
  190. package/dist/openapi-types.d.ts +2 -1
  191. package/dist/openapi-types.d.ts.map +1 -1
  192. package/dist/openapi-types.js +240 -12
  193. package/dist/openapi-types.js.map +1 -1
  194. package/dist/pipeline-report.d.ts +3 -2
  195. package/dist/pipeline-report.d.ts.map +1 -1
  196. package/dist/pipeline-report.js +19 -16
  197. package/dist/pipeline-report.js.map +1 -1
  198. package/dist/port.d.ts +5 -4
  199. package/dist/port.d.ts.map +1 -1
  200. package/dist/port.js +46 -29
  201. package/dist/port.js.map +1 -1
  202. package/dist/project-facts.d.ts +5 -0
  203. package/dist/project-facts.d.ts.map +1 -1
  204. package/dist/project-facts.js.map +1 -1
  205. package/dist/recipes/4.0.0.d.ts +9 -0
  206. package/dist/recipes/4.0.0.d.ts.map +1 -0
  207. package/dist/recipes/4.0.0.js +36 -0
  208. package/dist/recipes/4.0.0.js.map +1 -0
  209. package/dist/recipes/index.d.ts.map +1 -1
  210. package/dist/recipes/index.js +2 -0
  211. package/dist/recipes/index.js.map +1 -1
  212. package/dist/release-verification.d.ts +11 -1
  213. package/dist/release-verification.d.ts.map +1 -1
  214. package/dist/release-verification.js +47 -4
  215. package/dist/release-verification.js.map +1 -1
  216. package/dist/review.d.ts.map +1 -1
  217. package/dist/review.js +12 -8
  218. package/dist/review.js.map +1 -1
  219. package/dist/route-types.d.ts +28 -0
  220. package/dist/route-types.d.ts.map +1 -0
  221. package/dist/route-types.js +106 -0
  222. package/dist/route-types.js.map +1 -0
  223. package/dist/rules/codes.d.ts +9 -0
  224. package/dist/rules/codes.d.ts.map +1 -1
  225. package/dist/rules/codes.js +9 -0
  226. package/dist/rules/codes.js.map +1 -1
  227. package/dist/rules/data-guard.d.ts +12 -0
  228. package/dist/rules/data-guard.d.ts.map +1 -0
  229. package/dist/rules/data-guard.js +213 -0
  230. package/dist/rules/data-guard.js.map +1 -0
  231. package/dist/rules/index.d.ts +3 -0
  232. package/dist/rules/index.d.ts.map +1 -1
  233. package/dist/rules/index.js +17 -5
  234. package/dist/rules/index.js.map +1 -1
  235. package/dist/rules/islands.d.ts.map +1 -1
  236. package/dist/rules/islands.js +7 -4
  237. package/dist/rules/islands.js.map +1 -1
  238. package/dist/rules/legacy.d.ts.map +1 -1
  239. package/dist/rules/legacy.js +71 -38
  240. package/dist/rules/legacy.js.map +1 -1
  241. package/dist/rules/nano.d.ts.map +1 -1
  242. package/dist/rules/nano.js +15 -6
  243. package/dist/rules/nano.js.map +1 -1
  244. package/dist/rules/pages.d.ts +15 -0
  245. package/dist/rules/pages.d.ts.map +1 -0
  246. package/dist/rules/pages.js +88 -0
  247. package/dist/rules/pages.js.map +1 -0
  248. package/dist/rules/routes.d.ts +11 -0
  249. package/dist/rules/routes.d.ts.map +1 -1
  250. package/dist/rules/routes.js +180 -2
  251. package/dist/rules/routes.js.map +1 -1
  252. package/dist/rules/secrets.d.ts +10 -0
  253. package/dist/rules/secrets.d.ts.map +1 -0
  254. package/dist/rules/secrets.js +169 -0
  255. package/dist/rules/secrets.js.map +1 -0
  256. package/dist/rules/security.d.ts +7 -0
  257. package/dist/rules/security.d.ts.map +1 -1
  258. package/dist/rules/security.js +171 -65
  259. package/dist/rules/security.js.map +1 -1
  260. package/dist/rules/typescript.d.ts +16 -0
  261. package/dist/rules/typescript.d.ts.map +1 -0
  262. package/dist/rules/typescript.js +59 -0
  263. package/dist/rules/typescript.js.map +1 -0
  264. package/dist/rules/zones.d.ts +17 -0
  265. package/dist/rules/zones.d.ts.map +1 -0
  266. package/dist/rules/zones.js +174 -0
  267. package/dist/rules/zones.js.map +1 -0
  268. package/dist/scaffold.d.ts +25 -12
  269. package/dist/scaffold.d.ts.map +1 -1
  270. package/dist/scaffold.js +132 -87
  271. package/dist/scaffold.js.map +1 -1
  272. package/dist/sdk.d.ts +17 -3
  273. package/dist/sdk.d.ts.map +1 -1
  274. package/dist/sdk.js +941 -153
  275. package/dist/sdk.js.map +1 -1
  276. package/dist/search-terms.d.ts +2 -0
  277. package/dist/search-terms.d.ts.map +1 -1
  278. package/dist/search-terms.js +23 -8
  279. package/dist/search-terms.js.map +1 -1
  280. package/dist/smoke.d.ts +79 -0
  281. package/dist/smoke.d.ts.map +1 -0
  282. package/dist/smoke.js +494 -0
  283. package/dist/smoke.js.map +1 -0
  284. package/dist/sync-manifest.d.ts +10 -2
  285. package/dist/sync-manifest.d.ts.map +1 -1
  286. package/dist/sync-manifest.js +44 -19
  287. package/dist/sync-manifest.js.map +1 -1
  288. package/dist/sync-routes.d.ts +4 -3
  289. package/dist/sync-routes.d.ts.map +1 -1
  290. package/dist/sync-routes.js +4 -3
  291. package/dist/sync-routes.js.map +1 -1
  292. package/dist/test-tool.d.ts.map +1 -1
  293. package/dist/test-tool.js +30 -11
  294. package/dist/test-tool.js.map +1 -1
  295. package/dist/types-search.d.ts.map +1 -1
  296. package/dist/types-search.js +11 -5
  297. package/dist/types-search.js.map +1 -1
  298. package/dist/upgrade.d.ts +36 -6
  299. package/dist/upgrade.d.ts.map +1 -1
  300. package/dist/upgrade.js +167 -29
  301. package/dist/upgrade.js.map +1 -1
  302. package/dist/verification-plan.d.ts.map +1 -1
  303. package/dist/verification-plan.js +4 -0
  304. package/dist/verification-plan.js.map +1 -1
  305. package/dist/verification.d.ts.map +1 -1
  306. package/dist/verification.js +45 -12
  307. package/dist/verification.js.map +1 -1
  308. package/dist/web-app-options.d.ts +19 -0
  309. package/dist/web-app-options.d.ts.map +1 -0
  310. package/dist/web-app-options.js +49 -0
  311. package/dist/web-app-options.js.map +1 -0
  312. package/dist/work-graph.d.ts.map +1 -1
  313. package/dist/work-graph.js +29 -13
  314. package/dist/work-graph.js.map +1 -1
  315. package/docs/examples.json +185 -29
  316. package/docs/llms-full.txt +3807 -580
  317. package/docs/types.json +14229 -10939
  318. package/package.json +19 -12
  319. package/src/app-files.ts +40 -0
  320. package/src/assure-hydration.ts +139 -46
  321. package/src/assure.ts +17 -2
  322. package/src/capabilities-tool.ts +35 -9
  323. package/src/cdn-check.ts +268 -0
  324. package/src/check-diagnostics.ts +17 -0
  325. package/src/check-scan.ts +358 -63
  326. package/src/check.ts +97 -62
  327. package/src/cli.ts +296 -87
  328. package/src/command-catalog.ts +389 -48
  329. package/src/config-target.ts +47 -0
  330. package/src/contracts.ts +54 -12
  331. package/src/db-audit.ts +128 -0
  332. package/src/db-child.ts +285 -0
  333. package/src/db-config.ts +224 -0
  334. package/src/db-tool.ts +690 -0
  335. package/src/dev-bun-config.ts +116 -40
  336. package/src/dev-feed-tool.ts +912 -0
  337. package/src/dev-server-client.ts +267 -0
  338. package/src/diff-tool.ts +6 -5
  339. package/src/docs-search.ts +10 -3
  340. package/src/doctor.ts +98 -15
  341. package/src/env-file.ts +78 -1
  342. package/src/fix-recipes.ts +53 -32
  343. package/src/frontend-guidance.ts +5 -5
  344. package/src/i18n-check.ts +179 -0
  345. package/src/init-agents.ts +301 -38
  346. package/src/internal/code-unit-order.ts +5 -0
  347. package/src/internal/script-kind.ts +11 -0
  348. package/src/internal/source-facts.ts +96 -22
  349. package/src/internal/typescript-import.ts +663 -68
  350. package/src/introspect.ts +90 -27
  351. package/src/learn.ts +3 -3
  352. package/src/levels-tool.ts +47 -2
  353. package/src/load.ts +140 -33
  354. package/src/manifest-tool.ts +3 -2
  355. package/src/mcp-app-tool.ts +2 -0
  356. package/src/mcp-context.ts +1 -1
  357. package/src/mcp-delegate.ts +162 -0
  358. package/src/mcp-docs-tools.ts +23 -5
  359. package/src/mcp-exec.ts +160 -132
  360. package/src/mcp-http.ts +19 -5
  361. package/src/mcp-io.ts +57 -0
  362. package/src/mcp-isolate.ts +287 -0
  363. package/src/mcp-project-child.ts +182 -0
  364. package/src/mcp-render.ts +2 -2
  365. package/src/mcp-root.ts +117 -22
  366. package/src/mcp-run.ts +104 -10
  367. package/src/mcp-ws.ts +4 -1
  368. package/src/mcp.ts +106 -64
  369. package/src/migrate-layout.ts +1338 -0
  370. package/src/openapi-tool.ts +7 -3
  371. package/src/openapi-types.ts +315 -13
  372. package/src/pipeline-report.ts +22 -18
  373. package/src/port.ts +53 -30
  374. package/src/project-facts.ts +5 -0
  375. package/src/recipes/4.0.0.ts +37 -0
  376. package/src/recipes/index.ts +2 -0
  377. package/src/release-verification.ts +74 -5
  378. package/src/review.ts +13 -8
  379. package/src/route-types.ts +133 -0
  380. package/src/rules/codes.ts +9 -0
  381. package/src/rules/data-guard.ts +230 -0
  382. package/src/rules/index.ts +18 -5
  383. package/src/rules/islands.ts +12 -4
  384. package/src/rules/legacy.ts +71 -39
  385. package/src/rules/nano.ts +18 -6
  386. package/src/rules/pages.ts +99 -0
  387. package/src/rules/routes.ts +181 -2
  388. package/src/rules/secrets.ts +174 -0
  389. package/src/rules/security.ts +198 -70
  390. package/src/rules/typescript.ts +77 -0
  391. package/src/rules/zones.ts +195 -0
  392. package/src/scaffold.ts +147 -95
  393. package/src/sdk.ts +1215 -158
  394. package/src/search-terms.ts +24 -7
  395. package/src/smoke.ts +607 -0
  396. package/src/sync-manifest.ts +50 -19
  397. package/src/sync-routes.ts +4 -3
  398. package/src/test-tool.ts +32 -11
  399. package/src/types-search.ts +11 -4
  400. package/src/upgrade.ts +205 -25
  401. package/src/verification-plan.ts +13 -0
  402. package/src/verification.ts +50 -12
  403. package/src/web-app-options.ts +57 -0
  404. package/src/work-graph.ts +27 -13
@@ -1,4 +1,6 @@
1
1
  import { RESERVED_KEY_READOUT, reservedKeyFor } from "@nifrajs/client";
2
+ import { RoutePatternOverlapLimitError, routePatternOverlap } from "@nifrajs/core";
3
+ import { expandOptionalParams, paramConstraint } from "@nifrajs/core/pattern";
2
4
  import { diagnostic } from "../diagnostics.js";
3
5
  import { commentBlockHasMarker } from "./comment-markers.js";
4
6
  /**
@@ -20,6 +22,8 @@ import { commentBlockHasMarker } from "./comment-markers.js";
20
22
  */
21
23
  /** Opt-out pragma for a route deliberately served only to NON-typed-client consumers. */
22
24
  const RESERVED_SEGMENT_PRAGMA = "nifra-expect reserved-segment";
25
+ const ROUTE_OVERLAP_PRAGMA = "nifra-expect route-overlap";
26
+ const PARAM_MODIFIER_PRAGMA = "nifra-expect param-modifier";
23
27
  /** Parse-don't-cast over the project fact: a malformed entry is dropped, never trusted. */
24
28
  function routeFacts(ctx) {
25
29
  const raw = ctx.project.routes;
@@ -37,6 +41,17 @@ function routeFacts(ctx) {
37
41
  }
38
42
  return out;
39
43
  }
44
+ /** One fact per registration site, so `all()` or `method([...])` reports its path once, not per method. */
45
+ function pathSites(routes) {
46
+ const seen = new Set();
47
+ return routes.filter((route) => {
48
+ const site = `${route.file}\n${route.line}\n${route.path}`;
49
+ if (seen.has(site))
50
+ return false;
51
+ seen.add(site);
52
+ return true;
53
+ });
54
+ }
40
55
  /**
41
56
  * The typed escape spelling for a colliding segment, e.g. `/api/delete` + `delete` →
42
57
  * `api("delete").post()` shown as the chain up to the collision. Best-effort readability: earlier
@@ -53,7 +68,7 @@ export const reservedSegmentRule = {
53
68
  async scan(ctx) {
54
69
  const findings = [];
55
70
  const linesByFile = new Map();
56
- for (const route of routeFacts(ctx)) {
71
+ for (const route of pathSites(routeFacts(ctx))) {
57
72
  for (const segment of route.path.split("/")) {
58
73
  if (segment === "")
59
74
  continue;
@@ -98,6 +113,9 @@ export const duplicateRouteRule = {
98
113
  async scan(ctx) {
99
114
  const findings = [];
100
115
  const byFile = new Map();
116
+ // Two calls that each register several methods collide once per shared method; that is one
117
+ // pair of lines to fix, so it is reported once.
118
+ const reported = new Set();
101
119
  for (const route of routeFacts(ctx)) {
102
120
  let seen = byFile.get(route.file);
103
121
  if (seen === undefined) {
@@ -110,6 +128,10 @@ export const duplicateRouteRule = {
110
128
  seen.set(key, route);
111
129
  continue;
112
130
  }
131
+ const pair = `${route.file}\n${route.line}\n${first.line}\n${route.path}`;
132
+ if (reported.has(pair))
133
+ continue;
134
+ reported.add(pair);
113
135
  findings.push(diagnostic({
114
136
  code: "NF-C019",
115
137
  severity: "error",
@@ -123,5 +145,161 @@ export const duplicateRouteRule = {
123
145
  return findings;
124
146
  },
125
147
  };
126
- export const routeRules = Object.freeze([reservedSegmentRule, duplicateRouteRule]);
148
+ /**
149
+ * NF-C024: two different same-method route patterns in one source file accept at least one of the
150
+ * same paths. This is deliberately file-scoped, like NF-C019: the static scanner cannot prove
151
+ * which application instance owns routes collected from different files in a monorepo.
152
+ */
153
+ export const overlappingRouteRule = {
154
+ code: "NF-C024",
155
+ title: "Overlapping route registration",
156
+ async scan(ctx) {
157
+ const findings = [];
158
+ const linesByFile = new Map();
159
+ const byFile = new Map();
160
+ for (const route of routeFacts(ctx)) {
161
+ const routes = byFile.get(route.file);
162
+ if (routes === undefined)
163
+ byFile.set(route.file, [route]);
164
+ else
165
+ routes.push(route);
166
+ }
167
+ // As in NF-C019: two multi-method calls overlap once per shared method, reported once.
168
+ const reported = new Set();
169
+ for (const [file, routes] of byFile) {
170
+ const lines = () => {
171
+ let value = linesByFile.get(file);
172
+ if (value === undefined) {
173
+ value = (ctx.project.source.read(file) ?? "").split("\n");
174
+ linesByFile.set(file, value);
175
+ }
176
+ return value;
177
+ };
178
+ for (let laterIndex = 0; laterIndex < routes.length; laterIndex += 1) {
179
+ const later = routes[laterIndex];
180
+ const laterKey = `${later.method} ${later.path}`;
181
+ for (let earlierIndex = 0; earlierIndex < laterIndex; earlierIndex += 1) {
182
+ const earlier = routes[earlierIndex];
183
+ if (earlier.method !== later.method || `${earlier.method} ${earlier.path}` === laterKey)
184
+ continue;
185
+ if (commentBlockHasMarker(lines(), later.line, ROUTE_OVERLAP_PRAGMA))
186
+ continue;
187
+ if (commentBlockHasMarker(lines(), earlier.line, ROUTE_OVERLAP_PRAGMA))
188
+ continue;
189
+ let witness;
190
+ try {
191
+ witness = routePatternOverlap(earlier.path, later.path);
192
+ }
193
+ catch (error) {
194
+ if (error instanceof RoutePatternOverlapLimitError) {
195
+ findings.push(diagnostic({
196
+ code: "NF-C025",
197
+ severity: "error",
198
+ file: later.file,
199
+ line: later.line,
200
+ message: `${laterKey} could not be proven disjoint from ${earlier.method} ${earlier.path}; bounded route-overlap analysis exceeded its safety budget - simplify the patterns or add the ${ROUTE_OVERLAP_PRAGMA} comment only after manual review`,
201
+ evidence: [
202
+ `${earlier.method} ${earlier.path}`,
203
+ `${later.method} ${later.path}`,
204
+ `first registration: line ${earlier.line}`,
205
+ ],
206
+ verify: "nifra check --lints-only",
207
+ }));
208
+ }
209
+ // The primary route parser diagnostic owns malformed route patterns. A secondary lint
210
+ // must remain total and never turn an invalid route into a checker crash.
211
+ continue;
212
+ }
213
+ if (witness === undefined)
214
+ continue;
215
+ const pair = `${file}\n${later.line}\n${later.path}\n${earlier.line}\n${earlier.path}`;
216
+ if (reported.has(pair))
217
+ break;
218
+ reported.add(pair);
219
+ findings.push(diagnostic({
220
+ code: "NF-C024",
221
+ severity: "error",
222
+ file: later.file,
223
+ line: later.line,
224
+ message: `${laterKey} overlaps ${earlier.method} ${earlier.path} (first at line ${earlier.line}); witness path: ${witness} - make the patterns disjoint, order them intentionally with a pragma, or add the ${ROUTE_OVERLAP_PRAGMA} comment above either registration`,
225
+ evidence: [
226
+ `${earlier.method} ${earlier.path}`,
227
+ `${later.method} ${later.path}`,
228
+ `witness: ${witness}`,
229
+ `first registration: line ${earlier.line}`,
230
+ ],
231
+ verify: "nifra check --lints-only",
232
+ }));
233
+ break;
234
+ }
235
+ }
236
+ }
237
+ return findings;
238
+ },
239
+ };
240
+ /** A param name directly followed by a character other routers read as a modifier. */
241
+ const PARAM_MODIFIER = /:[A-Za-z_][A-Za-z0-9_]*[?*+{(<]/g;
242
+ /**
243
+ * The first param in `path` whose modifier the router reads as literal text. A `{...}` group the
244
+ * router reads as a constraint (`:id{[0-9]+}`, `:ext{png|jpg}`) is syntax, so it is passed over.
245
+ */
246
+ function literalModifier(path) {
247
+ PARAM_MODIFIER.lastIndex = 0;
248
+ for (let found = PARAM_MODIFIER.exec(path); found !== null; found = PARAM_MODIFIER.exec(path)) {
249
+ const text = found[0];
250
+ if (!text.endsWith("{"))
251
+ return text;
252
+ const constraint = paramConstraint(path.slice(PARAM_MODIFIER.lastIndex - 1));
253
+ if (constraint === undefined)
254
+ return text;
255
+ const end = PARAM_MODIFIER.lastIndex + constraint.source.length + 1;
256
+ // A modifier after the constraint is literal text like any other (`:id{[0-9]+}?/posts`).
257
+ if ("?*+{(<".includes(path[end] ?? "/"))
258
+ return path.slice(found.index, end + 1);
259
+ PARAM_MODIFIER.lastIndex = end;
260
+ }
261
+ return undefined;
262
+ }
263
+ /**
264
+ * NF-C026: a param followed by `?`, `*`, `+`, `{`, `(` or `<` that the router reads as literal text
265
+ * (`/users/:id?/posts`, `/users/:id{int}`). Only a trailing `?` run and a valid `{...}` are syntax.
266
+ */
267
+ export const paramModifierRule = {
268
+ code: "NF-C026",
269
+ title: "Route param followed by an unsupported modifier",
270
+ async scan(ctx) {
271
+ const findings = [];
272
+ const linesByFile = new Map();
273
+ for (const route of pathSites(routeFacts(ctx))) {
274
+ const forms = expandOptionalParams(route.path);
275
+ const text = literalModifier(forms[forms.length - 1] ?? route.path);
276
+ if (text === undefined)
277
+ continue;
278
+ let lines = linesByFile.get(route.file);
279
+ if (lines === undefined) {
280
+ lines = (ctx.project.source.read(route.file) ?? "").split("\n");
281
+ linesByFile.set(route.file, lines);
282
+ }
283
+ if (commentBlockHasMarker(lines, route.line, PARAM_MODIFIER_PRAGMA))
284
+ continue;
285
+ const character = text[text.length - 1];
286
+ findings.push(diagnostic({
287
+ code: "NF-C026",
288
+ severity: "warn",
289
+ file: route.file,
290
+ line: route.line,
291
+ message: `${route.method} ${route.path} - '${character}' after '${text.slice(0, -1)}' is matched as literal text, not as a param modifier${character === "?" ? "; a request path never contains '?', so this route cannot be reached" : ""}. Optional params are supported as the trailing whole segments of a path (\`/users/:id?\`, \`/d/:year?/:month?\`), and a constraint as one character class with an optional count (\`:id{[0-9]+}\`, \`:code{[A-Z]{2}}\`) or a list of two or more values (\`:ext{png|jpg}\`); for anything else register each path, or mark a deliberate literal with \`// ${PARAM_MODIFIER_PRAGMA}\` above the registration`,
292
+ evidence: [`${route.method} ${route.path}`, `literal: ${text}`],
293
+ verify: "nifra check --lints-only",
294
+ }));
295
+ }
296
+ return findings;
297
+ },
298
+ };
299
+ export const routeRules = Object.freeze([
300
+ reservedSegmentRule,
301
+ duplicateRouteRule,
302
+ overlappingRouteRule,
303
+ paramModifierRule,
304
+ ]);
127
305
  //# sourceMappingURL=routes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/rules/routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/rules/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type SecretExemption } from "@nifrajs/web/zones";
2
+ import type { CheckRule } from "./index.js";
3
+ /**
4
+ * The app's `secretExemptions`, read statically from `nifra.config.ts` (check never imports app code):
5
+ * an array literal of object literals with string fields, the form the build error suggests.
6
+ */
7
+ export declare function readSecretExemptions(appRoot: string): SecretExemption[];
8
+ export declare const secretSourceRule: CheckRule;
9
+ export declare const secretRules: readonly CheckRule[];
10
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/rules/secrets.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,eAAe,EAGrB,MAAM,oBAAoB,CAAA;AAK3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAkD3C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CA0CvE;AAED,eAAO,MAAM,gBAAgB,EAAE,SAuD9B,CAAA;AAED,eAAO,MAAM,WAAW,sBAAoC,CAAA"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * NF-C032: what looks like a credential in browser code or in `public/`, checked from source with the
3
+ * scanner every client build runs (`@nifrajs/web/zones`). The build also matches the values of the
4
+ * build environment's private variables in its output; that needs the build's environment, so it
5
+ * stays the build's.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { publicScanFiles, scanForSecrets, } from "@nifrajs/web/zones";
10
+ import { CONFIG_FILE } from "../app-files.js";
11
+ import { stripComments } from "../check-scan.js";
12
+ import { diagnostic } from "../diagnostics.js";
13
+ import { codeUnitOrder } from "../internal/code-unit-order.js";
14
+ import { zonedFiles } from "./zones.js";
15
+ const BROWSER_CODE = new Set(["route-frontend", "frontend", "shared"]);
16
+ const EXEMPTIONS_START = /\bexport\s+const\s+secretExemptions\b[^=]*=\s*\[/;
17
+ /**
18
+ * The tokens of an array literal, from just after its `[` to the `]` that closes it. A string is one
19
+ * token, so a bracket or a colon inside one (`"routes/[lang]/index.tsx"`) is text, never syntax.
20
+ */
21
+ function arrayTokens(src, from) {
22
+ const tokens = [];
23
+ let depth = 0;
24
+ let i = from;
25
+ while (i < src.length) {
26
+ const c = src[i];
27
+ if (c === '"' || c === "'" || c === "`") {
28
+ let value = "";
29
+ let j = i + 1;
30
+ while (j < src.length && src[j] !== c) {
31
+ if (src[j] === "\\") {
32
+ value += src[j + 1] ?? "";
33
+ j += 2;
34
+ }
35
+ else
36
+ value += src[j++];
37
+ }
38
+ tokens.push({ kind: "string", value });
39
+ i = j + 1;
40
+ }
41
+ else if (/[\w$]/.test(c)) {
42
+ let j = i;
43
+ while (j < src.length && /[\w$]/.test(src[j]))
44
+ j++;
45
+ tokens.push({ kind: "word", value: src.slice(i, j) });
46
+ i = j;
47
+ }
48
+ else {
49
+ if (c === "]" || c === "}" || c === ")") {
50
+ if (depth === 0)
51
+ break;
52
+ depth--;
53
+ }
54
+ else if (c === "[" || c === "{" || c === "(")
55
+ depth++;
56
+ if (!/\s/.test(c))
57
+ tokens.push({ kind: "punct", value: c });
58
+ i++;
59
+ }
60
+ }
61
+ return tokens;
62
+ }
63
+ const isPunct = (token, value) => token?.kind === "punct" && token.value === value;
64
+ /**
65
+ * The app's `secretExemptions`, read statically from `nifra.config.ts` (check never imports app code):
66
+ * an array literal of object literals with string fields, the form the build error suggests.
67
+ */
68
+ export function readSecretExemptions(appRoot) {
69
+ const path = join(appRoot, CONFIG_FILE);
70
+ if (!existsSync(path))
71
+ return [];
72
+ const text = stripComments(readFileSync(path, "utf8"));
73
+ const opened = EXEMPTIONS_START.exec(text);
74
+ if (opened === null)
75
+ return [];
76
+ const tokens = arrayTokens(text, opened.index + opened[0].length);
77
+ const exemptions = [];
78
+ let depth = 0;
79
+ let fields;
80
+ for (let i = 0; i < tokens.length; i++) {
81
+ const token = tokens[i];
82
+ if (token.kind === "punct" && "[{(".includes(token.value)) {
83
+ depth++;
84
+ if (depth === 1 && token.value === "{")
85
+ fields = {};
86
+ }
87
+ else if (token.kind === "punct" && "]})".includes(token.value)) {
88
+ if (depth === 1 && fields !== undefined) {
89
+ const { rule, reason, file, env } = fields;
90
+ if (rule !== undefined && reason !== undefined) {
91
+ exemptions.push({
92
+ rule: rule,
93
+ reason,
94
+ ...(file === undefined ? {} : { file }),
95
+ ...(env === undefined ? {} : { env }),
96
+ });
97
+ }
98
+ fields = undefined;
99
+ }
100
+ depth--;
101
+ }
102
+ else if (depth === 1 &&
103
+ fields !== undefined &&
104
+ token.kind !== "punct" &&
105
+ isPunct(tokens[i + 1], ":") &&
106
+ tokens[i + 2]?.kind === "string" &&
107
+ (isPunct(tokens[i + 3], ",") || isPunct(tokens[i + 3], "}"))) {
108
+ fields[token.value] ??= tokens[i + 2].value;
109
+ i += 2;
110
+ }
111
+ }
112
+ return exemptions;
113
+ }
114
+ export const secretSourceRule = {
115
+ code: "NF-C032",
116
+ title: "Credential in browser code",
117
+ async scan(ctx) {
118
+ const { files } = zonedFiles(ctx);
119
+ const apps = new Map();
120
+ for (const { file, app, classification } of files) {
121
+ if (!apps.has(app))
122
+ apps.set(app, []);
123
+ if (!BROWSER_CODE.has(classification.zone))
124
+ continue;
125
+ const text = ctx.sources.read(file);
126
+ if (text === undefined)
127
+ continue;
128
+ const name = app === "" ? file : file.slice(app.length + 1);
129
+ apps.get(app)?.push({ name, text, firstParty: true });
130
+ }
131
+ const findings = [];
132
+ for (const [app, sources] of apps) {
133
+ const appRoot = join(ctx.root, app);
134
+ const publicDir = join(appRoot, "public");
135
+ try {
136
+ const found = scanForSecrets({
137
+ sources,
138
+ artifacts: existsSync(publicDir) ? publicScanFiles(publicDir, "public") : [],
139
+ exemptions: readSecretExemptions(appRoot),
140
+ });
141
+ for (const finding of found) {
142
+ const file = app === "" ? finding.file : `${app}/${finding.file}`;
143
+ findings.push(diagnostic({
144
+ code: "NF-C032",
145
+ severity: "error",
146
+ file,
147
+ line: finding.line,
148
+ message: `${file} carries what looks like a credential (${finding.what}), and it reaches the browser. Read it from the environment in a route's backend half or under backend/, and rotate it if it was ever committed`,
149
+ evidence: [`rule: ${finding.rule}`, `match: ${finding.preview}`],
150
+ verify: "nifra check",
151
+ }));
152
+ }
153
+ }
154
+ catch (error) {
155
+ // A malformed exemption: the build refuses it with the same message.
156
+ findings.push(diagnostic({
157
+ code: "NF-C032",
158
+ severity: "error",
159
+ file: join(app, CONFIG_FILE),
160
+ message: error instanceof Error ? error.message : String(error),
161
+ verify: "nifra check",
162
+ }));
163
+ }
164
+ }
165
+ return findings.sort((a, b) => codeUnitOrder(a.file ?? "", b.file ?? "") || (a.line ?? 0) - (b.line ?? 0));
166
+ },
167
+ };
168
+ export const secretRules = Object.freeze([secretSourceRule]);
169
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/rules/secrets.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,11 @@
1
+ import type * as TSApi from "typescript";
1
2
  import type { CheckRule } from "./index.js";
3
+ /**
4
+ * Rewrite every unreviewed NF-S002 comparison in one file to `nifraTimingSafeEqual(left, right)` and
5
+ * add the helper once. Sites come from the syntax tree, not a reported line: the helper lands above
6
+ * them, so a line number a check run reported no longer names the same statement after the first fix.
7
+ */
8
+ export declare function rewriteSecretComparisons(ts: typeof TSApi, file: string, source: string): string;
2
9
  export declare const secretComparisonRule: CheckRule;
3
10
  export declare const piiLogRule: CheckRule;
4
11
  export declare const failOpenGateRule: CheckRule;
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/rules/security.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAA;AAyHxD,eAAO,MAAM,oBAAoB,EAAE,SA2DlC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,SA8CxB,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,SAgE9B,CAAA;AAiBD,eAAO,MAAM,uBAAuB,EAAE,SA2DrC,CAAA;AAED,eAAO,MAAM,oBAAoB,EAAE,SA8ClC,CAAA;AAWD,eAAO,MAAM,wBAAwB,EAAE,SAuCtC,CAAA;AAID,eAAO,MAAM,0BAA0B,EAAE,SAsDxC,CAAA;AAED,eAAO,MAAM,aAAa,sBAQxB,CAAA"}
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/rules/security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAKxC,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAA;AAoMxD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA+B/F;AAED,eAAO,MAAM,oBAAoB,EAAE,SAwClC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,SAgDxB,CAAA;AA0BD,eAAO,MAAM,gBAAgB,EAAE,SA+D9B,CAAA;AAiBD,eAAO,MAAM,uBAAuB,EAAE,SA6DrC,CAAA;AAED,eAAO,MAAM,oBAAoB,EAAE,SAgDlC,CAAA;AAWD,eAAO,MAAM,wBAAwB,EAAE,SAyCtC,CAAA;AAID,eAAO,MAAM,0BAA0B,EAAE,SAwDxC,CAAA;AAED,eAAO,MAAM,aAAa,sBAQxB,CAAA"}