@nifrajs/web 3.3.0 → 4.0.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 (407) hide show
  1. package/dist/backend-only.d.ts +15 -0
  2. package/dist/backend-only.d.ts.map +1 -0
  3. package/dist/backend-only.js +16 -0
  4. package/dist/backend-only.js.map +1 -0
  5. package/dist/boundary.d.ts +5 -0
  6. package/dist/boundary.d.ts.map +1 -1
  7. package/dist/boundary.js +11 -2
  8. package/dist/boundary.js.map +1 -1
  9. package/dist/build-plan.d.ts +35 -6
  10. package/dist/build-plan.d.ts.map +1 -1
  11. package/dist/build-plan.js +89 -35
  12. package/dist/build-plan.js.map +1 -1
  13. package/dist/build-vite.d.ts +18 -0
  14. package/dist/build-vite.d.ts.map +1 -1
  15. package/dist/build-vite.js +97 -17
  16. package/dist/build-vite.js.map +1 -1
  17. package/dist/build.d.ts +72 -23
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/build.js +411 -98
  20. package/dist/build.js.map +1 -1
  21. package/dist/bun-dev-entry.d.ts +1 -1
  22. package/dist/bun-dev-entry.js +1 -1
  23. package/dist/cdn.d.ts +74 -0
  24. package/dist/cdn.d.ts.map +1 -0
  25. package/dist/cdn.js +132 -0
  26. package/dist/cdn.js.map +1 -0
  27. package/dist/client.d.ts +21 -1
  28. package/dist/client.d.ts.map +1 -1
  29. package/dist/client.js +226 -15
  30. package/dist/client.js.map +1 -1
  31. package/dist/conformance.d.ts +4 -3
  32. package/dist/conformance.d.ts.map +1 -1
  33. package/dist/conformance.js +12 -3
  34. package/dist/conformance.js.map +1 -1
  35. package/dist/csp.d.ts +93 -0
  36. package/dist/csp.d.ts.map +1 -0
  37. package/dist/csp.js +122 -0
  38. package/dist/csp.js.map +1 -0
  39. package/dist/css-contract.d.ts +21 -0
  40. package/dist/css-contract.d.ts.map +1 -0
  41. package/dist/css-contract.js +28 -0
  42. package/dist/css-contract.js.map +1 -0
  43. package/dist/dev-client.d.ts +76 -0
  44. package/dist/dev-client.d.ts.map +1 -0
  45. package/dist/dev-client.js +280 -0
  46. package/dist/dev-client.js.map +1 -0
  47. package/dist/dev-diagnostics.d.ts +26 -2
  48. package/dist/dev-diagnostics.d.ts.map +1 -1
  49. package/dist/dev-diagnostics.js +38 -7
  50. package/dist/dev-diagnostics.js.map +1 -1
  51. package/dist/dev-error.d.ts +4 -13
  52. package/dist/dev-error.d.ts.map +1 -1
  53. package/dist/dev-error.js +46 -4
  54. package/dist/dev-error.js.map +1 -1
  55. package/dist/dev-feed.d.ts +298 -0
  56. package/dist/dev-feed.d.ts.map +1 -0
  57. package/dist/dev-feed.js +797 -0
  58. package/dist/dev-feed.js.map +1 -0
  59. package/dist/dev-indicator.d.ts +38 -0
  60. package/dist/dev-indicator.d.ts.map +1 -0
  61. package/dist/dev-indicator.js +93 -0
  62. package/dist/dev-indicator.js.map +1 -0
  63. package/dist/dev-session.d.ts +77 -0
  64. package/dist/dev-session.d.ts.map +1 -0
  65. package/dist/dev-session.js +580 -0
  66. package/dist/dev-session.js.map +1 -0
  67. package/dist/dev-sourcemap.d.ts +27 -0
  68. package/dist/dev-sourcemap.d.ts.map +1 -0
  69. package/dist/dev-sourcemap.js +235 -0
  70. package/dist/dev-sourcemap.js.map +1 -0
  71. package/dist/dev-ssr-graph.d.ts.map +1 -1
  72. package/dist/dev-ssr-graph.js +13 -5
  73. package/dist/dev-ssr-graph.js.map +1 -1
  74. package/dist/dev.d.ts +15 -2
  75. package/dist/dev.d.ts.map +1 -1
  76. package/dist/dev.js +69 -44
  77. package/dist/dev.js.map +1 -1
  78. package/dist/diagnostic-catalog.d.ts +35 -0
  79. package/dist/diagnostic-catalog.d.ts.map +1 -0
  80. package/dist/diagnostic-catalog.js +203 -0
  81. package/dist/diagnostic-catalog.js.map +1 -0
  82. package/dist/diagnostic-prompt.d.ts +49 -0
  83. package/dist/diagnostic-prompt.d.ts.map +1 -0
  84. package/dist/diagnostic-prompt.js +303 -0
  85. package/dist/diagnostic-prompt.js.map +1 -0
  86. package/dist/diagnostic.d.ts +17 -23
  87. package/dist/diagnostic.d.ts.map +1 -1
  88. package/dist/diagnostic.js +31 -39
  89. package/dist/diagnostic.js.map +1 -1
  90. package/dist/draft.d.ts.map +1 -1
  91. package/dist/draft.js +4 -1
  92. package/dist/draft.js.map +1 -1
  93. package/dist/fn.d.ts +5 -0
  94. package/dist/fn.d.ts.map +1 -1
  95. package/dist/fn.js +13 -3
  96. package/dist/fn.js.map +1 -1
  97. package/dist/fs.d.ts.map +1 -1
  98. package/dist/fs.js +4 -2
  99. package/dist/fs.js.map +1 -1
  100. package/dist/index.d.ts +15 -15
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +13 -12
  103. package/dist/index.js.map +1 -1
  104. package/dist/internal/cdn/cloudflare.d.ts +48 -0
  105. package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
  106. package/dist/internal/cdn/cloudflare.js +94 -0
  107. package/dist/internal/cdn/cloudflare.js.map +1 -0
  108. package/dist/internal/cdn/core.d.ts +91 -0
  109. package/dist/internal/cdn/core.d.ts.map +1 -0
  110. package/dist/internal/cdn/core.js +220 -0
  111. package/dist/internal/cdn/core.js.map +1 -0
  112. package/dist/internal/cdn/fastly.d.ts +17 -0
  113. package/dist/internal/cdn/fastly.d.ts.map +1 -0
  114. package/dist/internal/cdn/fastly.js +43 -0
  115. package/dist/internal/cdn/fastly.js.map +1 -0
  116. package/dist/internal/cdn/vercel.d.ts +31 -0
  117. package/dist/internal/cdn/vercel.d.ts.map +1 -0
  118. package/dist/internal/cdn/vercel.js +56 -0
  119. package/dist/internal/cdn/vercel.js.map +1 -0
  120. package/dist/internal/code-unit-order.d.ts +4 -0
  121. package/dist/internal/code-unit-order.d.ts.map +1 -0
  122. package/dist/internal/code-unit-order.js +6 -0
  123. package/dist/internal/code-unit-order.js.map +1 -0
  124. package/dist/internal/codegen.d.ts +3 -0
  125. package/dist/internal/codegen.d.ts.map +1 -1
  126. package/dist/internal/codegen.js +151 -30
  127. package/dist/internal/codegen.js.map +1 -1
  128. package/dist/internal/dev-csp.d.ts +24 -0
  129. package/dist/internal/dev-csp.d.ts.map +1 -0
  130. package/dist/internal/dev-csp.js +160 -0
  131. package/dist/internal/dev-csp.js.map +1 -0
  132. package/dist/internal/dev-reserved.d.ts +25 -0
  133. package/dist/internal/dev-reserved.d.ts.map +1 -0
  134. package/dist/internal/dev-reserved.js +39 -0
  135. package/dist/internal/dev-reserved.js.map +1 -0
  136. package/dist/internal/html-spans.d.ts +10 -0
  137. package/dist/internal/html-spans.d.ts.map +1 -0
  138. package/dist/internal/html-spans.js +46 -0
  139. package/dist/internal/html-spans.js.map +1 -0
  140. package/dist/internal/loading-runtime.d.ts +44 -0
  141. package/dist/internal/loading-runtime.d.ts.map +1 -0
  142. package/dist/internal/loading-runtime.js +206 -0
  143. package/dist/internal/loading-runtime.js.map +1 -0
  144. package/dist/internal/matches-runtime.d.ts +8 -0
  145. package/dist/internal/matches-runtime.d.ts.map +1 -0
  146. package/dist/internal/matches-runtime.js +61 -0
  147. package/dist/internal/matches-runtime.js.map +1 -0
  148. package/dist/internal/mount-shadow.d.ts +36 -0
  149. package/dist/internal/mount-shadow.d.ts.map +1 -0
  150. package/dist/internal/mount-shadow.js +65 -0
  151. package/dist/internal/mount-shadow.js.map +1 -0
  152. package/dist/internal/node-builtins.d.ts +3 -0
  153. package/dist/internal/node-builtins.d.ts.map +1 -0
  154. package/dist/internal/node-builtins.js +79 -0
  155. package/dist/internal/node-builtins.js.map +1 -0
  156. package/dist/internal/output-guard.d.ts +65 -0
  157. package/dist/internal/output-guard.d.ts.map +1 -0
  158. package/dist/internal/output-guard.js +630 -0
  159. package/dist/internal/output-guard.js.map +1 -0
  160. package/dist/internal/page-execution.d.ts +69 -0
  161. package/dist/internal/page-execution.d.ts.map +1 -0
  162. package/dist/internal/page-execution.js +1021 -0
  163. package/dist/internal/page-execution.js.map +1 -0
  164. package/dist/internal/parity.d.ts +21 -0
  165. package/dist/internal/parity.d.ts.map +1 -1
  166. package/dist/internal/parity.js +104 -22
  167. package/dist/internal/parity.js.map +1 -1
  168. package/dist/internal/private-env.d.ts +12 -0
  169. package/dist/internal/private-env.d.ts.map +1 -0
  170. package/dist/internal/private-env.js +335 -0
  171. package/dist/internal/private-env.js.map +1 -0
  172. package/dist/internal/public-url.d.ts +3 -0
  173. package/dist/internal/public-url.d.ts.map +1 -0
  174. package/dist/internal/public-url.js +18 -0
  175. package/dist/internal/public-url.js.map +1 -0
  176. package/dist/internal/render-document.d.ts +37 -21
  177. package/dist/internal/render-document.d.ts.map +1 -1
  178. package/dist/internal/render-document.js +104 -65
  179. package/dist/internal/render-document.js.map +1 -1
  180. package/dist/internal/request-url.d.ts +3 -0
  181. package/dist/internal/request-url.d.ts.map +1 -0
  182. package/dist/internal/request-url.js +14 -0
  183. package/dist/internal/request-url.js.map +1 -0
  184. package/dist/internal/response-controls.d.ts +75 -0
  185. package/dist/internal/response-controls.d.ts.map +1 -0
  186. package/dist/internal/response-controls.js +256 -0
  187. package/dist/internal/response-controls.js.map +1 -0
  188. package/dist/internal/runtime-contract.d.ts +11 -16
  189. package/dist/internal/runtime-contract.d.ts.map +1 -1
  190. package/dist/internal/runtime-contract.js.map +1 -1
  191. package/dist/internal/secret-scan.d.ts +104 -0
  192. package/dist/internal/secret-scan.d.ts.map +1 -0
  193. package/dist/internal/secret-scan.js +485 -0
  194. package/dist/internal/secret-scan.js.map +1 -0
  195. package/dist/internal/server-boundary.d.ts +6 -29
  196. package/dist/internal/server-boundary.d.ts.map +1 -1
  197. package/dist/internal/server-boundary.js +6 -66
  198. package/dist/internal/server-boundary.js.map +1 -1
  199. package/dist/internal/source-map.d.ts +47 -0
  200. package/dist/internal/source-map.d.ts.map +1 -0
  201. package/dist/internal/source-map.js +225 -0
  202. package/dist/internal/source-map.js.map +1 -0
  203. package/dist/internal/target-compat.d.ts +27 -0
  204. package/dist/internal/target-compat.d.ts.map +1 -0
  205. package/dist/internal/target-compat.js +77 -0
  206. package/dist/internal/target-compat.js.map +1 -0
  207. package/dist/internal/timing-safe-equal.d.ts +6 -0
  208. package/dist/internal/timing-safe-equal.d.ts.map +1 -1
  209. package/dist/internal/timing-safe-equal.js +10 -0
  210. package/dist/internal/timing-safe-equal.js.map +1 -1
  211. package/dist/internal/vitals-runtime.d.ts +18 -0
  212. package/dist/internal/vitals-runtime.d.ts.map +1 -0
  213. package/dist/internal/vitals-runtime.js +38 -0
  214. package/dist/internal/vitals-runtime.js.map +1 -0
  215. package/dist/internal/web-app.d.ts +56 -8
  216. package/dist/internal/web-app.d.ts.map +1 -1
  217. package/dist/internal/web-app.js +215 -772
  218. package/dist/internal/web-app.js.map +1 -1
  219. package/dist/internal/zone-graph.d.ts +89 -0
  220. package/dist/internal/zone-graph.d.ts.map +1 -0
  221. package/dist/internal/zone-graph.js +492 -0
  222. package/dist/internal/zone-graph.js.map +1 -0
  223. package/dist/isr.d.ts +83 -6
  224. package/dist/isr.d.ts.map +1 -1
  225. package/dist/isr.js +301 -39
  226. package/dist/isr.js.map +1 -1
  227. package/dist/llms-txt.d.ts +3 -0
  228. package/dist/llms-txt.d.ts.map +1 -1
  229. package/dist/llms-txt.js +37 -9
  230. package/dist/llms-txt.js.map +1 -1
  231. package/dist/manifest.d.ts +221 -14
  232. package/dist/manifest.d.ts.map +1 -1
  233. package/dist/manifest.js +386 -9
  234. package/dist/manifest.js.map +1 -1
  235. package/dist/module-graph.d.ts +26 -5
  236. package/dist/module-graph.d.ts.map +1 -1
  237. package/dist/module-graph.js +17 -7
  238. package/dist/module-graph.js.map +1 -1
  239. package/dist/navigation.d.ts +17 -3
  240. package/dist/navigation.d.ts.map +1 -1
  241. package/dist/navigation.js +11 -5
  242. package/dist/navigation.js.map +1 -1
  243. package/dist/nonce.d.ts +47 -0
  244. package/dist/nonce.d.ts.map +1 -0
  245. package/dist/nonce.js +63 -0
  246. package/dist/nonce.js.map +1 -0
  247. package/dist/plugins/css-modules.d.ts.map +1 -1
  248. package/dist/plugins/css-modules.js +4 -3
  249. package/dist/plugins/css-modules.js.map +1 -1
  250. package/dist/plugins/kit.d.ts +9 -0
  251. package/dist/plugins/kit.d.ts.map +1 -1
  252. package/dist/plugins/kit.js +18 -0
  253. package/dist/plugins/kit.js.map +1 -1
  254. package/dist/plugins/svg.d.ts +22 -3
  255. package/dist/plugins/svg.d.ts.map +1 -1
  256. package/dist/plugins/svg.js +112 -1
  257. package/dist/plugins/svg.js.map +1 -1
  258. package/dist/plugins/vite-leak-guard.d.ts +107 -3
  259. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  260. package/dist/plugins/vite-leak-guard.js +378 -17
  261. package/dist/plugins/vite-leak-guard.js.map +1 -1
  262. package/dist/plugins/vite-zone-guard.d.ts +61 -0
  263. package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
  264. package/dist/plugins/vite-zone-guard.js +208 -0
  265. package/dist/plugins/vite-zone-guard.js.map +1 -0
  266. package/dist/plugins/zone-guard.d.ts +27 -0
  267. package/dist/plugins/zone-guard.d.ts.map +1 -0
  268. package/dist/plugins/zone-guard.js +72 -0
  269. package/dist/plugins/zone-guard.js.map +1 -0
  270. package/dist/prerender.d.ts +10 -0
  271. package/dist/prerender.d.ts.map +1 -1
  272. package/dist/prerender.js +35 -3
  273. package/dist/prerender.js.map +1 -1
  274. package/dist/public-dir.d.ts +9 -0
  275. package/dist/public-dir.d.ts.map +1 -1
  276. package/dist/public-dir.js +131 -48
  277. package/dist/public-dir.js.map +1 -1
  278. package/dist/pwa-manifest.d.ts +68 -0
  279. package/dist/pwa-manifest.d.ts.map +1 -0
  280. package/dist/pwa-manifest.js +178 -0
  281. package/dist/pwa-manifest.js.map +1 -0
  282. package/dist/query.js +4 -4
  283. package/dist/query.js.map +1 -1
  284. package/dist/render-seam.d.ts +42 -2
  285. package/dist/render-seam.d.ts.map +1 -1
  286. package/dist/render-seam.js +6 -0
  287. package/dist/render-seam.js.map +1 -1
  288. package/dist/route-manifest.d.ts +3 -0
  289. package/dist/route-manifest.d.ts.map +1 -1
  290. package/dist/route-manifest.js +26 -3
  291. package/dist/route-manifest.js.map +1 -1
  292. package/dist/route-types.d.ts +24 -0
  293. package/dist/route-types.d.ts.map +1 -0
  294. package/dist/route-types.js +195 -0
  295. package/dist/route-types.js.map +1 -0
  296. package/dist/router.d.ts +28 -6
  297. package/dist/router.d.ts.map +1 -1
  298. package/dist/router.js +129 -40
  299. package/dist/router.js.map +1 -1
  300. package/dist/trusted-html.d.ts +34 -0
  301. package/dist/trusted-html.d.ts.map +1 -0
  302. package/dist/trusted-html.js +35 -0
  303. package/dist/trusted-html.js.map +1 -0
  304. package/dist/vitals.d.ts +33 -0
  305. package/dist/vitals.d.ts.map +1 -0
  306. package/dist/vitals.js +16 -0
  307. package/dist/vitals.js.map +1 -0
  308. package/dist/vite.d.ts +16 -1
  309. package/dist/vite.d.ts.map +1 -1
  310. package/dist/vite.js +187 -36
  311. package/dist/vite.js.map +1 -1
  312. package/dist/zones.d.ts +70 -0
  313. package/dist/zones.d.ts.map +1 -0
  314. package/dist/zones.js +365 -0
  315. package/dist/zones.js.map +1 -0
  316. package/package.json +67 -14
  317. package/src/backend-only.ts +16 -0
  318. package/src/boundary.ts +18 -5
  319. package/src/build-plan.ts +113 -36
  320. package/src/build-vite.ts +124 -15
  321. package/src/build.ts +535 -102
  322. package/src/bun-dev-entry.ts +1 -1
  323. package/src/cdn.ts +216 -0
  324. package/src/client.ts +251 -18
  325. package/src/conformance.ts +16 -3
  326. package/src/csp.ts +194 -0
  327. package/src/css-contract.ts +44 -0
  328. package/src/dev-client.ts +349 -0
  329. package/src/dev-diagnostics.ts +60 -7
  330. package/src/dev-error.ts +49 -5
  331. package/src/dev-feed.ts +1140 -0
  332. package/src/dev-indicator.ts +113 -0
  333. package/src/dev-session.ts +729 -0
  334. package/src/dev-sourcemap.ts +281 -0
  335. package/src/dev-ssr-graph.ts +16 -5
  336. package/src/dev.ts +86 -43
  337. package/src/diagnostic-catalog.ts +243 -0
  338. package/src/diagnostic-prompt.ts +355 -0
  339. package/src/diagnostic.ts +63 -66
  340. package/src/draft.ts +4 -1
  341. package/src/fn.ts +19 -3
  342. package/src/fs.ts +13 -2
  343. package/src/index.ts +57 -10
  344. package/src/internal/cdn/cloudflare.ts +146 -0
  345. package/src/internal/cdn/core.ts +316 -0
  346. package/src/internal/cdn/fastly.ts +62 -0
  347. package/src/internal/cdn/vercel.ts +95 -0
  348. package/src/internal/code-unit-order.ts +5 -0
  349. package/src/internal/codegen.ts +180 -29
  350. package/src/internal/dev-csp.ts +176 -0
  351. package/src/internal/dev-reserved.ts +52 -0
  352. package/src/internal/html-spans.ts +50 -0
  353. package/src/internal/loading-runtime.ts +244 -0
  354. package/src/internal/matches-runtime.ts +62 -0
  355. package/src/internal/mount-shadow.ts +88 -0
  356. package/src/internal/node-builtins.ts +79 -0
  357. package/src/internal/output-guard.ts +701 -0
  358. package/src/internal/page-execution.ts +1371 -0
  359. package/src/internal/parity.ts +150 -36
  360. package/src/internal/private-env.ts +334 -0
  361. package/src/internal/public-url.ts +22 -0
  362. package/src/internal/render-document.ts +153 -73
  363. package/src/internal/request-url.ts +14 -0
  364. package/src/internal/response-controls.ts +284 -0
  365. package/src/internal/runtime-contract.ts +10 -15
  366. package/src/internal/secret-scan.ts +623 -0
  367. package/src/internal/server-boundary.ts +6 -69
  368. package/src/internal/source-map.ts +256 -0
  369. package/src/internal/target-compat.ts +92 -0
  370. package/src/internal/timing-safe-equal.ts +13 -0
  371. package/src/internal/vitals-runtime.ts +53 -0
  372. package/src/internal/web-app.ts +290 -879
  373. package/src/internal/zone-graph.ts +593 -0
  374. package/src/isr.ts +366 -39
  375. package/src/llms-txt.ts +44 -8
  376. package/src/manifest.ts +671 -17
  377. package/src/module-graph.ts +53 -9
  378. package/src/navigation.ts +29 -6
  379. package/src/nonce.ts +104 -0
  380. package/src/plugins/css-modules.ts +5 -3
  381. package/src/plugins/kit.ts +24 -0
  382. package/src/plugins/svg.ts +135 -4
  383. package/src/plugins/vite-leak-guard.ts +529 -16
  384. package/src/plugins/vite-zone-guard.ts +263 -0
  385. package/src/plugins/zone-guard.ts +110 -0
  386. package/src/prerender.ts +49 -3
  387. package/src/public-dir.ts +135 -49
  388. package/src/pwa-manifest.ts +226 -0
  389. package/src/query.ts +4 -4
  390. package/src/render-seam.ts +46 -1
  391. package/src/route-manifest.ts +15 -3
  392. package/src/route-types.ts +264 -0
  393. package/src/router.ts +163 -63
  394. package/src/trusted-html.ts +44 -0
  395. package/src/vitals.ts +44 -0
  396. package/src/vite.ts +207 -36
  397. package/src/zones.ts +430 -0
  398. package/dist/plugins/vite-server-only.d.ts +0 -49
  399. package/dist/plugins/vite-server-only.d.ts.map +0 -1
  400. package/dist/plugins/vite-server-only.js +0 -57
  401. package/dist/plugins/vite-server-only.js.map +0 -1
  402. package/dist/server-only.d.ts +0 -21
  403. package/dist/server-only.d.ts.map +0 -1
  404. package/dist/server-only.js +0 -25
  405. package/dist/server-only.js.map +0 -1
  406. package/src/plugins/vite-server-only.ts +0 -75
  407. package/src/server-only.ts +0 -25
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Private environment reads in browser-reachable code. Browser code (a route's frontend half,
3
+ * `frontend/`, `shared/`) may read `NODE_ENV`, the bundler's own `import.meta.env` flags and variables
4
+ * named with the public prefix; any other `process.env`, `import.meta.env`, `Bun.env` or `Deno.env`
5
+ * read is a secret on its way to a browser, or a value that is silently `undefined` there.
6
+ *
7
+ * Detection works on tokens, not text, so a string or comment that mentions `process.env.X` is not a
8
+ * read. TypeScript and JSX go through Bun's transpiler first: JSX prose becomes string literals and type
9
+ * positions disappear. SFC and MDX files contribute only their code: scripts, markup expressions and
10
+ * ESM lines.
11
+ */
12
+ import { readFileSync } from "node:fs"
13
+ import { delimitedSpans, withoutComments, withoutMatches } from "./html-spans.ts"
14
+
15
+ const IMPORT_META_FLAGS = new Set(["MODE", "DEV", "PROD", "SSR", "BASE_URL"])
16
+
17
+ /** Words after which a `/` starts a regular expression rather than a division. */
18
+ const BEFORE_EXPRESSION = new Set([
19
+ "return",
20
+ "typeof",
21
+ "case",
22
+ "do",
23
+ "else",
24
+ "in",
25
+ "of",
26
+ "new",
27
+ "delete",
28
+ "void",
29
+ "throw",
30
+ "yield",
31
+ "await",
32
+ "instanceof",
33
+ ])
34
+
35
+ type Token =
36
+ | { readonly kind: "id" | "punct" | "string"; readonly value: string }
37
+ | { readonly kind: "other" }
38
+
39
+ /** JavaScript tokens, with strings, template text, comments and regular expressions skipped. */
40
+ function tokenize(code: string): Token[] {
41
+ const tokens: Token[] = []
42
+ // One entry per open `${`: how many `{` are open inside it.
43
+ const templates: number[] = []
44
+ let i = 0
45
+ const readTemplate = (from: number): number => {
46
+ let j = from
47
+ while (j < code.length) {
48
+ const c = code[j]
49
+ if (c === "\\") j += 2
50
+ else if (c === "`") {
51
+ tokens.push({ kind: "other" })
52
+ return j + 1
53
+ } else if (c === "$" && code[j + 1] === "{") {
54
+ templates.push(0)
55
+ return j + 2
56
+ } else j++
57
+ }
58
+ return j
59
+ }
60
+ const regexAllowed = (): boolean => {
61
+ const prev = tokens.at(-1)
62
+ if (prev === undefined) return true
63
+ if (prev.kind === "id") return BEFORE_EXPRESSION.has(prev.value)
64
+ if (prev.kind === "punct") return prev.value !== ")" && prev.value !== "]" && prev.value !== "}"
65
+ return false
66
+ }
67
+ while (i < code.length) {
68
+ const c = code[i] as string
69
+ if (/\s/.test(c)) {
70
+ i++
71
+ } else if (c === "/" && code[i + 1] === "/") {
72
+ const end = code.indexOf("\n", i)
73
+ i = end === -1 ? code.length : end
74
+ } else if (c === "/" && code[i + 1] === "*") {
75
+ const end = code.indexOf("*/", i + 2)
76
+ i = end === -1 ? code.length : end + 2
77
+ } else if (c === "'" || c === '"') {
78
+ let j = i + 1
79
+ let value = ""
80
+ while (j < code.length && code[j] !== c && code[j] !== "\n") {
81
+ if (code[j] === "\\") {
82
+ value += code[j + 1] ?? ""
83
+ j += 2
84
+ } else value += code[j++]
85
+ }
86
+ tokens.push({ kind: "string", value })
87
+ i = j + 1
88
+ } else if (c === "`") {
89
+ i = readTemplate(i + 1)
90
+ } else if (c === "}" && templates.at(-1) === 0) {
91
+ templates.pop()
92
+ i = readTemplate(i + 1)
93
+ } else if (/[A-Za-z_$\u0080-￿]/.test(c)) {
94
+ let j = i + 1
95
+ while (j < code.length && /[\w$\u0080-￿]/.test(code[j] as string)) j++
96
+ tokens.push({ kind: "id", value: code.slice(i, j) })
97
+ i = j
98
+ } else if (/\d/.test(c) || (c === "." && /\d/.test(code[i + 1] ?? ""))) {
99
+ let j = i + 1
100
+ while (j < code.length && /[\w.]/.test(code[j] as string)) j++
101
+ tokens.push({ kind: "other" })
102
+ i = j
103
+ } else if (c === "/" && regexAllowed()) {
104
+ let j = i + 1
105
+ let inClass = false
106
+ while (j < code.length && code[j] !== "\n") {
107
+ const d = code[j]
108
+ if (d === "\\") j++
109
+ else if (d === "[") inClass = true
110
+ else if (d === "]") inClass = false
111
+ else if (d === "/" && !inClass) break
112
+ j++
113
+ }
114
+ j++
115
+ while (j < code.length && /[a-z]/i.test(code[j] as string)) j++
116
+ tokens.push({ kind: "other" })
117
+ i = j
118
+ } else if (c === "?" && code[i + 1] === "." && !/\d/.test(code[i + 2] ?? "")) {
119
+ // Optional chaining reads the same property: `process?.env?.X` is `process.env.X`.
120
+ tokens.push({ kind: "punct", value: "." })
121
+ i += 2
122
+ } else {
123
+ const open = templates.length - 1
124
+ if (c === "{" && open >= 0) templates[open] = (templates[open] as number) + 1
125
+ if (c === "}" && open >= 0) templates[open] = (templates[open] as number) - 1
126
+ tokens.push({ kind: "punct", value: c })
127
+ i++
128
+ }
129
+ }
130
+ return tokens
131
+ }
132
+
133
+ /** One environment read: what it looks like in source, and the variable it names, when it names one. */
134
+ interface EnvRead {
135
+ readonly expression: string
136
+ readonly name?: string
137
+ /** `import.meta.env`, whose bundler flags (`MODE`, `DEV`, ...) are public. */
138
+ readonly importMeta: boolean
139
+ }
140
+
141
+ function envReads(tokens: readonly Token[]): EnvRead[] {
142
+ const reads: EnvRead[] = []
143
+ const is = (at: number, kind: "id" | "punct", value: string): boolean => {
144
+ const token = tokens[at]
145
+ return token !== undefined && token.kind === kind && token.value === value
146
+ }
147
+ const stringAt = (at: number): string | undefined => {
148
+ const token = tokens[at]
149
+ return token?.kind === "string" ? token.value : undefined
150
+ }
151
+ const idAt = (at: number): string | undefined => {
152
+ const token = tokens[at]
153
+ return token?.kind === "id" ? token.value : undefined
154
+ }
155
+ /** A property read after an env object that ends at `at`: `.X` or `["X"]`. */
156
+ const member = (base: string, at: number, importMeta: boolean): EnvRead => {
157
+ const property = is(at, "punct", ".") ? idAt(at + 1) : undefined
158
+ if (property !== undefined)
159
+ return { expression: `${base}.${property}`, name: property, importMeta }
160
+ const key = is(at, "punct", "[") && is(at + 2, "punct", "]") ? stringAt(at + 1) : undefined
161
+ if (key !== undefined)
162
+ return { expression: `${base}[${JSON.stringify(key)}]`, name: key, importMeta }
163
+ return { expression: base, importMeta }
164
+ }
165
+ for (let i = 0; i < tokens.length; i++) {
166
+ const head = idAt(i)
167
+ if (
168
+ (head === "process" || head === "Bun") &&
169
+ is(i + 1, "punct", ".") &&
170
+ is(i + 2, "id", "env")
171
+ ) {
172
+ reads.push(member(`${head}.env`, i + 3, false))
173
+ } else if (
174
+ head === "import" &&
175
+ is(i + 1, "punct", ".") &&
176
+ is(i + 2, "id", "meta") &&
177
+ is(i + 3, "punct", ".") &&
178
+ is(i + 4, "id", "env")
179
+ ) {
180
+ reads.push(member("import.meta.env", i + 5, true))
181
+ } else if (head === "Deno" && is(i + 1, "punct", ".") && is(i + 2, "id", "env")) {
182
+ const method = is(i + 3, "punct", ".") ? idAt(i + 4) : undefined
183
+ const name =
184
+ (method === "get" || method === "has") && is(i + 5, "punct", "(")
185
+ ? stringAt(i + 6)
186
+ : undefined
187
+ reads.push(
188
+ name !== undefined && is(i + 7, "punct", ")")
189
+ ? { expression: `Deno.env.${method}(${JSON.stringify(name)})`, name, importMeta: false }
190
+ : {
191
+ expression: method === undefined ? "Deno.env" : `Deno.env.${method}()`,
192
+ importMeta: false,
193
+ },
194
+ )
195
+ }
196
+ }
197
+ return reads
198
+ }
199
+
200
+ // A closing tag ends at any space, slash or `>` after its name, as a browser and the compilers read it.
201
+ const SCRIPT_ELEMENT = /<script\b[^>]*>([\s\S]*?)<\/script(?=[\s/>])[^>]*>/gi
202
+ const STYLE_ELEMENT = /<style\b[^>]*>[\s\S]*?<\/style(?=[\s/>])[^>]*>/gi
203
+
204
+ /** The code of a Svelte or Vue file: its scripts as modules, its markup expressions one by one. */
205
+ function sfcCode(
206
+ source: string,
207
+ vue: boolean,
208
+ ): { readonly scripts: string[]; readonly expressions: string[] } {
209
+ const scripts: string[] = []
210
+ const withoutScripts = withoutMatches(source, SCRIPT_ELEMENT, (match) => {
211
+ scripts.push(match[1] ?? "")
212
+ })
213
+ const markup = withoutComments(withoutMatches(withoutScripts, STYLE_ELEMENT))
214
+ const expressions: string[] = []
215
+ if (vue) {
216
+ expressions.push(...delimitedSpans(markup, "{{", "}}"))
217
+ for (const match of markup.matchAll(/\s(?::|@|v-|#)[\w:.[\]-]*\s*=\s*("([^"]*)"|'([^']*)')/g))
218
+ expressions.push(match[2] ?? match[3] ?? "")
219
+ } else {
220
+ expressions.push(...braceExpressions(markup))
221
+ }
222
+ return { scripts, expressions }
223
+ }
224
+
225
+ /** Every top-level `{...}` region of markup, braces balanced. */
226
+ function braceExpressions(markup: string): string[] {
227
+ const out: string[] = []
228
+ let depth = 0
229
+ let start = -1
230
+ for (let i = 0; i < markup.length; i++) {
231
+ if (markup[i] === "{") {
232
+ if (depth++ === 0) start = i + 1
233
+ } else if (markup[i] === "}" && depth > 0 && --depth === 0) {
234
+ out.push(markup.slice(start, i))
235
+ }
236
+ }
237
+ return out
238
+ }
239
+
240
+ /** The code of an MDX file: its import/export lines and `{...}` expressions, never its prose or code. */
241
+ function mdxCode(source: string): string[] {
242
+ const text = source
243
+ .replace(/^(```|~~~)[^\n]*\n[\s\S]*?^\1[^\n]*$/gm, "")
244
+ .replace(/`[^`\n]*`/g, "")
245
+ const esm = text.split("\n").filter((line) => /^(?:import|export)\b/.test(line))
246
+ return [...esm, ...braceExpressions(text)]
247
+ }
248
+
249
+ const SCRIPT = /\.(?:[cm]?[jt]sx?)$/
250
+
251
+ function transpile(code: string, loader: "ts" | "tsx" | "js" | "jsx"): string {
252
+ if (typeof Bun === "undefined") return code
253
+ try {
254
+ return new Bun.Transpiler({ loader }).transformSync(code)
255
+ } catch {
256
+ // Code the transpiler cannot parse fails the real build anyway; scan it as written.
257
+ return code
258
+ }
259
+ }
260
+
261
+ const loaderFor = (file: string): "ts" | "tsx" | "js" | "jsx" =>
262
+ /\.[cm]?tsx$/.test(file)
263
+ ? "tsx"
264
+ : /\.[cm]?ts$/.test(file)
265
+ ? "ts"
266
+ : /\.jsx$/.test(file)
267
+ ? "jsx"
268
+ : "js"
269
+
270
+ /**
271
+ * The private environment reads in one browser-reachable file, as written (`process.env.SECRET`).
272
+ * `publicPrefix` is the app's public-env prefix; `""` makes every variable but `NODE_ENV` private.
273
+ */
274
+ export function privateEnvReads(file: string, source: string, publicPrefix: string): string[] {
275
+ if (!/\benv\b/.test(source)) return []
276
+ const chunks: string[] = []
277
+ if (SCRIPT.test(file)) chunks.push(transpile(source, loaderFor(file)))
278
+ else if (/\.(?:svelte|vue)$/.test(file)) {
279
+ const { scripts, expressions } = sfcCode(source, file.endsWith(".vue"))
280
+ chunks.push(...scripts.map((script) => transpile(script, "ts")), ...expressions)
281
+ } else if (/\.mdx?$/.test(file)) chunks.push(...mdxCode(source))
282
+ else return []
283
+ const allowed = (read: EnvRead): boolean => {
284
+ if (read.name === undefined) return false
285
+ if (read.name === "NODE_ENV") return true
286
+ if (read.importMeta && IMPORT_META_FLAGS.has(read.name)) return true
287
+ return publicPrefix !== "" && read.name.startsWith(publicPrefix)
288
+ }
289
+ const found = new Set<string>()
290
+ for (const chunk of chunks) {
291
+ if (!/\benv\b/.test(chunk)) continue
292
+ for (const read of envReads(tokenize(chunk))) if (!allowed(read)) found.add(read.expression)
293
+ }
294
+ return [...found].sort()
295
+ }
296
+
297
+ /** The denial reason for a file's private environment reads. */
298
+ export function privateEnvReason(reads: readonly string[], publicPrefix: string): string {
299
+ const allowed = publicPrefix === "" ? "NODE_ENV" : `NODE_ENV and variables named ${publicPrefix}*`
300
+ return (
301
+ `it reads private environment ${reads.length === 1 ? "variable" : "variables"} ${reads.join(", ")}. ` +
302
+ `Browser code may read only ${allowed}; read the rest in a loader, an action or under backend/`
303
+ )
304
+ }
305
+
306
+ /** The denial reason for one file's source, or `undefined` when it reads no private variable. */
307
+ export function privateEnvDenial(
308
+ file: string,
309
+ source: string,
310
+ publicPrefix: string,
311
+ ): string | undefined {
312
+ const reads = privateEnvReads(file, source, publicPrefix)
313
+ return reads.length === 0 ? undefined : privateEnvReason(reads, publicPrefix)
314
+ }
315
+
316
+ /** A per-file check for a finished build, reading each file once. */
317
+ export function privateEnvCheck(publicPrefix: string): (file: string) => string | undefined {
318
+ const seen = new Map<string, string | undefined>()
319
+ return (file) => {
320
+ if (!seen.has(file)) {
321
+ let source: string | undefined
322
+ try {
323
+ source = readFileSync(file, "utf8")
324
+ } catch {
325
+ source = undefined
326
+ }
327
+ seen.set(
328
+ file,
329
+ source === undefined ? undefined : privateEnvDenial(file, source, publicPrefix),
330
+ )
331
+ }
332
+ return seen.get(file)
333
+ }
334
+ }
@@ -0,0 +1,22 @@
1
+ import { sep } from "node:path"
2
+
3
+ /**
4
+ * Percent-encode one path segment exactly the way a browser encodes it into a request URL.
5
+ *
6
+ * NOT `encodeURIComponent`. That escapes the sub-delimiters `, @ + = & ; $`, which a browser sends raw -
7
+ * so a file named `report,2026.csv` would be recorded as `/report%2C2026.csv` while the request arrives
8
+ * as `/report,2026.csv`, the allowlist lookup misses, and the file 404s in production only. `encodeURI`
9
+ * agrees with `URL.pathname` on every character except `?` and `#`, which terminate a path and so must
10
+ * be escaped explicitly here.
11
+ */
12
+ function encodePathSegment(segment: string): string {
13
+ return encodeURI(segment).replace(
14
+ /[?#]/g,
15
+ (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,
16
+ )
17
+ }
18
+
19
+ /** The URL path a browser requests for a file at `rel` (OS separators) under the public directory. */
20
+ export function publicUrlPath(rel: string): string {
21
+ return `/${rel.split(sep).map(encodePathSegment).join("/")}`
22
+ }