@nifrajs/web 4.0.2 → 5.1.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 (426) hide show
  1. package/README.md +19 -0
  2. package/dist/build-vite.d.ts +19 -0
  3. package/dist/build-vite.d.ts.map +1 -1
  4. package/dist/build-vite.js +31 -10
  5. package/dist/build-vite.js.map +1 -1
  6. package/dist/build.d.ts +8 -10
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +90 -31
  9. package/dist/build.js.map +1 -1
  10. package/dist/cdn.d.ts +8 -0
  11. package/dist/cdn.d.ts.map +1 -1
  12. package/dist/cdn.js +14 -15
  13. package/dist/cdn.js.map +1 -1
  14. package/dist/client.d.ts +6 -6
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +46 -18
  17. package/dist/client.js.map +1 -1
  18. package/dist/deferred.d.ts +2 -2
  19. package/dist/deferred.d.ts.map +1 -1
  20. package/dist/deferred.js +28 -7
  21. package/dist/deferred.js.map +1 -1
  22. package/dist/dev-feed.d.ts.map +1 -1
  23. package/dist/dev-feed.js +24 -5
  24. package/dist/dev-feed.js.map +1 -1
  25. package/dist/dev-port.d.ts +3 -2
  26. package/dist/dev-port.d.ts.map +1 -1
  27. package/dist/dev-port.js +2 -6
  28. package/dist/dev-port.js.map +1 -1
  29. package/dist/dev-session.d.ts.map +1 -1
  30. package/dist/dev-session.js +4 -14
  31. package/dist/dev-session.js.map +1 -1
  32. package/dist/dev.d.ts +7 -17
  33. package/dist/dev.d.ts.map +1 -1
  34. package/dist/dev.js +13 -1
  35. package/dist/dev.js.map +1 -1
  36. package/dist/draft.d.ts +8 -5
  37. package/dist/draft.d.ts.map +1 -1
  38. package/dist/draft.js +30 -13
  39. package/dist/draft.js.map +1 -1
  40. package/dist/fonts-google.d.ts.map +1 -1
  41. package/dist/fonts-google.js +7 -0
  42. package/dist/fonts-google.js.map +1 -1
  43. package/dist/index.d.ts +5 -4
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -3
  46. package/dist/index.js.map +1 -1
  47. package/dist/internal/codegen.d.ts +1 -0
  48. package/dist/internal/codegen.d.ts.map +1 -1
  49. package/dist/internal/codegen.js +56 -29
  50. package/dist/internal/codegen.js.map +1 -1
  51. package/dist/internal/disposable-dir.d.ts +8 -0
  52. package/dist/internal/disposable-dir.d.ts.map +1 -0
  53. package/dist/internal/disposable-dir.js +25 -0
  54. package/dist/internal/disposable-dir.js.map +1 -0
  55. package/dist/internal/entry-chunks.d.ts +9 -0
  56. package/dist/internal/entry-chunks.d.ts.map +1 -0
  57. package/dist/internal/entry-chunks.js +15 -0
  58. package/dist/internal/entry-chunks.js.map +1 -0
  59. package/dist/internal/evidence-traversal.d.ts +12 -0
  60. package/dist/internal/evidence-traversal.d.ts.map +1 -0
  61. package/dist/internal/evidence-traversal.js +48 -0
  62. package/dist/internal/evidence-traversal.js.map +1 -0
  63. package/dist/internal/head-merge.d.ts +6 -0
  64. package/dist/internal/head-merge.d.ts.map +1 -1
  65. package/dist/internal/head-merge.js +9 -0
  66. package/dist/internal/head-merge.js.map +1 -1
  67. package/dist/internal/js-string.d.ts +2 -0
  68. package/dist/internal/js-string.d.ts.map +1 -1
  69. package/dist/internal/js-string.js +5 -1
  70. package/dist/internal/js-string.js.map +1 -1
  71. package/dist/internal/mount-shadow.js +1 -1
  72. package/dist/internal/mount-shadow.js.map +1 -1
  73. package/dist/internal/output-guard.d.ts +7 -0
  74. package/dist/internal/output-guard.d.ts.map +1 -1
  75. package/dist/internal/output-guard.js +80 -8
  76. package/dist/internal/output-guard.js.map +1 -1
  77. package/dist/internal/package-entries.d.ts +34 -0
  78. package/dist/internal/package-entries.d.ts.map +1 -0
  79. package/dist/internal/package-entries.js +324 -0
  80. package/dist/internal/package-entries.js.map +1 -0
  81. package/dist/internal/page-execution.d.ts +6 -0
  82. package/dist/internal/page-execution.d.ts.map +1 -1
  83. package/dist/internal/page-execution.js +437 -114
  84. package/dist/internal/page-execution.js.map +1 -1
  85. package/dist/internal/parity.d.ts.map +1 -1
  86. package/dist/internal/parity.js +15 -2
  87. package/dist/internal/parity.js.map +1 -1
  88. package/dist/internal/private-env.d.ts +8 -0
  89. package/dist/internal/private-env.d.ts.map +1 -1
  90. package/dist/internal/private-env.js +25 -10
  91. package/dist/internal/private-env.js.map +1 -1
  92. package/dist/internal/progressive-cookies.d.ts +14 -0
  93. package/dist/internal/progressive-cookies.d.ts.map +1 -0
  94. package/dist/internal/progressive-cookies.js +43 -0
  95. package/dist/internal/progressive-cookies.js.map +1 -0
  96. package/dist/internal/public-env.d.ts +2 -0
  97. package/dist/internal/public-env.d.ts.map +1 -0
  98. package/dist/internal/public-env.js +13 -0
  99. package/dist/internal/public-env.js.map +1 -0
  100. package/dist/internal/render-document.d.ts +5 -44
  101. package/dist/internal/render-document.d.ts.map +1 -1
  102. package/dist/internal/render-document.js +51 -63
  103. package/dist/internal/render-document.js.map +1 -1
  104. package/dist/internal/response-controls.d.ts +2 -0
  105. package/dist/internal/response-controls.d.ts.map +1 -1
  106. package/dist/internal/response-controls.js +26 -16
  107. package/dist/internal/response-controls.js.map +1 -1
  108. package/dist/internal/revive-dates.d.ts +16 -0
  109. package/dist/internal/revive-dates.d.ts.map +1 -0
  110. package/dist/internal/revive-dates.js +32 -0
  111. package/dist/internal/revive-dates.js.map +1 -0
  112. package/dist/internal/route-path.d.ts +14 -0
  113. package/dist/internal/route-path.d.ts.map +1 -0
  114. package/dist/internal/route-path.js +19 -0
  115. package/dist/internal/route-path.js.map +1 -0
  116. package/dist/internal/secret-scan.d.ts.map +1 -1
  117. package/dist/internal/secret-scan.js +190 -20
  118. package/dist/internal/secret-scan.js.map +1 -1
  119. package/dist/internal/server-boundary.d.ts.map +1 -1
  120. package/dist/internal/server-boundary.js +33 -5
  121. package/dist/internal/server-boundary.js.map +1 -1
  122. package/dist/internal/web-app.d.ts +25 -18
  123. package/dist/internal/web-app.d.ts.map +1 -1
  124. package/dist/internal/web-app.js +81 -24
  125. package/dist/internal/web-app.js.map +1 -1
  126. package/dist/internal/wire-date.d.ts +3 -0
  127. package/dist/internal/wire-date.d.ts.map +1 -0
  128. package/dist/internal/wire-date.js +14 -0
  129. package/dist/internal/wire-date.js.map +1 -0
  130. package/dist/internal/without-query.d.ts +4 -0
  131. package/dist/internal/without-query.d.ts.map +1 -0
  132. package/dist/internal/without-query.js +9 -0
  133. package/dist/internal/without-query.js.map +1 -0
  134. package/dist/internal/zone-graph.d.ts +4 -0
  135. package/dist/internal/zone-graph.d.ts.map +1 -1
  136. package/dist/internal/zone-graph.js +71 -20
  137. package/dist/internal/zone-graph.js.map +1 -1
  138. package/dist/isr.d.ts +59 -3
  139. package/dist/isr.d.ts.map +1 -1
  140. package/dist/isr.js +311 -41
  141. package/dist/isr.js.map +1 -1
  142. package/dist/llms-txt.d.ts +2 -0
  143. package/dist/llms-txt.d.ts.map +1 -1
  144. package/dist/llms-txt.js +29 -3
  145. package/dist/llms-txt.js.map +1 -1
  146. package/dist/manifest.d.ts +37 -4
  147. package/dist/manifest.d.ts.map +1 -1
  148. package/dist/manifest.js +4 -0
  149. package/dist/manifest.js.map +1 -1
  150. package/dist/open-resolved.d.ts +18 -0
  151. package/dist/open-resolved.d.ts.map +1 -0
  152. package/dist/open-resolved.js +62 -0
  153. package/dist/open-resolved.js.map +1 -0
  154. package/dist/plugins/kit.d.ts +1 -0
  155. package/dist/plugins/kit.d.ts.map +1 -1
  156. package/dist/plugins/kit.js +26 -11
  157. package/dist/plugins/kit.js.map +1 -1
  158. package/dist/plugins/vite-leak-guard.d.ts +9 -1
  159. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  160. package/dist/plugins/vite-leak-guard.js +84 -7
  161. package/dist/plugins/vite-leak-guard.js.map +1 -1
  162. package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
  163. package/dist/plugins/vite-zone-guard.js +17 -21
  164. package/dist/plugins/vite-zone-guard.js.map +1 -1
  165. package/dist/plugins/zone-guard.d.ts.map +1 -1
  166. package/dist/plugins/zone-guard.js +44 -3
  167. package/dist/plugins/zone-guard.js.map +1 -1
  168. package/dist/prerender.d.ts +5 -1
  169. package/dist/prerender.d.ts.map +1 -1
  170. package/dist/prerender.js +41 -19
  171. package/dist/prerender.js.map +1 -1
  172. package/dist/public-dir.d.ts +8 -1
  173. package/dist/public-dir.d.ts.map +1 -1
  174. package/dist/public-dir.js +17 -6
  175. package/dist/public-dir.js.map +1 -1
  176. package/dist/query.d.ts +13 -4
  177. package/dist/query.d.ts.map +1 -1
  178. package/dist/query.js +89 -26
  179. package/dist/query.js.map +1 -1
  180. package/dist/render-seam.d.ts +25 -2
  181. package/dist/render-seam.d.ts.map +1 -1
  182. package/dist/render-seam.js +1 -0
  183. package/dist/render-seam.js.map +1 -1
  184. package/dist/route-types.d.ts.map +1 -1
  185. package/dist/route-types.js +20 -9
  186. package/dist/route-types.js.map +1 -1
  187. package/dist/router.d.ts +9 -2
  188. package/dist/router.d.ts.map +1 -1
  189. package/dist/router.js +47 -50
  190. package/dist/router.js.map +1 -1
  191. package/dist/seo.d.ts +49 -0
  192. package/dist/seo.d.ts.map +1 -0
  193. package/dist/seo.js +48 -0
  194. package/dist/seo.js.map +1 -0
  195. package/dist/server-components/build-info.d.ts +3 -0
  196. package/dist/server-components/build-info.d.ts.map +1 -0
  197. package/dist/server-components/build-info.js +7 -0
  198. package/dist/server-components/build-info.js.map +1 -0
  199. package/dist/server-components/build-target.d.ts +12 -0
  200. package/dist/server-components/build-target.d.ts.map +1 -0
  201. package/dist/server-components/build-target.js +153 -0
  202. package/dist/server-components/build-target.js.map +1 -0
  203. package/dist/server-components/build.d.ts +24 -0
  204. package/dist/server-components/build.d.ts.map +1 -0
  205. package/dist/server-components/build.js +366 -0
  206. package/dist/server-components/build.js.map +1 -0
  207. package/dist/server-components/bundle-proof.d.ts +3 -0
  208. package/dist/server-components/bundle-proof.d.ts.map +1 -0
  209. package/dist/server-components/bundle-proof.js +23 -0
  210. package/dist/server-components/bundle-proof.js.map +1 -0
  211. package/dist/server-components/client.d.ts +46 -0
  212. package/dist/server-components/client.d.ts.map +1 -0
  213. package/dist/server-components/client.js +594 -0
  214. package/dist/server-components/client.js.map +1 -0
  215. package/dist/server-components/definition.d.ts +11 -0
  216. package/dist/server-components/definition.d.ts.map +1 -0
  217. package/dist/server-components/definition.js +94 -0
  218. package/dist/server-components/definition.js.map +1 -0
  219. package/dist/server-components/dev-http.d.ts +5 -0
  220. package/dist/server-components/dev-http.d.ts.map +1 -0
  221. package/dist/server-components/dev-http.js +71 -0
  222. package/dist/server-components/dev-http.js.map +1 -0
  223. package/dist/server-components/error.d.ts +9 -0
  224. package/dist/server-components/error.d.ts.map +1 -0
  225. package/dist/server-components/error.js +24 -0
  226. package/dist/server-components/error.js.map +1 -0
  227. package/dist/server-components/frames.d.ts +7 -0
  228. package/dist/server-components/frames.d.ts.map +1 -0
  229. package/dist/server-components/frames.js +169 -0
  230. package/dist/server-components/frames.js.map +1 -0
  231. package/dist/server-components/html-policy.d.ts +6 -0
  232. package/dist/server-components/html-policy.d.ts.map +1 -0
  233. package/dist/server-components/html-policy.js +75 -0
  234. package/dist/server-components/html-policy.js.map +1 -0
  235. package/dist/server-components/html.d.ts +4 -0
  236. package/dist/server-components/html.d.ts.map +1 -0
  237. package/dist/server-components/html.js +67 -0
  238. package/dist/server-components/html.js.map +1 -0
  239. package/dist/server-components/islands.d.ts +44 -0
  240. package/dist/server-components/islands.d.ts.map +1 -0
  241. package/dist/server-components/islands.js +42 -0
  242. package/dist/server-components/islands.js.map +1 -0
  243. package/dist/server-components/native.d.ts +4 -0
  244. package/dist/server-components/native.d.ts.map +1 -0
  245. package/dist/server-components/native.js +81 -0
  246. package/dist/server-components/native.js.map +1 -0
  247. package/dist/server-components/portal.d.ts +6 -0
  248. package/dist/server-components/portal.d.ts.map +1 -0
  249. package/dist/server-components/portal.js +35 -0
  250. package/dist/server-components/portal.js.map +1 -0
  251. package/dist/server-components/props.d.ts +3 -0
  252. package/dist/server-components/props.d.ts.map +1 -0
  253. package/dist/server-components/props.js +14 -0
  254. package/dist/server-components/props.js.map +1 -0
  255. package/dist/server-components/protocol.d.ts +44 -0
  256. package/dist/server-components/protocol.d.ts.map +1 -0
  257. package/dist/server-components/protocol.js +139 -0
  258. package/dist/server-components/protocol.js.map +1 -0
  259. package/dist/server-components/range.d.ts +24 -0
  260. package/dist/server-components/range.d.ts.map +1 -0
  261. package/dist/server-components/range.js +263 -0
  262. package/dist/server-components/range.js.map +1 -0
  263. package/dist/server-components/registry.d.ts +6 -0
  264. package/dist/server-components/registry.d.ts.map +1 -0
  265. package/dist/server-components/registry.js +30 -0
  266. package/dist/server-components/registry.js.map +1 -0
  267. package/dist/server-components/response.d.ts +4 -0
  268. package/dist/server-components/response.d.ts.map +1 -0
  269. package/dist/server-components/response.js +32 -0
  270. package/dist/server-components/response.js.map +1 -0
  271. package/dist/server-components/router-integration.d.ts +9 -0
  272. package/dist/server-components/router-integration.d.ts.map +1 -0
  273. package/dist/server-components/router-integration.js +29 -0
  274. package/dist/server-components/router-integration.js.map +1 -0
  275. package/dist/server-components/runtime.d.ts +14 -0
  276. package/dist/server-components/runtime.d.ts.map +1 -0
  277. package/dist/server-components/runtime.js +276 -0
  278. package/dist/server-components/runtime.js.map +1 -0
  279. package/dist/server-components/scheduler.d.ts +14 -0
  280. package/dist/server-components/scheduler.d.ts.map +1 -0
  281. package/dist/server-components/scheduler.js +74 -0
  282. package/dist/server-components/scheduler.js.map +1 -0
  283. package/dist/server-components/schema.d.ts +4 -0
  284. package/dist/server-components/schema.d.ts.map +1 -0
  285. package/dist/server-components/schema.js +77 -0
  286. package/dist/server-components/schema.js.map +1 -0
  287. package/dist/server-components/server-entry.d.ts +12 -0
  288. package/dist/server-components/server-entry.d.ts.map +1 -0
  289. package/dist/server-components/server-entry.js +90 -0
  290. package/dist/server-components/server-entry.js.map +1 -0
  291. package/dist/server-components/source.d.ts +15 -0
  292. package/dist/server-components/source.d.ts.map +1 -0
  293. package/dist/server-components/source.js +269 -0
  294. package/dist/server-components/source.js.map +1 -0
  295. package/dist/server-components/state.d.ts +4 -0
  296. package/dist/server-components/state.d.ts.map +1 -0
  297. package/dist/server-components/state.js +123 -0
  298. package/dist/server-components/state.js.map +1 -0
  299. package/dist/server-components/stream.d.ts +6 -0
  300. package/dist/server-components/stream.d.ts.map +1 -0
  301. package/dist/server-components/stream.js +70 -0
  302. package/dist/server-components/stream.js.map +1 -0
  303. package/dist/server-components/trusted-html.d.ts +3 -0
  304. package/dist/server-components/trusted-html.d.ts.map +1 -0
  305. package/dist/server-components/trusted-html.js +14 -0
  306. package/dist/server-components/trusted-html.js.map +1 -0
  307. package/dist/server-components/types.d.ts +107 -0
  308. package/dist/server-components/types.d.ts.map +1 -0
  309. package/dist/server-components/types.js +2 -0
  310. package/dist/server-components/types.js.map +1 -0
  311. package/dist/server-components/typescript.d.ts +11 -0
  312. package/dist/server-components/typescript.d.ts.map +1 -0
  313. package/dist/server-components/typescript.js +28 -0
  314. package/dist/server-components/typescript.js.map +1 -0
  315. package/dist/server-components/value.d.ts +5 -0
  316. package/dist/server-components/value.d.ts.map +1 -0
  317. package/dist/server-components/value.js +77 -0
  318. package/dist/server-components/value.js.map +1 -0
  319. package/dist/server-components/vite.d.ts +12 -0
  320. package/dist/server-components/vite.d.ts.map +1 -0
  321. package/dist/server-components/vite.js +197 -0
  322. package/dist/server-components/vite.js.map +1 -0
  323. package/dist/server-components.d.ts +5 -0
  324. package/dist/server-components.d.ts.map +1 -0
  325. package/dist/server-components.js +4 -0
  326. package/dist/server-components.js.map +1 -0
  327. package/dist/service-worker.d.ts +8 -1
  328. package/dist/service-worker.d.ts.map +1 -1
  329. package/dist/service-worker.js +20 -6
  330. package/dist/service-worker.js.map +1 -1
  331. package/dist/vite.d.ts +12 -0
  332. package/dist/vite.d.ts.map +1 -1
  333. package/dist/vite.js +7 -1
  334. package/dist/vite.js.map +1 -1
  335. package/dist/zones.d.ts +64 -2
  336. package/dist/zones.d.ts.map +1 -1
  337. package/dist/zones.js +331 -28
  338. package/dist/zones.js.map +1 -1
  339. package/package.json +102 -4
  340. package/src/build-vite.ts +31 -9
  341. package/src/build.ts +109 -31
  342. package/src/cdn.ts +18 -12
  343. package/src/client.ts +48 -17
  344. package/src/deferred.ts +27 -9
  345. package/src/dev-feed.ts +24 -5
  346. package/src/dev-port.ts +4 -10
  347. package/src/dev-session.ts +4 -13
  348. package/src/dev.ts +29 -1
  349. package/src/draft.ts +35 -14
  350. package/src/fonts-google.ts +10 -0
  351. package/src/index.ts +5 -6
  352. package/src/internal/codegen.ts +60 -29
  353. package/src/internal/disposable-dir.ts +24 -0
  354. package/src/internal/entry-chunks.ts +18 -0
  355. package/src/internal/evidence-traversal.ts +57 -0
  356. package/src/internal/head-merge.ts +16 -0
  357. package/src/internal/js-string.ts +6 -1
  358. package/src/internal/mount-shadow.ts +1 -1
  359. package/src/internal/output-guard.ts +88 -7
  360. package/src/internal/package-entries.ts +331 -0
  361. package/src/internal/page-execution.ts +573 -114
  362. package/src/internal/parity.ts +15 -2
  363. package/src/internal/private-env.ts +27 -13
  364. package/src/internal/progressive-cookies.ts +60 -0
  365. package/src/internal/public-env.ts +14 -0
  366. package/src/internal/render-document.ts +66 -89
  367. package/src/internal/response-controls.ts +31 -14
  368. package/src/internal/revive-dates.ts +35 -0
  369. package/src/internal/route-path.ts +24 -0
  370. package/src/internal/secret-scan.ts +190 -18
  371. package/src/internal/server-boundary.ts +29 -5
  372. package/src/internal/web-app.ts +126 -42
  373. package/src/internal/wire-date.ts +16 -0
  374. package/src/internal/without-query.ts +8 -0
  375. package/src/internal/zone-graph.ts +78 -25
  376. package/src/isr.ts +364 -45
  377. package/src/llms-txt.ts +32 -3
  378. package/src/manifest.ts +38 -4
  379. package/src/open-resolved.ts +64 -0
  380. package/src/plugins/kit.ts +31 -11
  381. package/src/plugins/vite-leak-guard.ts +104 -8
  382. package/src/plugins/vite-zone-guard.ts +21 -19
  383. package/src/plugins/zone-guard.ts +41 -2
  384. package/src/prerender.ts +39 -18
  385. package/src/public-dir.ts +27 -8
  386. package/src/query.ts +107 -26
  387. package/src/render-seam.ts +30 -2
  388. package/src/route-types.ts +34 -7
  389. package/src/router.ts +66 -59
  390. package/src/seo.ts +68 -0
  391. package/src/server-components/build-info.ts +11 -0
  392. package/src/server-components/build-target.ts +194 -0
  393. package/src/server-components/build.ts +419 -0
  394. package/src/server-components/bundle-proof.ts +33 -0
  395. package/src/server-components/client.ts +663 -0
  396. package/src/server-components/definition.ts +125 -0
  397. package/src/server-components/dev-http.ts +75 -0
  398. package/src/server-components/error.ts +24 -0
  399. package/src/server-components/frames.ts +181 -0
  400. package/src/server-components/html-policy.ts +94 -0
  401. package/src/server-components/html.ts +81 -0
  402. package/src/server-components/islands.ts +111 -0
  403. package/src/server-components/native.ts +101 -0
  404. package/src/server-components/portal.ts +42 -0
  405. package/src/server-components/props.ts +19 -0
  406. package/src/server-components/protocol.ts +167 -0
  407. package/src/server-components/range.ts +313 -0
  408. package/src/server-components/registry.ts +34 -0
  409. package/src/server-components/response.ts +44 -0
  410. package/src/server-components/router-integration.ts +40 -0
  411. package/src/server-components/runtime.ts +376 -0
  412. package/src/server-components/scheduler.ts +84 -0
  413. package/src/server-components/schema.ts +69 -0
  414. package/src/server-components/server-entry.ts +98 -0
  415. package/src/server-components/source.ts +324 -0
  416. package/src/server-components/state.ts +143 -0
  417. package/src/server-components/stream.ts +71 -0
  418. package/src/server-components/trusted-html.ts +21 -0
  419. package/src/server-components/types.ts +138 -0
  420. package/src/server-components/typescript.ts +39 -0
  421. package/src/server-components/value.ts +82 -0
  422. package/src/server-components/vite.ts +242 -0
  423. package/src/server-components.ts +32 -0
  424. package/src/service-worker.ts +24 -7
  425. package/src/vite.ts +8 -1
  426. package/src/zones.ts +418 -32
package/src/llms-txt.ts CHANGED
@@ -3,9 +3,12 @@ import {
3
3
  reflectedRoutesFromEvidence,
4
4
  snapshotProjectEvidence,
5
5
  } from "@nifrajs/core/evidence"
6
+ import { type BackendEvidenceProvider, NIFRA_BACKEND_EVIDENCE } from "@nifrajs/core/mount"
6
7
  import { compileRoutePattern, type RoutePatternSegment } from "@nifrajs/core/pattern"
7
8
 
8
9
  const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/
10
+ const RESERVED_CLIENT_SEGMENT = /^(?:get|post|put|patch|delete|head|options)$/i
11
+ const RESERVED_EXACT_SEGMENTS = new Set(["subscribe", "ws", "index", "then"])
9
12
 
10
13
  /** The subset of JSON Schema fields {@link tsTypeOf} reads to render a TypeScript type string. */
11
14
  interface JsonSchemaNode {
@@ -93,7 +96,13 @@ function clientCall(method: string, path: string, schema: unknown): string {
93
96
  if (segs.length === 0) chain += ".index"
94
97
  else
95
98
  for (const seg of segs) {
96
- chain += paramCall(seg) ?? (IDENT.test(seg) ? `.${seg}` : `[${JSON.stringify(seg)}]`)
99
+ chain +=
100
+ paramCall(seg) ??
101
+ (RESERVED_CLIENT_SEGMENT.test(seg) || RESERVED_EXACT_SEGMENTS.has(seg)
102
+ ? `(${JSON.stringify(seg)})`
103
+ : IDENT.test(seg)
104
+ ? `.${seg}`
105
+ : `[${JSON.stringify(seg)}]`)
97
106
  }
98
107
  const isBodyVerb = verb === "post" || verb === "put" || verb === "patch"
99
108
  let call: string
@@ -101,12 +110,27 @@ function clientCall(method: string, path: string, schema: unknown): string {
101
110
  const bodyArg = s?.body ? "body" : s?.query ? "undefined" : ""
102
111
  const opts = s?.query ? (bodyArg ? ", { query }" : "{ query }") : ""
103
112
  call = `.${verb}(${bodyArg}${opts})`
113
+ } else if (verb === "delete") {
114
+ const fields = [...(s?.body ? ["body"] : []), ...(s?.query ? ["query"] : [])]
115
+ call = `.delete(${fields.length === 0 ? "" : `{ ${fields.join(", ")} }`})`
104
116
  } else {
105
117
  call = `.${verb}(${s?.query ? "{ query }" : ""})`
106
118
  }
107
119
  return `await ${chain}${call}`
108
120
  }
109
121
 
122
+ async function backendEvidence(backend: unknown): Promise<ProjectEvidenceSnapshot> {
123
+ if (backend !== null && (typeof backend === "object" || typeof backend === "function")) {
124
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object/function check allows reading the optional symbol-keyed provider without touching the client path proxy.
125
+ const provider = (backend as { readonly [NIFRA_BACKEND_EVIDENCE]?: unknown })[
126
+ NIFRA_BACKEND_EVIDENCE
127
+ ]
128
+ // biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: A callable at NIFRA_BACKEND_EVIDENCE implements the framework's trusted evidence-provider seam.
129
+ if (typeof provider === "function") return (provider as BackendEvidenceProvider).call(backend)
130
+ }
131
+ return snapshotProjectEvidence(backend)
132
+ }
133
+
110
134
  /** Read the project's `AGENTS.md`, or `""` if there isn't one (or we're on an edge runtime with no
111
135
  * filesystem). Never throws - a missing file just means no guidelines section. */
112
136
  async function readAgentsMd(): Promise<string> {
@@ -134,6 +158,8 @@ export async function generateLlmsTxt(
134
158
  readonly includeLocalGuidelines?: boolean
135
159
  /** Reuse the project's canonical route-evidence pass when one is already available. */
136
160
  readonly evidence?: ProjectEvidenceSnapshot
161
+ /** The mount prefix a visitor's URL carries but the backend's own routes do not (`apiStrip`). */
162
+ readonly apiPathPrefix?: string
137
163
  } = {},
138
164
  ): Promise<string> {
139
165
  let output = ""
@@ -160,14 +186,17 @@ export async function generateLlmsTxt(
160
186
  // 3. API Routes
161
187
  output += `## API Routes\n\n`
162
188
  const apiRoutes = reflectedRoutesFromEvidence(
163
- options.evidence ?? snapshotProjectEvidence(backend),
189
+ options.evidence ?? (await backendEvidence(backend)),
164
190
  )
165
191
 
166
192
  if (apiRoutes.length === 0) {
167
193
  output += `No API routes registered.\n`
168
194
  } else {
195
+ const prefix = options.apiPathPrefix ?? ""
169
196
  for (const route of apiRoutes) {
170
- output += `- **${route.method}** \`${route.path}\`\n`
197
+ // The stripped backend's root is the bare prefix, as the mount matches it.
198
+ const path = prefix !== "" && route.path === "/" ? prefix : prefix + route.path
199
+ output += `- **${route.method}** \`${path}\`\n`
171
200
  if (full) {
172
201
  output += ` - Client Call: \`${clientCall(route.method, route.path, route.schema)}\`\n`
173
202
  const s = route.schema
package/src/manifest.ts CHANGED
@@ -271,13 +271,24 @@ export interface Meta {
271
271
  }
272
272
 
273
273
  /**
274
- * Args for a route's `meta` function: the loader's `data` + the route `params` + the request `origin`.
275
- * `meta()` runs in BOTH SSR and client navigation, so it has **no `request`/`process.env`/server access** -
276
- * `origin` is the only server-resolved fact it gets (so you needn't thread `siteUrl` through loader data
277
- * for absolute `og:url`/`canonical`/`og:image` URLs). See {@link origin}.
274
+ * Args for a route's or layout's `meta` function: its own loader's `data` + the page's `pageData` + the
275
+ * route `params` + the request `origin`. `meta()` runs in BOTH SSR and client navigation, so it has
276
+ * **no `request`/`process.env`/server access** - `origin` is the only server-resolved fact it gets (so you
277
+ * needn't thread `siteUrl` through loader data for absolute `og:url`/`canonical`/`og:image` URLs). See
278
+ * {@link origin}.
278
279
  */
279
280
  export interface MetaArgs<Data = unknown> {
281
+ /**
282
+ * The loader data of the module that exports this `meta`: the page's for a page, the layout's own for a
283
+ * layout (`null` when the layout has no loader), the same value its component receives. A layout that
284
+ * loads the locale sets `lang` from here, so a soft navigation keeps it.
285
+ */
280
286
  readonly data: Data
287
+ /**
288
+ * The page's loader data, for a layout's `meta` that builds on the page (a title template). In a page's
289
+ * own `meta` it is the same value as {@link data}. Always set by the framework.
290
+ */
291
+ readonly pageData?: unknown
281
292
  readonly params: Record<string, string>
282
293
  /** The resolved document CSP nonce, when this render uses per-request nonce protection. */
283
294
  readonly nonce?: string
@@ -323,7 +334,18 @@ export type GetStaticPaths = () => StaticPaths | Promise<StaticPaths>
323
334
  export interface RouteModule {
324
335
  readonly default: unknown
325
336
  readonly loader?: Loader
337
+ /** Layout loader that must run before actions and is not retained across the action's render. */
338
+ readonly gate?: boolean
326
339
  readonly action?: Action
340
+ /**
341
+ * Backend half only: which browser origins may POST to {@link action}. When omitted, a request
342
+ * whose `Origin` is another site's is refused with 403 before middleware, gates or the action run;
343
+ * requests without an `Origin` (non-browser clients) pass. An array allows exactly those origins
344
+ * (and refuses a request without one); a function decides, receiving `null` when there is none.
345
+ * Use it for a callback a third party posts to, such as an identity provider's `form_post`.
346
+ * `Origin` is browser-enforced, not authentication: keep the action's own auth checks.
347
+ */
348
+ readonly allowedOrigins?: ReadonlyArray<string> | ((origin: string | null) => boolean)
327
349
  /**
328
350
  * Backend half only: the schema of what {@link loader} sends to the browser. Required whenever the
329
351
  * loader returns data. Keys it does not declare are dropped before the page renders; a declared
@@ -384,6 +406,14 @@ export interface RouteModule {
384
406
  * the module on the server and in the browser, so it must be the same value on both.
385
407
  */
386
408
  readonly handle?: unknown
409
+ /**
410
+ * On a root status page (`_404.tsx`, `_<status>.tsx`) or an `_error.tsx`. By default (`true`) the
411
+ * page renders inside the layouts above it with their loader data and `meta`, as a nested `_404`
412
+ * renders. When those layouts cannot render it - a loader throws, a gate or the middleware refuses -
413
+ * a root status page renders alone and an `_error` inside its layouts without their data, with the
414
+ * same status. `false` always renders them that way, and runs no layout loader for them.
415
+ */
416
+ readonly inLayouts?: boolean
387
417
  /**
388
418
  * Opt a **static** route (no `:param`/`*`) into build-time prerendering (SSG): `prerenderRoutes`
389
419
  * (from `@nifrajs/web/build`) renders it to a static `index.html` at build. The loader runs at build
@@ -621,6 +651,7 @@ export const BACKEND_ROUTE_EXPORTS: ReadonlySet<string> = new Set([
621
651
  "middleware",
622
652
  "gate",
623
653
  "shouldRevalidate",
654
+ "allowedOrigins",
624
655
  ])
625
656
 
626
657
  const routeError = (message: string): Error => new Error(`[nifra/web] ${message}`)
@@ -998,6 +1029,9 @@ const ancestorDirs = (file: string): string[] => {
998
1029
  * turns a path into a lazy module loader. Pure - no fs. Throws at boot (the loud-and-early
999
1030
  * RouteConfigError ethos) on duplicate patterns. `_layout`/`_404`/`_error`/`_loading`/`_middleware`
1000
1031
  * files are special; other `_`-prefixed files are ignored (private/colocated, never routed).
1032
+ * A page pairs with its `.backend.*` half only when both are in `files`, so a hand-written list that
1033
+ * omits the half serves the page without its loader and guards; `discoverRoutes` from
1034
+ * `@nifrajs/web/fs` scans a routes dir and never does.
1001
1035
  */
1002
1036
  export function buildManifest(
1003
1037
  files: readonly string[],
@@ -0,0 +1,64 @@
1
+ import { constants as FS } from "node:fs"
2
+ import { type FileHandle, open, readlink, realpath, stat } from "node:fs/promises"
3
+
4
+ // Darwin's open(2) flag refusing a symlink in ANY component; `O_NOFOLLOW` covers only the last one.
5
+ const O_NOFOLLOW_ANY = 0x20000000
6
+ // Deno's node:fs rejects flags it does not know with EINVAL; the first refusal turns the flag off.
7
+ let noFollowAny: boolean | undefined
8
+
9
+ /**
10
+ * Open a name `realpath` already resolved, refusing it when any component became a symlink since.
11
+ * A local writer inside the served tree can swap an intermediate directory for a link after the
12
+ * containment check; `O_NOFOLLOW` alone would follow it. Darwin refuses such a path in the kernel;
13
+ * Linux reads back the name the descriptor holds. Elsewhere, Windows included, no file API can open
14
+ * without following a middle link or name an open descriptor's path, so the check in
15
+ * {@link opensAt} is a platform limit: it narrows the race to repeated swaps but cannot close it.
16
+ */
17
+ export async function openResolved(realFile: string): Promise<FileHandle> {
18
+ // Read per open, not at load: this module can sit in a client bundle graph, where `process` is
19
+ // undefined.
20
+ if (noFollowAny ?? process.platform === "darwin") {
21
+ try {
22
+ return await open(realFile, FS.O_RDONLY | O_NOFOLLOW_ANY)
23
+ } catch (error) {
24
+ if (!(error instanceof Error && "code" in error && error.code === "EINVAL")) throw error
25
+ noFollowAny = false
26
+ }
27
+ }
28
+ const handle = await open(realFile, FS.O_RDONLY | FS.O_NOFOLLOW)
29
+ try {
30
+ if (await opensAt(handle, realFile)) return handle
31
+ } catch {}
32
+ await handle.close()
33
+ throw Object.assign(new Error(`${realFile} moved while it was opened`), { code: "ELOOP" })
34
+ }
35
+
36
+ /** Whether `handle` is the file at `realFile` with no symlink in its path. */
37
+ export async function opensAt(
38
+ handle: Pick<FileHandle, "fd" | "stat">,
39
+ realFile: string,
40
+ ): Promise<boolean> {
41
+ try {
42
+ return (await readlink(`/proc/self/fd/${handle.fd}`)) === realFile
43
+ } catch {
44
+ return sameFileAt(handle, realFile)
45
+ }
46
+ }
47
+
48
+ /**
49
+ * {@link opensAt} where no descriptor's path can be read. Exported for its test, which runs it on
50
+ * every host.
51
+ */
52
+ export async function sameFileAt(
53
+ handle: Pick<FileHandle, "stat">,
54
+ realFile: string,
55
+ ): Promise<boolean> {
56
+ // One step after another, so passing takes a swap back and a swap again between steps: the name
57
+ // resolves to itself, then to the opened file, then to itself again. Identities are compared as
58
+ // bigints because a Windows file index can exceed 2^53.
59
+ if ((await realpath(realFile)) !== realFile) return false
60
+ const opened = await handle.stat({ bigint: true })
61
+ const named = await stat(realFile, { bigint: true })
62
+ if (opened.dev !== named.dev || opened.ino !== named.ino) return false
63
+ return (await realpath(realFile)) === realFile
64
+ }
@@ -4,7 +4,7 @@
4
4
  * CSS→client-bundle wiring, the deterministic hash, or the optional-peer guard. Internal to
5
5
  * `@nifrajs/web` (imported by the sibling plugin modules); not a public subpath.
6
6
  */
7
- import { existsSync } from "node:fs"
7
+ import { existsSync, readFileSync } from "node:fs"
8
8
  import { dirname, join, relative } from "node:path"
9
9
  import { fileURLToPath, pathToFileURL } from "node:url"
10
10
  import type { BunPlugin } from "bun"
@@ -23,7 +23,7 @@ export type PluginBuilder = Parameters<BunPlugin["setup"]>[0]
23
23
  */
24
24
  export { rewriteSsrImports } from "../dev-ssr-graph.ts"
25
25
 
26
- const packageRootCache = new Map<string, string>()
26
+ const packageCache = new Map<string, PackageOf>()
27
27
 
28
28
  /**
29
29
  * Convert a plugin/runtime path into a path the host filesystem can open.
@@ -57,14 +57,31 @@ export function portablePath(path: string): string {
57
57
  return normalizeFilePath(path).replaceAll("\\", "/")
58
58
  }
59
59
 
60
- /** The nearest ancestor directory of `startDir` that holds a `package.json` (the file's package root),
61
- * cached. Falls back to `startDir` if none is found, so it never throws. */
62
- function packageRootOf(startDir: string): string {
63
- const cached = packageRootCache.get(startDir)
60
+ interface PackageOf {
61
+ readonly root: string
62
+ readonly name: string
63
+ }
64
+
65
+ /** The nearest ancestor directory of `startDir` that holds a `package.json` (the file's package root)
66
+ * and that package's `name` (`""` when it has none), cached. Falls back to `startDir` if none is found,
67
+ * so it never throws. */
68
+ function packageOf(startDir: string): PackageOf {
69
+ const cached = packageCache.get(startDir)
64
70
  if (cached !== undefined) return cached
65
71
  let dir = startDir
72
+ let name = ""
66
73
  for (;;) {
67
- if (existsSync(join(dir, "package.json"))) break
74
+ const manifest = join(dir, "package.json")
75
+ if (existsSync(manifest)) {
76
+ try {
77
+ const parsed: unknown = JSON.parse(readFileSync(manifest, "utf8"))
78
+ if (typeof parsed === "object" && parsed !== null && "name" in parsed)
79
+ name = typeof parsed.name === "string" ? parsed.name : ""
80
+ } catch {
81
+ // An unreadable manifest still marks the package root; the name is only a disambiguator.
82
+ }
83
+ break
84
+ }
68
85
  const parent = dirname(dir)
69
86
  if (parent === dir) {
70
87
  dir = startDir // reached the filesystem root with no package.json - anchor on the file's own dir
@@ -72,8 +89,9 @@ function packageRootOf(startDir: string): string {
72
89
  }
73
90
  dir = parent
74
91
  }
75
- packageRootCache.set(startDir, dir)
76
- return dir
92
+ const found = { root: dir, name }
93
+ packageCache.set(startDir, found)
94
+ return found
77
95
  }
78
96
 
79
97
  /**
@@ -84,11 +102,13 @@ function packageRootOf(startDir: string): string {
84
102
  * different machines or from different cwds - derive the SAME relative path for a given file, so their
85
103
  * scoped class maps always agree. (Hashing the absolute path would differ across CI/host; a
86
104
  * cwd-relative path would silently desync if the SSR server started from a non-project-root cwd.)
105
+ * The package's `name` leads the path, so `src/Card.vue` in two workspace packages hashes apart.
87
106
  */
88
107
  export function reproduciblePath(absolutePath: string): string {
89
108
  const filePath = normalizeFilePath(absolutePath)
90
- const root = packageRootOf(dirname(filePath))
91
- return relative(root, filePath).replaceAll("\\", "/")
109
+ const { root, name } = packageOf(dirname(filePath))
110
+ const path = relative(root, filePath).replaceAll("\\", "/")
111
+ return name === "" ? path : `${name}/${path}`
92
112
  }
93
113
 
94
114
  /**
@@ -46,6 +46,7 @@ import {
46
46
  formatServerOnlyLeak,
47
47
  } from "../build-plan.ts"
48
48
  import { isBareNodeBuiltin } from "../internal/node-builtins.ts"
49
+ import { installedManifest } from "../internal/package-entries.ts"
49
50
  import { privateEnvCheck } from "../internal/private-env.ts"
50
51
  import {
51
52
  emittedScanFiles,
@@ -58,6 +59,7 @@ import {
58
59
  scanForSecrets,
59
60
  textOf,
60
61
  } from "../internal/secret-scan.ts"
62
+ import { withoutQuery } from "../internal/without-query.ts"
61
63
  import {
62
64
  accountEmittedFiles,
63
65
  assetDenial,
@@ -75,7 +77,7 @@ import {
75
77
  type GraphModule,
76
78
  type RollupBundleLike,
77
79
  } from "../module-graph.ts"
78
- import { createZoneClassifier, type ZoneClassifier } from "../zones.ts"
80
+ import { createZoneClassifier, specifierPackage, type ZoneClassifier } from "../zones.ts"
79
81
 
80
82
  /**
81
83
  * The Rollup plugin-context slice this uses: `getModuleInfo` for a module's resolved imports, and `error`
@@ -160,9 +162,80 @@ export interface LeakGuardPlugin {
160
162
  * reached the browser.
161
163
  */
162
164
  leak?: string
165
+ buildStart(): void
166
+ /** Ordered first: the bundler's own resolver answers before a plugin listed after it is asked. */
167
+ readonly resolveId: {
168
+ readonly order: "pre"
169
+ handler(
170
+ this: ResolveContext,
171
+ source: string,
172
+ importer: string | undefined,
173
+ options?: Readonly<Record<string, unknown>>,
174
+ ): Promise<null>
175
+ }
163
176
  generateBundle(this: RollupPluginContext, options: unknown, bundle: RollupBundleLike): void
164
177
  }
165
178
 
179
+ /**
180
+ * Rollup keeps no import's written specifier, yet a package that gives each entry its own side is judged
181
+ * by the entry an import names, since two entries may share one file. This records the specifier of
182
+ * each import of such a package by the edge it resolved to, and puts it back on the finished graph.
183
+ */
184
+ function entrySpecifiers(appRoot: string, options: ServerZoneGuardOptions) {
185
+ // One edge may carry several specifiers: `pkg` and `pkg/react` can resolve to one shared file.
186
+ const written = new Map<string, Set<string>>()
187
+ let classifier: ZoneClassifier | undefined
188
+ const edge = (from: string, to: string): string =>
189
+ `${from.replaceAll("\\", "/")}\0${to.replaceAll("\\", "/")}`
190
+ return {
191
+ reset(): void {
192
+ written.clear()
193
+ classifier = undefined
194
+ },
195
+ async resolveId(
196
+ context: ResolveContext,
197
+ source: string,
198
+ importer: string | undefined,
199
+ resolveOptions: Readonly<Record<string, unknown>> | undefined,
200
+ ): Promise<null> {
201
+ const name = specifierPackage(source)
202
+ const from = importer === undefined ? undefined : withoutQuery(importer)
203
+ if (name === undefined || importer === undefined || from === undefined || !isAbsolute(from))
204
+ return null
205
+ const manifest = installedManifest(dirname(from), name)
206
+ if (manifest === undefined) return null
207
+ classifier ??= classifierFor(appRoot, options)
208
+ if (classifier.classify(manifest, source).entry === undefined) return null
209
+ const resolved = await context.resolve(source, importer, {
210
+ ...resolveOptions,
211
+ skipSelf: true,
212
+ })
213
+ if (resolved === null) return null
214
+ const key = edge(importer, resolved.id)
215
+ written.set(key, (written.get(key) ?? new Set<string>()).add(source))
216
+ return null
217
+ },
218
+ annotate(graph: ClientModuleGraph): ClientModuleGraph {
219
+ if (written.size === 0) return graph
220
+ const modules: Record<string, GraphModule> = {}
221
+ for (const [id, module] of Object.entries(graph.modules)) {
222
+ modules[id] = {
223
+ imports: module.imports.flatMap((im) => {
224
+ const originals =
225
+ im.original !== undefined || im.path === undefined
226
+ ? undefined
227
+ : written.get(edge(id, im.path))
228
+ return originals === undefined
229
+ ? [im]
230
+ : [...originals].map((original) => ({ ...im, original }))
231
+ }),
232
+ }
233
+ }
234
+ return { ...graph, modules }
235
+ },
236
+ }
237
+ }
238
+
166
239
  const CSS_FILE = /\.(?:css|scss|sass|less|styl)(?:$|\?)/
167
240
 
168
241
  /**
@@ -175,8 +248,16 @@ export function viteLeakGuard(options: LeakGuardOptions = {}): LeakGuardPlugin {
175
248
  const appRoot = resolve(options.appRoot ?? process.cwd())
176
249
  const root = resolve(options.root ?? appRoot)
177
250
  const outDir = resolve(root, options.outDir ?? "dist")
251
+ const specifiers = entrySpecifiers(appRoot, options)
178
252
  const plugin: LeakGuardPlugin = {
179
253
  name: "nifra:leak-guard",
254
+ buildStart: () => specifiers.reset(),
255
+ resolveId: {
256
+ order: "pre",
257
+ handler(source, importer, resolveOptions) {
258
+ return specifiers.resolveId(this, source, importer, resolveOptions)
259
+ },
260
+ },
180
261
  generateBundle(_options, bundle) {
181
262
  const classifier = classifierFor(appRoot, options)
182
263
  // Resolved import edges per module, straight from Rollup's graph. Dynamic imports count too: a
@@ -186,7 +267,9 @@ export function viteLeakGuard(options: LeakGuardOptions = {}): LeakGuardPlugin {
186
267
  if (info === null) return []
187
268
  return [...(info.importedIds ?? []), ...(info.dynamicallyImportedIds ?? [])]
188
269
  }
189
- const graph = completeGraph(fromRollupBundle(bundle, importsOf), this, importsOf)
270
+ const graph = specifiers.annotate(
271
+ completeGraph(fromRollupBundle(bundle, importsOf), this, importsOf),
272
+ )
190
273
  const sources = { classifier, sourceOf: rollupModuleSource }
191
274
  const privateEnv = privateEnvCheck(options.publicEnvPrefix ?? "PUBLIC_")
192
275
  const outputs = Object.values(bundle as Readonly<Record<string, RollupOutputLike>>)
@@ -251,6 +334,7 @@ function secretLeak(
251
334
  artifacts,
252
335
  env: options.secrets?.env ?? process.env,
253
336
  publicEnvPrefix: options.publicEnvPrefix ?? "PUBLIC_",
337
+ // biome-ignore lint/plugin/noConditionalEmptyObjectSpread: Explicitly configured secret-scan exemptions preserve their absence by default.
254
338
  ...(options.secrets?.exemptions ? { exemptions: options.secrets.exemptions } : {}),
255
339
  }),
256
340
  )
@@ -260,7 +344,7 @@ function secretLeak(
260
344
  export type ServerZoneGuardOptions = Pick<
261
345
  LeakGuardOptions,
262
346
  "appRoot" | "routesDir" | "generatedFiles" | "publicEnvPrefix"
263
- >
347
+ > & { readonly componentModules?: ReadonlySet<string> }
264
348
 
265
349
  /**
266
350
  * The server build's half of the zone rules, with the same checks and message as nifra's Bun server
@@ -270,8 +354,16 @@ export type ServerZoneGuardOptions = Pick<
270
354
  */
271
355
  export function viteServerZoneGuard(options: ServerZoneGuardOptions = {}): LeakGuardPlugin {
272
356
  const appRoot = resolve(options.appRoot ?? process.cwd())
357
+ const specifiers = entrySpecifiers(appRoot, options)
273
358
  const plugin: LeakGuardPlugin = {
274
359
  name: "nifra:server-zone-guard",
360
+ buildStart: () => specifiers.reset(),
361
+ resolveId: {
362
+ order: "pre",
363
+ handler(source, importer, resolveOptions) {
364
+ return specifiers.resolveId(this, source, importer, resolveOptions)
365
+ },
366
+ },
275
367
  generateBundle(_options, bundle) {
276
368
  const classifier = classifierFor(appRoot, options)
277
369
  const importsOf = (id: string): readonly string[] => {
@@ -279,12 +371,17 @@ export function viteServerZoneGuard(options: ServerZoneGuardOptions = {}): LeakG
279
371
  if (info === null) return []
280
372
  return [...(info.importedIds ?? []), ...(info.dynamicallyImportedIds ?? [])]
281
373
  }
282
- const graph = completeGraph(fromRollupBundle(bundle, importsOf), this, importsOf)
374
+ const graph = specifiers.annotate(
375
+ completeGraph(fromRollupBundle(bundle, importsOf), this, importsOf),
376
+ )
283
377
  const leak = formatServerGraphVerdict(
284
378
  verifyServerGraph(graph, {
285
379
  classifier,
286
380
  sourceOf: rollupModuleSource,
287
381
  privateEnv: privateEnvCheck(options.publicEnvPrefix ?? "PUBLIC_"),
382
+ ...(options.componentModules === undefined
383
+ ? {}
384
+ : { componentModules: options.componentModules }),
288
385
  }),
289
386
  )
290
387
  if (leak === undefined) return
@@ -461,7 +558,6 @@ export interface AssetUrlGuardPlugin {
461
558
  ): Promise<null>
462
559
  }
463
560
 
464
- const POSTFIX = /[?#].*$/s
465
561
  const URL_SCHEME = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i
466
562
 
467
563
  /**
@@ -503,7 +599,7 @@ export function viteAssetUrlGuard(options: ServerZoneGuardOptions = {}): AssetUr
503
599
  },
504
600
  async transform(code, id, transformOptions) {
505
601
  if (transformOptions?.ssr === true || !code.includes("import.meta.url")) return null
506
- const importer = id.replace(POSTFIX, "")
602
+ const importer = withoutQuery(id)
507
603
  if (!isAbsolute(importer) || importer.includes("/node_modules/") || CSS_FILE.test(importer))
508
604
  return null
509
605
  let program: unknown
@@ -549,11 +645,11 @@ async function assetUrlFiles(
549
645
  ): Promise<string[]> {
550
646
  const files = new Set<string>()
551
647
  const add = (path: string | undefined): void => {
552
- const file = path?.replace(POSTFIX, "")
648
+ const file = path === undefined ? undefined : withoutQuery(path)
553
649
  if (file === undefined || !isAbsolute(file) || file.includes("\0")) return
554
650
  if (statSync(file, { throwIfNoEntry: false })?.isFile() === true) files.add(file)
555
651
  }
556
- if (!url.startsWith("/")) add(resolve(dirname(importer), url.replace(POSTFIX, "")))
652
+ if (!url.startsWith("/")) add(resolve(dirname(importer), withoutQuery(url)))
557
653
  const sources = url.startsWith(".") || url.startsWith("/") ? [url] : [url, `./${url}`]
558
654
  for (const source of sources) {
559
655
  try {
@@ -10,10 +10,12 @@
10
10
  *
11
11
  * The SSR environment loads the same files legitimately and is never checked here.
12
12
  */
13
- import { existsSync, readFileSync, statSync } from "node:fs"
13
+ import { readFileSync, statSync } from "node:fs"
14
14
  import type { IncomingMessage, ServerResponse } from "node:http"
15
15
  import { dirname, isAbsolute, join, relative, resolve } from "node:path"
16
+ import { installedManifest } from "../internal/package-entries.ts"
16
17
  import { privateEnvDenial } from "../internal/private-env.ts"
18
+ import { withoutQuery } from "../internal/without-query.ts"
17
19
  import {
18
20
  browserDenial,
19
21
  type Classification,
@@ -78,7 +80,6 @@ export interface ViteZoneGuardPlugin {
78
80
  fileForUrl(url: string): string | undefined
79
81
  }
80
82
 
81
- const stripQuery = (id: string): string => id.replace(/[?#].*$/, "")
82
83
  /** Query forms that hand a module's source, or a URL to it, to the browser untransformed. */
83
84
  const RAW_QUERY = /(?:^|&)(?:raw|url|inline)(?:[&=]|$)/
84
85
 
@@ -136,7 +137,7 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
136
137
  const fileForUrl = (url: string): string | undefined => {
137
138
  let path: string
138
139
  try {
139
- path = decodeURIComponent(stripQuery(url))
140
+ path = decodeURIComponent(withoutQuery(url))
140
141
  } catch {
141
142
  return undefined
142
143
  }
@@ -187,16 +188,17 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
187
188
  return context.error(new Error(message))
188
189
  }
189
190
 
190
- /** A bare specifier's package, classified through its manifest so a workspace link is first-party. */
191
- const packageClassification = (name: string, importer: string): Classification => {
192
- let dir = dirname(importer)
193
- for (;;) {
194
- const manifest = join(dir, "node_modules", name, "package.json")
195
- if (existsSync(manifest)) return classifier.classify(manifest)
196
- const parent = dirname(dir)
197
- if (parent === dir) return { zone: "library", packageName: name }
198
- dir = parent
199
- }
191
+ /** A bare import's package, classified through its manifest so a workspace link is first-party,
192
+ * and by the entry it names when the package declares a side per entry. */
193
+ const packageClassification = (
194
+ name: string,
195
+ importer: string,
196
+ specifier: string,
197
+ ): Classification => {
198
+ const manifest = installedManifest(dirname(importer), name)
199
+ return manifest === undefined
200
+ ? { zone: "library", packageName: name }
201
+ : classifier.classify(manifest, specifier)
200
202
  }
201
203
 
202
204
  return {
@@ -219,10 +221,10 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
219
221
  async resolveId(source, importer, resolveOptions) {
220
222
  if (importer === undefined || resolveOptions?.scan === true) return null
221
223
  if (!isClient(this, resolveOptions?.ssr)) return null
222
- const importerFile = stripQuery(importer)
224
+ const importerFile = withoutQuery(importer)
223
225
  if (!isAbsolute(importerFile)) return null
224
226
  if (source.startsWith(".")) {
225
- const target = resolve(dirname(importerFile), stripQuery(source))
227
+ const target = resolve(dirname(importerFile), withoutQuery(source))
226
228
  if (isFile(target)) {
227
229
  const reason = browserDenial(classifier.classify(target))
228
230
  if (reason !== undefined) refuse(this, target, reason, importerFile)
@@ -231,21 +233,21 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
231
233
  }
232
234
  const name = specifierPackage(source)
233
235
  if (name !== undefined) {
234
- const reason = browserDenial(packageClassification(name, importerFile), source)
236
+ const reason = browserDenial(packageClassification(name, importerFile, source), source)
235
237
  if (reason !== undefined) refuse(this, source, reason, importerFile)
236
238
  }
237
239
  // An alias, a tsconfig path or a package export: what the specifier resolves to decides.
238
240
  const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true })
239
- const target = resolved === null || resolved.external ? undefined : stripQuery(resolved.id)
241
+ const target = resolved === null || resolved.external ? undefined : withoutQuery(resolved.id)
240
242
  if (target !== undefined && isAbsolute(target) && isFile(target)) {
241
- const reason = browserDenial(classifier.classify(target), source)
243
+ const reason = browserDenial(classifier.classify(target, source), source)
242
244
  if (reason !== undefined) refuse(this, target, reason, importerFile)
243
245
  }
244
246
  return null
245
247
  },
246
248
  transform(code, id, transformOptions) {
247
249
  if (!isClient(this, transformOptions?.ssr)) return null
248
- const file = stripQuery(id)
250
+ const file = withoutQuery(id)
249
251
  if (!isAbsolute(file) || !isFile(file)) return null
250
252
  // A fresh transform re-resolves every import, so an earlier refusal is stale until it recurs.
251
253
  refusals.delete(resolve(file))