@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
@@ -6,7 +6,7 @@
6
6
  * manifest normalization so those callers cannot quietly grow separate rules.
7
7
  */
8
8
 
9
- import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs"
9
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs"
10
10
  import { lstat, realpath, stat } from "node:fs/promises"
11
11
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"
12
12
  import {
@@ -17,7 +17,10 @@ import {
17
17
  type SingleCopyRegistration,
18
18
  } from "@nifrajs/core/single-copy"
19
19
  import { discoverRoutes } from "../fs.ts"
20
+ import { codeUnitOrder } from "./code-unit-order.ts"
21
+ import { withoutComments } from "./html-spans.ts"
20
22
  import { isIdentitySensitivePackage } from "./identity-policy.ts"
23
+ import { publicUrlPath } from "./public-url.ts"
21
24
 
22
25
  const DEPENDENCY_FIELDS = [
23
26
  "dependencies",
@@ -39,6 +42,13 @@ export interface IdentityParityCopy {
39
42
  */
40
43
  readonly absolutePath?: string
41
44
  readonly importers: readonly string[]
45
+ /**
46
+ * Symlinks an importer reached this copy through that point outside the install that owns the
47
+ * importer, display-relative - a link another project planted (a sibling app's `node_modules`
48
+ * linked into a shared package). Removing it lets the importer resolve its own install's copy.
49
+ * Absent when no such link was crossed.
50
+ */
51
+ readonly links?: readonly string[]
42
52
  }
43
53
 
44
54
  export type IdentityParityCause = "version-skew" | "duplicate-path"
@@ -78,6 +88,11 @@ export interface IdentityParityFinding {
78
88
  * workspace, and at least one copy sits outside that subdirectory.
79
89
  */
80
90
  readonly scope?: string
91
+ /**
92
+ * Where a copy came from when an importer reached it through a symlink pointing outside its own
93
+ * install: each link and its target, and the fix (remove the link). Absent when no copy was.
94
+ */
95
+ readonly provenance?: string
81
96
  /**
82
97
  * The copies exist but the app declared this package single-copy, so the resolver collapses them
83
98
  * before anything loads. Reported, never fatal - see `SingleCopyCoverage`.
@@ -279,8 +294,10 @@ const hasStylesheetImport = (source: string): boolean => {
279
294
  }
280
295
  /** A single-file-component `<style>` block (Svelte/Vue). The bundler extracts these into the app
281
296
  * stylesheet even though no `import "...css"` statement exists, so the dev contract must count them
282
- * or a scoped-style component would look style-free next to a production manifest that carries css. */
283
- const SFC_STYLE = /<style[\s>]/i
297
+ * or a scoped-style component would look style-free next to a production manifest that carries css.
298
+ * A `<style>` inside markup or an expression is not a block, which is why the match is anchored to a
299
+ * line start; one in an HTML comment is prose, so comments are removed before it runs. */
300
+ const SFC_STYLE = /^<style[\s>]/im
284
301
  const isSingleFileComponent = (file: string): boolean =>
285
302
  file.endsWith(".svelte") || file.endsWith(".vue")
286
303
 
@@ -484,21 +501,53 @@ const workspaceImporters = async (
484
501
  return { importers, truncated }
485
502
  }
486
503
 
504
+ /**
505
+ * The symlink under `dir/node_modules` an import of `parts` crossed to reach `resolved`, when the copy
506
+ * sits outside `boundary` - the install that owns the importer. Package-manager store links (bun's
507
+ * `.bun/`, pnpm's `.pnpm/`, a workspace package linked to the root store) stay inside it and are not
508
+ * reported; a link another project planted, or a `bun link` into a global directory, is.
509
+ */
510
+ const foreignLink = (
511
+ dir: string,
512
+ parts: readonly string[],
513
+ boundary: string,
514
+ resolved: string,
515
+ ): string | undefined => {
516
+ if (pathInside(boundary, resolved)) return undefined
517
+ const realBoundary = realpathOrSelf(boundary)
518
+ if (pathInside(realBoundary, resolved)) return undefined
519
+ let segment = join(dir, "node_modules")
520
+ for (let index = 0; ; index++) {
521
+ const info = lstatSync(segment, { throwIfNoEntry: false })
522
+ if (info === undefined) return undefined
523
+ if (info.isSymbolicLink() && !pathInside(realBoundary, realpathOrSelf(segment))) return segment
524
+ const next = parts[index]
525
+ if (next === undefined) return undefined
526
+ segment = join(segment, next)
527
+ }
528
+ }
529
+
487
530
  export const resolvedInstalledCopy = async (
488
531
  importer: string,
489
532
  boundary: string,
490
533
  name: string,
491
- ): Promise<{ readonly path: string; readonly version: string } | undefined> => {
534
+ ): Promise<
535
+ { readonly path: string; readonly version: string; readonly link?: string } | undefined
536
+ > => {
492
537
  const parts = name.split("/")
493
538
  for (let dir = importer; ; dir = dirname(dir)) {
494
539
  const packageDir = join(dir, "node_modules", ...parts)
495
540
  const meta = await readJson(join(packageDir, "package.json"))
496
541
  if (meta !== undefined) {
497
542
  try {
543
+ const path = await realpath(packageDir)
544
+ // Boundary "" (doctor's stale-dist scan, workspace-link) asks only where the copy is.
545
+ const link = boundary === "" ? undefined : foreignLink(dir, parts, boundary, path)
498
546
  return {
499
- path: await realpath(packageDir),
547
+ path,
500
548
  version:
501
549
  typeof meta.version === "string" && meta.version.length > 0 ? meta.version : "unknown",
550
+ ...(link === undefined ? {} : { link }),
502
551
  }
503
552
  } catch {
504
553
  return undefined
@@ -604,7 +653,7 @@ const describeTopology = (
604
653
  const root = installRootOf(path)
605
654
  counts.set(root, (counts.get(root) ?? 0) + 1)
606
655
  }
607
- const roots = [...counts.entries()].sort(([a], [b]) => a.localeCompare(b))
656
+ const roots = [...counts.entries()].sort(([a], [b]) => codeUnitOrder(a, b))
608
657
  const breakdown = roots
609
658
  .map(([root, count]) => `${count} under ${displayPath(base, root)}`)
610
659
  .join(", ")
@@ -644,8 +693,22 @@ const describeScope = (
644
693
  return `${outside.length} of these copies ${outside.length === 1 ? "is" : "are"} outside ${here}, so this fails every build in the workspace, including apps that never import the package. The scan is anchored on the workspace on purpose: a copy reached through a workspace-linked dependency cannot be seen from ${here} alone, and scoping to it would miss the case this check exists for. Fix the copies where they live, or declare the package single-copy.`
645
694
  }
646
695
 
647
- const identityTargets = (pkg: Record<string, unknown>): readonly string[] =>
648
- dependencyNames(pkg).filter(isIdentitySensitivePackage).sort()
696
+ /**
697
+ * The dependencies of `pkg` the scan compares across copies: the built-in identity-sensitive set plus
698
+ * every package the app declared single-copy. A declaration is a claim that one copy loads, so a
699
+ * declared package gets the same scrutiny as react - otherwise a version skew in it would be enforced
700
+ * at load time (the resolver refuses to redirect it) and never reported here. The match is core's
701
+ * `matchesSingleCopyDeclaration`, the one the resolver uses, so check and enforcement cover one set.
702
+ */
703
+ const identityTargets = (
704
+ pkg: Record<string, unknown>,
705
+ declared: readonly string[],
706
+ ): readonly string[] =>
707
+ dependencyNames(pkg)
708
+ .filter(
709
+ (name) => isIdentitySensitivePackage(name) || matchesSingleCopyDeclaration(declared, name),
710
+ )
711
+ .sort()
649
712
 
650
713
  /** version-skew is a range problem: one reinstall from the root collapses it. */
651
714
  const VERSION_SKEW_REMEDIATION =
@@ -658,6 +721,14 @@ const duplicatePathRemediation = (name: string): string =>
658
721
  const identityRemediation = (cause: IdentityParityCause, name: string): string =>
659
722
  cause === "version-skew" ? VERSION_SKEW_REMEDIATION : duplicatePathRemediation(name)
660
723
 
724
+ /** Name each planted link, its target, and the fix, for a finding whose copies were reached that way. */
725
+ const describeProvenance = (copies: readonly IdentityParityCopy[]): string | undefined => {
726
+ const lines = copies.flatMap((copy) => (copy.links ?? []).map((link) => `${link} → ${copy.path}`))
727
+ if (lines.length === 0) return undefined
728
+ const one = lines.length === 1
729
+ return `reached through ${one ? "a symlink" : "symlinks"} that ${one ? "points" : "point"} outside the importer's install: ${lines.join(", ")}. Unless the importer declares ${one ? "it" : "them"} as a \`link:\` dependency, ${one ? "the link was" : "the links were"} planted by hand, by another project, or by \`bun link\` - remove ${one ? "it" : "them"} and reinstall there, so the importer resolves its own install's copy.`
730
+ }
731
+
661
732
  /** What is left to do about a duplicate the declaration already covers. */
662
733
  const deduplicatedRemediation = (registration: SingleCopyRegistration): string =>
663
734
  registration.run || registration.test
@@ -695,22 +766,31 @@ export async function collectIdentityParity(
695
766
  const scanRoot = workspace.root
696
767
  const scanPackage = workspace.package
697
768
  const { importers, truncated } = await workspaceImporters(scanRoot, scanPackage)
698
- const byPackage = new Map<string, Map<string, { version: string; importers: Set<string> }>>()
769
+ const byPackage = new Map<
770
+ string,
771
+ Map<string, { version: string; importers: Set<string>; links: Set<string> }>
772
+ >()
699
773
  const record = (
700
774
  name: string,
701
- copy: { readonly path: string; readonly version: string },
775
+ copy: { readonly path: string; readonly version: string; readonly link?: string },
702
776
  importer: string,
703
777
  ): void => {
704
778
  const copies = byPackage.get(name) ?? new Map()
705
- const entry = copies.get(copy.path) ?? { version: copy.version, importers: new Set<string>() }
779
+ const entry = copies.get(copy.path) ?? {
780
+ version: copy.version,
781
+ importers: new Set<string>(),
782
+ links: new Set<string>(),
783
+ }
706
784
  entry.importers.add(importer)
785
+ if (copy.link !== undefined) entry.links.add(displayPath(requestedRoot, copy.link))
707
786
  copies.set(copy.path, entry)
708
787
  byPackage.set(name, copies)
709
788
  }
710
789
 
790
+ const singleCopy = singleCopyCoverage(requestedRoot, scanRoot)
711
791
  const targets = new Set<string>()
712
792
  for (const importer of importers) {
713
- for (const name of identityTargets(importer.package)) {
793
+ for (const name of identityTargets(importer.package, singleCopy.declared)) {
714
794
  if (importer.package.name === name) continue
715
795
  targets.add(name)
716
796
  const copy = await resolvedInstalledCopy(importer.root, scanRoot, name)
@@ -732,7 +812,7 @@ export async function collectIdentityParity(
732
812
  const boundary = await linkedRepoBoundary(linkedRoot)
733
813
  const linkedTargets = new Set([
734
814
  ...targets,
735
- ...(linkedPackage === undefined ? [] : identityTargets(linkedPackage)),
815
+ ...(linkedPackage === undefined ? [] : identityTargets(linkedPackage, singleCopy.declared)),
736
816
  ])
737
817
  for (const name of linkedTargets) {
738
818
  if (linkedPackage?.name === name) continue
@@ -741,21 +821,24 @@ export async function collectIdentityParity(
741
821
  }
742
822
  }
743
823
 
744
- const singleCopy = singleCopyCoverage(requestedRoot, scanRoot)
745
824
  const findings: IdentityParityFinding[] = []
746
825
  const deduplicated: IdentityParityFinding[] = []
747
- for (const [name, copies] of [...byPackage.entries()].sort(([a], [b]) => a.localeCompare(b))) {
826
+ for (const [name, copies] of [...byPackage.entries()].sort(([a], [b]) => codeUnitOrder(a, b))) {
748
827
  if (copies.size < 2) continue
749
828
  const absolutePaths = [...copies.keys()].sort()
750
829
  const resolvedCopies = [...copies.entries()]
751
- .sort(([a], [b]) => a.localeCompare(b))
752
- .map(([path, copy]) => ({
753
- version: copy.version,
754
- path: displayPath(requestedRoot, path),
755
- absolutePath: path,
756
- importers: [...copy.importers].sort(),
757
- }))
830
+ .sort(([a], [b]) => codeUnitOrder(a, b))
831
+ .map(
832
+ ([path, copy]): IdentityParityCopy => ({
833
+ version: copy.version,
834
+ path: displayPath(requestedRoot, path),
835
+ absolutePath: path,
836
+ importers: [...copy.importers].sort(),
837
+ ...(copy.links.size === 0 ? {} : { links: [...copy.links].sort() }),
838
+ }),
839
+ )
758
840
  const topology = describeTopology(requestedRoot, scanRoot, absolutePaths)
841
+ const provenance = describeProvenance(resolvedCopies)
759
842
  const scope = describeScope(requestedRoot, scanRoot, absolutePaths)
760
843
  const versions = [...new Set(resolvedCopies.map((copy) => copy.version))].sort()
761
844
  const cause: IdentityParityCause = versions.length > 1 ? "version-skew" : "duplicate-path"
@@ -780,6 +863,7 @@ export async function collectIdentityParity(
780
863
  : identityRemediation(cause, name),
781
864
  ...(topology === undefined ? {} : { topology }),
782
865
  ...(scope === undefined ? {} : { scope }),
866
+ ...(provenance === undefined ? {} : { provenance }),
783
867
  deduplicated: covered,
784
868
  }
785
869
  ;(covered ? deduplicated : findings).push(finding)
@@ -804,7 +888,8 @@ export function formatIdentityParityFindings(findings: readonly IdentityParityFi
804
888
  .map((copy) => copy.path)
805
889
  .join(", ")}` +
806
890
  (finding.topology === undefined ? "" : `\n topology: ${finding.topology}`) +
807
- (finding.scope === undefined ? "" : `\n scope: ${finding.scope}`),
891
+ (finding.scope === undefined ? "" : `\n scope: ${finding.scope}`) +
892
+ (finding.provenance === undefined ? "" : `\n links: ${finding.provenance}`),
808
893
  )
809
894
  .join("\n")
810
895
  }
@@ -904,15 +989,21 @@ export function logicalStaticAssets(manifest: BuildManifestLike): readonly strin
904
989
  return [...logical].sort()
905
990
  }
906
991
 
992
+ /**
993
+ * The one order parity puts route ids in, on both sides. Code-unit order, not `localeCompare`: the
994
+ * two disagree on `_` against `[` (`[lang]` sorts before `_404` by code unit, after it by locale), so
995
+ * a dev side sorted one way and a build side sorted the other failed the equality check for every app
996
+ * with a `_404` and a dynamic route. Code-unit order is also locale-independent, so the verdict cannot
997
+ * change with the machine running the build.
998
+ */
999
+ export const compareRouteIds = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0)
1000
+
907
1001
  export function normalizeBuildManifest(manifest: BuildManifestLike): ParityManifest {
1002
+ const routes = Object.entries(manifest.routes).sort(([a], [b]) => compareRouteIds(a, b))
908
1003
  return {
909
1004
  moduleGraph: {
910
- routes: Object.keys(manifest.routes).sort(),
911
- routeChunks: Object.fromEntries(
912
- Object.entries(manifest.routes)
913
- .sort(([a], [b]) => a.localeCompare(b))
914
- .map(([route, chunks]) => [route, chunks.length]),
915
- ),
1005
+ routes: routes.map(([route]) => route),
1006
+ routeChunks: Object.fromEntries(routes.map(([route, chunks]) => [route, chunks.length])),
916
1007
  // Allowlist, not denylist: the dev/prod module-graph contract is the JavaScript module graph
917
1008
  // only. `js:entry` and `js:route:<id>:<n>` are the roles dev can reconstruct from source. Which
918
1009
  // *non-JS* files a bundler emits (svg, woff2, an extracted stylesheet) is an output detail, not
@@ -928,7 +1019,7 @@ export function normalizeBuildManifest(manifest: BuildManifestLike): ParityManif
928
1019
 
929
1020
  export function createDevelopmentParityManifest(input: DevelopmentParityInput): ParityManifest {
930
1021
  const routes = Object.fromEntries(
931
- Object.entries(input.routes).sort(([a], [b]) => a.localeCompare(b)),
1022
+ Object.entries(input.routes).sort(([a], [b]) => compareRouteIds(a, b)),
932
1023
  )
933
1024
  const publicFiles = [...input.publicFiles].sort()
934
1025
  return {
@@ -951,10 +1042,11 @@ const sourceFilesUnder = (root: string): readonly string[] => {
951
1042
  const files: string[] = []
952
1043
  const walk = (current: string): void => {
953
1044
  for (const entry of readdirSync(current, { withFileTypes: true })) {
1045
+ // Dot-directories hold tool output (.wrangler, .vercel, .svelte-kit), whose bundles quote css
1046
+ // imports the app never makes.
954
1047
  if (
955
- ["node_modules", "dist", "dist-node", "build", ".git", ".nifra", "coverage"].includes(
956
- entry.name,
957
- )
1048
+ entry.name.startsWith(".") ||
1049
+ ["node_modules", "dist", "dist-node", "build", "coverage"].includes(entry.name)
958
1050
  )
959
1051
  continue
960
1052
  const path = join(current, entry.name)
@@ -974,7 +1066,7 @@ const publicFilesUnder = (publicDir: string | false | undefined): readonly strin
974
1066
  for (const entry of readdirSync(current, { withFileTypes: true })) {
975
1067
  const path = join(current, entry.name)
976
1068
  if (entry.isDirectory()) walk(path)
977
- else if (entry.isFile()) files.push(`/${relative(root, path).split(sep).join("/")}`)
1069
+ else if (entry.isFile()) files.push(publicUrlPath(relative(root, path)))
978
1070
  }
979
1071
  }
980
1072
  walk(root)
@@ -994,7 +1086,10 @@ export function collectDevelopmentParityInput(
994
1086
  const sourceRoot = dirname(resolve(routesDir))
995
1087
  const css = sourceFilesUnder(sourceRoot).some((file) => {
996
1088
  const content = readFileSync(file, "utf8")
997
- return hasStylesheetImport(content) || (isSingleFileComponent(file) && SFC_STYLE.test(content))
1089
+ return (
1090
+ hasStylesheetImport(content) ||
1091
+ (isSingleFileComponent(file) && SFC_STYLE.test(withoutComments(content)))
1092
+ )
998
1093
  })
999
1094
  ? ["css:present"]
1000
1095
  : []
@@ -1079,9 +1174,28 @@ function explainParityDifference(
1079
1174
  parts.push(
1080
1175
  `chunks only in development=${JSON.stringify(assets.onlyDevelopment)} only in production=${JSON.stringify(assets.onlyProduction)}`,
1081
1176
  )
1177
+ const counts = Object.keys(dev.routeChunks).filter(
1178
+ (route) =>
1179
+ Object.hasOwn(prod.routeChunks, route) &&
1180
+ dev.routeChunks[route] !== prod.routeChunks[route],
1181
+ )
1182
+ if (counts.length > 0)
1183
+ parts.push(
1184
+ `route chunk counts differ: ${counts.map((route) => `${route} development=${dev.routeChunks[route]} production=${prod.routeChunks[route]}`).join(", ")}`,
1185
+ )
1186
+ // Same members, same counts: what is left is order. Name the field, because an ordering
1187
+ // disagreement is a parity bug, not an app defect, and reads nothing like a missing chunk.
1188
+ if (parts.length === 0 && !equal(dev.routes, prod.routes))
1189
+ parts.push(
1190
+ `route order differs: development=${JSON.stringify(dev.routes)} production=${JSON.stringify(prod.routes)}`,
1191
+ )
1192
+ if (parts.length === 0 && !equal(dev.emittedAssets, prod.emittedAssets))
1193
+ parts.push(
1194
+ `chunk order differs: development=${JSON.stringify(dev.emittedAssets)} production=${JSON.stringify(prod.emittedAssets)}`,
1195
+ )
1082
1196
  if (parts.length === 0)
1083
1197
  parts.push(
1084
- `route chunk counts differ: development=${JSON.stringify(dev.routeChunks)} production=${JSON.stringify(prod.routeChunks)}`,
1198
+ `route chunk order differs: development=${JSON.stringify(dev.routeChunks)} production=${JSON.stringify(prod.routeChunks)}`,
1085
1199
  )
1086
1200
  return `module-graph: ${parts.join("; ")}`
1087
1201
  }