@nimbus-sh/worker 0.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 (408) hide show
  1. package/README.md +219 -0
  2. package/dist/_shared/bytes.d.ts +18 -0
  3. package/dist/_shared/bytes.d.ts.map +1 -0
  4. package/dist/_shared/bytes.js +17 -0
  5. package/dist/_shared/cache-stats.d.ts +146 -0
  6. package/dist/_shared/cache-stats.d.ts.map +1 -0
  7. package/dist/_shared/cache-stats.js +162 -0
  8. package/dist/_shared/exports-resolver.d.ts +76 -0
  9. package/dist/_shared/exports-resolver.d.ts.map +1 -0
  10. package/dist/_shared/exports-resolver.js +284 -0
  11. package/dist/_shared/install-phase.d.ts +47 -0
  12. package/dist/_shared/install-phase.d.ts.map +1 -0
  13. package/dist/_shared/install-phase.js +1 -0
  14. package/dist/_shared/real-node-imports.d.ts +38 -0
  15. package/dist/_shared/real-node-imports.d.ts.map +1 -0
  16. package/dist/_shared/real-node-imports.js +46 -0
  17. package/dist/_shared/retry.d.ts +93 -0
  18. package/dist/_shared/retry.d.ts.map +1 -0
  19. package/dist/_shared/retry.js +161 -0
  20. package/dist/_shared/session-id.d.ts +39 -0
  21. package/dist/_shared/session-id.d.ts.map +1 -0
  22. package/dist/_shared/session-id.js +127 -0
  23. package/dist/_shared/session-router.d.ts +104 -0
  24. package/dist/_shared/session-router.d.ts.map +1 -0
  25. package/dist/_shared/session-router.js +165 -0
  26. package/dist/_shared/w7-frame.d.ts +93 -0
  27. package/dist/_shared/w7-frame.d.ts.map +1 -0
  28. package/dist/_shared/w7-frame.js +402 -0
  29. package/dist/auth/index.d.ts +16 -0
  30. package/dist/auth/index.d.ts.map +1 -0
  31. package/dist/auth/index.js +15 -0
  32. package/dist/auth/middleware.d.ts +85 -0
  33. package/dist/auth/middleware.d.ts.map +1 -0
  34. package/dist/auth/middleware.js +150 -0
  35. package/dist/auth/token.d.ts +96 -0
  36. package/dist/auth/token.d.ts.map +1 -0
  37. package/dist/auth/token.js +226 -0
  38. package/dist/auth/types.d.ts +141 -0
  39. package/dist/auth/types.d.ts.map +1 -0
  40. package/dist/auth/types.js +114 -0
  41. package/dist/bindings/d1.d.ts +102 -0
  42. package/dist/bindings/d1.d.ts.map +1 -0
  43. package/dist/bindings/d1.js +549 -0
  44. package/dist/bindings/kv.d.ts +82 -0
  45. package/dist/bindings/kv.d.ts.map +1 -0
  46. package/dist/bindings/kv.js +290 -0
  47. package/dist/bindings/r2.d.ts +137 -0
  48. package/dist/bindings/r2.d.ts.map +1 -0
  49. package/dist/bindings/r2.js +379 -0
  50. package/dist/cirrus-npm-cjs.generated.d.ts +17 -0
  51. package/dist/cirrus-npm-cjs.generated.d.ts.map +1 -0
  52. package/dist/cirrus-npm-cjs.generated.js +41 -0
  53. package/dist/cirrus-plugin-react.generated.d.ts +34 -0
  54. package/dist/cirrus-plugin-react.generated.d.ts.map +1 -0
  55. package/dist/cirrus-plugin-react.generated.js +33 -0
  56. package/dist/constants.d.ts +34 -0
  57. package/dist/constants.d.ts.map +1 -0
  58. package/dist/constants.js +116 -0
  59. package/dist/esbuild-wasm-bundle.generated.d.ts +41 -0
  60. package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -0
  61. package/dist/esbuild-wasm-bundle.generated.js +40 -0
  62. package/dist/facets/cirrus-real.d.ts +147 -0
  63. package/dist/facets/cirrus-real.d.ts.map +1 -0
  64. package/dist/facets/cirrus-real.js +981 -0
  65. package/dist/facets/inner-do-registry.d.ts +41 -0
  66. package/dist/facets/inner-do-registry.d.ts.map +1 -0
  67. package/dist/facets/inner-do-registry.js +51 -0
  68. package/dist/facets/manager.d.ts +314 -0
  69. package/dist/facets/manager.d.ts.map +1 -0
  70. package/dist/facets/manager.js +2681 -0
  71. package/dist/facets/process.d.ts +318 -0
  72. package/dist/facets/process.d.ts.map +1 -0
  73. package/dist/facets/process.js +567 -0
  74. package/dist/facets/real-vite-fs-shim.d.ts +151 -0
  75. package/dist/facets/real-vite-fs-shim.d.ts.map +1 -0
  76. package/dist/facets/real-vite-fs-shim.js +1279 -0
  77. package/dist/facets/real-vite-hmr.d.ts +142 -0
  78. package/dist/facets/real-vite-hmr.d.ts.map +1 -0
  79. package/dist/facets/real-vite-hmr.js +476 -0
  80. package/dist/facets/vite-dev-server.d.ts +293 -0
  81. package/dist/facets/vite-dev-server.d.ts.map +1 -0
  82. package/dist/facets/vite-dev-server.js +2580 -0
  83. package/dist/facets/wasm-swap-registry.d.ts +261 -0
  84. package/dist/facets/wasm-swap-registry.d.ts.map +1 -0
  85. package/dist/facets/wasm-swap-registry.js +551 -0
  86. package/dist/facets/ws-terminal.d.ts +107 -0
  87. package/dist/facets/ws-terminal.d.ts.map +1 -0
  88. package/dist/facets/ws-terminal.js +207 -0
  89. package/dist/git/commands.d.ts +13 -0
  90. package/dist/git/commands.d.ts.map +1 -0
  91. package/dist/git/commands.js +633 -0
  92. package/dist/git/network-facet.d.ts +61 -0
  93. package/dist/git/network-facet.d.ts.map +1 -0
  94. package/dist/git/network-facet.js +700 -0
  95. package/dist/git-bundle.generated.d.ts +16 -0
  96. package/dist/git-bundle.generated.d.ts.map +1 -0
  97. package/dist/git-bundle.generated.js +15 -0
  98. package/dist/index.d.ts +41 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +73 -0
  101. package/dist/loaders/child-process/spawn-facet.d.ts +90 -0
  102. package/dist/loaders/child-process/spawn-facet.d.ts.map +1 -0
  103. package/dist/loaders/child-process/spawn-facet.js +103 -0
  104. package/dist/loaders/child-process/spawn-pool.d.ts +110 -0
  105. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -0
  106. package/dist/loaders/child-process/spawn-pool.js +161 -0
  107. package/dist/loaders/fanout-pool.d.ts +195 -0
  108. package/dist/loaders/fanout-pool.d.ts.map +1 -0
  109. package/dist/loaders/fanout-pool.js +326 -0
  110. package/dist/loaders/generated-workers.d.ts +26 -0
  111. package/dist/loaders/generated-workers.d.ts.map +1 -0
  112. package/dist/loaders/generated-workers.js +25 -0
  113. package/dist/loaders/index.d.ts +15 -0
  114. package/dist/loaders/index.d.ts.map +1 -0
  115. package/dist/loaders/index.js +13 -0
  116. package/dist/loaders/loader-pool.d.ts +273 -0
  117. package/dist/loaders/loader-pool.d.ts.map +1 -0
  118. package/dist/loaders/loader-pool.js +624 -0
  119. package/dist/loaders/npm-resolve-preamble.d.ts +34 -0
  120. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -0
  121. package/dist/loaders/npm-resolve-preamble.js +237 -0
  122. package/dist/loaders/pre-bundle-preamble.d.ts +41 -0
  123. package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -0
  124. package/dist/loaders/pre-bundle-preamble.js +114 -0
  125. package/dist/loaders/vendor/codegen.d.ts +24 -0
  126. package/dist/loaders/vendor/codegen.d.ts.map +1 -0
  127. package/dist/loaders/vendor/codegen.js +44 -0
  128. package/dist/loaders/vendor/errors.d.ts +24 -0
  129. package/dist/loaders/vendor/errors.d.ts.map +1 -0
  130. package/dist/loaders/vendor/errors.js +46 -0
  131. package/dist/loaders/vendor/index.d.ts +16 -0
  132. package/dist/loaders/vendor/index.d.ts.map +1 -0
  133. package/dist/loaders/vendor/index.js +11 -0
  134. package/dist/loaders/vendor/pool.d.ts +88 -0
  135. package/dist/loaders/vendor/pool.d.ts.map +1 -0
  136. package/dist/loaders/vendor/pool.js +353 -0
  137. package/dist/loaders/vendor/primitives.d.ts +11 -0
  138. package/dist/loaders/vendor/primitives.d.ts.map +1 -0
  139. package/dist/loaders/vendor/primitives.js +33 -0
  140. package/dist/loaders/vendor/serialize.d.ts +3 -0
  141. package/dist/loaders/vendor/serialize.d.ts.map +1 -0
  142. package/dist/loaders/vendor/serialize.js +25 -0
  143. package/dist/loaders/vendor/types.d.ts +60 -0
  144. package/dist/loaders/vendor/types.d.ts.map +1 -0
  145. package/dist/loaders/vendor/types.js +4 -0
  146. package/dist/npm/cache.d.ts +136 -0
  147. package/dist/npm/cache.d.ts.map +1 -0
  148. package/dist/npm/cache.js +429 -0
  149. package/dist/npm/install-batch-facet.d.ts +129 -0
  150. package/dist/npm/install-batch-facet.d.ts.map +1 -0
  151. package/dist/npm/install-batch-facet.js +598 -0
  152. package/dist/npm/install-facet.d.ts +29 -0
  153. package/dist/npm/install-facet.d.ts.map +1 -0
  154. package/dist/npm/install-facet.js +9 -0
  155. package/dist/npm/installer.d.ts +239 -0
  156. package/dist/npm/installer.d.ts.map +1 -0
  157. package/dist/npm/installer.js +2022 -0
  158. package/dist/npm/npx-install.d.ts +74 -0
  159. package/dist/npm/npx-install.d.ts.map +1 -0
  160. package/dist/npm/npx-install.js +302 -0
  161. package/dist/npm/pre-bundle-facet.d.ts +168 -0
  162. package/dist/npm/pre-bundle-facet.d.ts.map +1 -0
  163. package/dist/npm/pre-bundle-facet.js +653 -0
  164. package/dist/npm/r2-cache.d.ts +286 -0
  165. package/dist/npm/r2-cache.d.ts.map +1 -0
  166. package/dist/npm/r2-cache.js +506 -0
  167. package/dist/npm/resolve-facet.d.ts +202 -0
  168. package/dist/npm/resolve-facet.d.ts.map +1 -0
  169. package/dist/npm/resolve-facet.js +833 -0
  170. package/dist/npm/resolve-one-facet.d.ts +204 -0
  171. package/dist/npm/resolve-one-facet.d.ts.map +1 -0
  172. package/dist/npm/resolve-one-facet.js +437 -0
  173. package/dist/npm/resolver.d.ts +109 -0
  174. package/dist/npm/resolver.d.ts.map +1 -0
  175. package/dist/npm/resolver.js +899 -0
  176. package/dist/npm/tarball-stream.d.ts +85 -0
  177. package/dist/npm/tarball-stream.d.ts.map +1 -0
  178. package/dist/npm/tarball-stream.js +205 -0
  179. package/dist/npm/tarball.d.ts +51 -0
  180. package/dist/npm/tarball.d.ts.map +1 -0
  181. package/dist/npm/tarball.js +185 -0
  182. package/dist/observability/diag-counters.d.ts +292 -0
  183. package/dist/observability/diag-counters.d.ts.map +1 -0
  184. package/dist/observability/diag-counters.js +302 -0
  185. package/dist/observability/heap-estimate.d.ts +166 -0
  186. package/dist/observability/heap-estimate.d.ts.map +1 -0
  187. package/dist/observability/heap-estimate.js +192 -0
  188. package/dist/observability/heavy-alloc-coord.d.ts +95 -0
  189. package/dist/observability/heavy-alloc-coord.d.ts.map +1 -0
  190. package/dist/observability/heavy-alloc-coord.js +149 -0
  191. package/dist/observability/oom-classify.d.ts +56 -0
  192. package/dist/observability/oom-classify.d.ts.map +1 -0
  193. package/dist/observability/oom-classify.js +118 -0
  194. package/dist/observability/oom-discriminator.d.ts +192 -0
  195. package/dist/observability/oom-discriminator.d.ts.map +1 -0
  196. package/dist/observability/oom-discriminator.js +233 -0
  197. package/dist/real-vite-bundle.generated.d.ts +46 -0
  198. package/dist/real-vite-bundle.generated.d.ts.map +1 -0
  199. package/dist/real-vite-bundle.generated.js +53 -0
  200. package/dist/replica/routing.d.ts +171 -0
  201. package/dist/replica/routing.d.ts.map +1 -0
  202. package/dist/replica/routing.js +288 -0
  203. package/dist/replica/suspension.d.ts +36 -0
  204. package/dist/replica/suspension.d.ts.map +1 -0
  205. package/dist/replica/suspension.js +49 -0
  206. package/dist/router/index.d.ts +156 -0
  207. package/dist/router/index.d.ts.map +1 -0
  208. package/dist/router/index.js +220 -0
  209. package/dist/runtime/assets-loader.d.ts +71 -0
  210. package/dist/runtime/assets-loader.d.ts.map +1 -0
  211. package/dist/runtime/assets-loader.js +128 -0
  212. package/dist/runtime/barrel-detect.d.ts +46 -0
  213. package/dist/runtime/barrel-detect.d.ts.map +1 -0
  214. package/dist/runtime/barrel-detect.js +73 -0
  215. package/dist/runtime/barrel-synthesizer.d.ts +181 -0
  216. package/dist/runtime/barrel-synthesizer.d.ts.map +1 -0
  217. package/dist/runtime/barrel-synthesizer.js +541 -0
  218. package/dist/runtime/bun-repl.d.ts +46 -0
  219. package/dist/runtime/bun-repl.d.ts.map +1 -0
  220. package/dist/runtime/bun-repl.js +326 -0
  221. package/dist/runtime/bun-runner.d.ts +66 -0
  222. package/dist/runtime/bun-runner.d.ts.map +1 -0
  223. package/dist/runtime/bun-runner.js +219 -0
  224. package/dist/runtime/clang-runner.d.ts +38 -0
  225. package/dist/runtime/clang-runner.d.ts.map +1 -0
  226. package/dist/runtime/clang-runner.js +1033 -0
  227. package/dist/runtime/comment-strip.d.ts +37 -0
  228. package/dist/runtime/comment-strip.d.ts.map +1 -0
  229. package/dist/runtime/comment-strip.js +74 -0
  230. package/dist/runtime/esbuild-service.d.ts +201 -0
  231. package/dist/runtime/esbuild-service.d.ts.map +1 -0
  232. package/dist/runtime/esbuild-service.js +1444 -0
  233. package/dist/runtime/esbuild-wasm-bytes.d.ts +87 -0
  234. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -0
  235. package/dist/runtime/esbuild-wasm-bytes.js +131 -0
  236. package/dist/runtime/framework-detect.d.ts +71 -0
  237. package/dist/runtime/framework-detect.d.ts.map +1 -0
  238. package/dist/runtime/framework-detect.js +178 -0
  239. package/dist/runtime/long-running-handle.d.ts +133 -0
  240. package/dist/runtime/long-running-handle.d.ts.map +1 -0
  241. package/dist/runtime/long-running-handle.js +192 -0
  242. package/dist/runtime/node-repl.d.ts +36 -0
  243. package/dist/runtime/node-repl.d.ts.map +1 -0
  244. package/dist/runtime/node-repl.js +293 -0
  245. package/dist/runtime/node-runner.d.ts +111 -0
  246. package/dist/runtime/node-runner.d.ts.map +1 -0
  247. package/dist/runtime/node-runner.js +194 -0
  248. package/dist/runtime/node-shims.d.ts +24 -0
  249. package/dist/runtime/node-shims.d.ts.map +1 -0
  250. package/dist/runtime/node-shims.js +3019 -0
  251. package/dist/runtime/package-manager.d.ts +118 -0
  252. package/dist/runtime/package-manager.d.ts.map +1 -0
  253. package/dist/runtime/package-manager.js +420 -0
  254. package/dist/runtime/port-registry.d.ts +80 -0
  255. package/dist/runtime/port-registry.d.ts.map +1 -0
  256. package/dist/runtime/port-registry.js +166 -0
  257. package/dist/runtime/process-logs-api.d.ts +102 -0
  258. package/dist/runtime/process-logs-api.d.ts.map +1 -0
  259. package/dist/runtime/process-logs-api.js +253 -0
  260. package/dist/runtime/process-logs.d.ts +311 -0
  261. package/dist/runtime/process-logs.d.ts.map +1 -0
  262. package/dist/runtime/process-logs.js +656 -0
  263. package/dist/runtime/process-table.d.ts +65 -0
  264. package/dist/runtime/process-table.d.ts.map +1 -0
  265. package/dist/runtime/process-table.js +107 -0
  266. package/dist/runtime/project-detect.d.ts +21 -0
  267. package/dist/runtime/project-detect.d.ts.map +1 -0
  268. package/dist/runtime/project-detect.js +46 -0
  269. package/dist/runtime/python-repl.d.ts +66 -0
  270. package/dist/runtime/python-repl.d.ts.map +1 -0
  271. package/dist/runtime/python-repl.js +563 -0
  272. package/dist/runtime/python-runner.d.ts +50 -0
  273. package/dist/runtime/python-runner.d.ts.map +1 -0
  274. package/dist/runtime/python-runner.js +870 -0
  275. package/dist/runtime/repl-session.d.ts +160 -0
  276. package/dist/runtime/repl-session.d.ts.map +1 -0
  277. package/dist/runtime/repl-session.js +393 -0
  278. package/dist/runtime/require-resolver.d.ts +52 -0
  279. package/dist/runtime/require-resolver.d.ts.map +1 -0
  280. package/dist/runtime/require-resolver.js +690 -0
  281. package/dist/runtime/router-basename.d.ts +36 -0
  282. package/dist/runtime/router-basename.d.ts.map +1 -0
  283. package/dist/runtime/router-basename.js +310 -0
  284. package/dist/runtime/ruby-repl.d.ts +43 -0
  285. package/dist/runtime/ruby-repl.d.ts.map +1 -0
  286. package/dist/runtime/ruby-repl.js +229 -0
  287. package/dist/runtime/ruby-runner.d.ts +62 -0
  288. package/dist/runtime/ruby-runner.d.ts.map +1 -0
  289. package/dist/runtime/ruby-runner.js +737 -0
  290. package/dist/runtime/runtime-catalog.d.ts +99 -0
  291. package/dist/runtime/runtime-catalog.d.ts.map +1 -0
  292. package/dist/runtime/runtime-catalog.js +183 -0
  293. package/dist/runtime/runtime-registry.d.ts +136 -0
  294. package/dist/runtime/runtime-registry.d.ts.map +1 -0
  295. package/dist/runtime/runtime-registry.js +318 -0
  296. package/dist/runtime/streams.d.ts +13 -0
  297. package/dist/runtime/streams.d.ts.map +1 -0
  298. package/dist/runtime/streams.js +402 -0
  299. package/dist/runtime/wasi-instance.d.ts +190 -0
  300. package/dist/runtime/wasi-instance.d.ts.map +1 -0
  301. package/dist/runtime/wasi-instance.js +2044 -0
  302. package/dist/runtime/wasm-runner.d.ts +117 -0
  303. package/dist/runtime/wasm-runner.d.ts.map +1 -0
  304. package/dist/runtime/wasm-runner.js +705 -0
  305. package/dist/session/bindings.d.ts +168 -0
  306. package/dist/session/bindings.d.ts.map +1 -0
  307. package/dist/session/bindings.js +524 -0
  308. package/dist/session/ctx-exports.d.ts +16 -0
  309. package/dist/session/ctx-exports.d.ts.map +1 -0
  310. package/dist/session/ctx-exports.js +22 -0
  311. package/dist/session/diag.d.ts +62 -0
  312. package/dist/session/diag.d.ts.map +1 -0
  313. package/dist/session/diag.js +118 -0
  314. package/dist/session/fs-watch.d.ts +132 -0
  315. package/dist/session/fs-watch.d.ts.map +1 -0
  316. package/dist/session/fs-watch.js +255 -0
  317. package/dist/session/helpers.d.ts +150 -0
  318. package/dist/session/helpers.d.ts.map +1 -0
  319. package/dist/session/helpers.js +472 -0
  320. package/dist/session/hibernation.d.ts +145 -0
  321. package/dist/session/hibernation.d.ts.map +1 -0
  322. package/dist/session/hibernation.js +385 -0
  323. package/dist/session/init-phases.d.ts +87 -0
  324. package/dist/session/init-phases.d.ts.map +1 -0
  325. package/dist/session/init-phases.js +137 -0
  326. package/dist/session/init.d.ts +46 -0
  327. package/dist/session/init.d.ts.map +1 -0
  328. package/dist/session/init.js +3579 -0
  329. package/dist/session/keys.d.ts +56 -0
  330. package/dist/session/keys.d.ts.map +1 -0
  331. package/dist/session/keys.js +55 -0
  332. package/dist/session/nimbus-session.d.ts +434 -0
  333. package/dist/session/nimbus-session.d.ts.map +1 -0
  334. package/dist/session/nimbus-session.js +1084 -0
  335. package/dist/session/programmatic.d.ts +105 -0
  336. package/dist/session/programmatic.d.ts.map +1 -0
  337. package/dist/session/programmatic.js +326 -0
  338. package/dist/session/replica-routes.d.ts +68 -0
  339. package/dist/session/replica-routes.d.ts.map +1 -0
  340. package/dist/session/replica-routes.js +77 -0
  341. package/dist/session/routes.d.ts +29 -0
  342. package/dist/session/routes.d.ts.map +1 -0
  343. package/dist/session/routes.js +1331 -0
  344. package/dist/session/rpc.d.ts +323 -0
  345. package/dist/session/rpc.d.ts.map +1 -0
  346. package/dist/session/rpc.js +808 -0
  347. package/dist/session/state-store.d.ts +210 -0
  348. package/dist/session/state-store.d.ts.map +1 -0
  349. package/dist/session/state-store.js +459 -0
  350. package/dist/session/supervisor-rpc.d.ts +269 -0
  351. package/dist/session/supervisor-rpc.d.ts.map +1 -0
  352. package/dist/session/supervisor-rpc.js +437 -0
  353. package/dist/session/ws-hibernation-config.d.ts +64 -0
  354. package/dist/session/ws-hibernation-config.d.ts.map +1 -0
  355. package/dist/session/ws-hibernation-config.js +89 -0
  356. package/dist/session/ws.d.ts +95 -0
  357. package/dist/session/ws.d.ts.map +1 -0
  358. package/dist/session/ws.js +395 -0
  359. package/dist/shell/features.d.ts +410 -0
  360. package/dist/shell/features.d.ts.map +1 -0
  361. package/dist/shell/features.js +1885 -0
  362. package/dist/shell/unix-commands.d.ts +15 -0
  363. package/dist/shell/unix-commands.d.ts.map +1 -0
  364. package/dist/shell/unix-commands.js +3703 -0
  365. package/dist/stubs/lifo-ui.d.ts +10 -0
  366. package/dist/stubs/lifo-ui.d.ts.map +1 -0
  367. package/dist/stubs/lifo-ui.js +1 -0
  368. package/dist/tailwind-play.generated.d.ts +19 -0
  369. package/dist/tailwind-play.generated.d.ts.map +1 -0
  370. package/dist/tailwind-play.generated.js +18 -0
  371. package/dist/vfs/dev-provider.d.ts +64 -0
  372. package/dist/vfs/dev-provider.d.ts.map +1 -0
  373. package/dist/vfs/dev-provider.js +191 -0
  374. package/dist/vfs/events.d.ts +51 -0
  375. package/dist/vfs/events.d.ts.map +1 -0
  376. package/dist/vfs/events.js +127 -0
  377. package/dist/vfs/path.d.ts +30 -0
  378. package/dist/vfs/path.d.ts.map +1 -0
  379. package/dist/vfs/path.js +41 -0
  380. package/dist/vfs/seed-project.d.ts +62 -0
  381. package/dist/vfs/seed-project.d.ts.map +1 -0
  382. package/dist/vfs/seed-project.js +1045 -0
  383. package/dist/vfs/sqlite-vfs.d.ts +435 -0
  384. package/dist/vfs/sqlite-vfs.d.ts.map +1 -0
  385. package/dist/vfs/sqlite-vfs.js +1670 -0
  386. package/dist/vfs/symlink-registry.d.ts +64 -0
  387. package/dist/vfs/symlink-registry.d.ts.map +1 -0
  388. package/dist/vfs/symlink-registry.js +137 -0
  389. package/dist/wrangler/nimbus-wrangler.d.ts +157 -0
  390. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -0
  391. package/dist/wrangler/nimbus-wrangler.js +931 -0
  392. package/package.json +77 -0
  393. package/public/_assets/cirrus-plugin-react.bundle.js +70740 -0
  394. package/public/_assets/esbuild-0.24.2.wasm +0 -0
  395. package/public/_assets/real-vite-bundle.js +101478 -0
  396. package/public/_assets/rollup.wasm +0 -0
  397. package/public/favicon.svg +8 -0
  398. package/public/index.html +565 -0
  399. package/public/s/index.html +2379 -0
  400. package/scripts/bundle-esbuild-wasm.mjs +244 -0
  401. package/scripts/bundle-facet-workers.mjs +137 -0
  402. package/scripts/bundle-git.mjs +118 -0
  403. package/scripts/bundle-npm-cjs.mjs +363 -0
  404. package/scripts/bundle-plugin-react.mjs +480 -0
  405. package/scripts/bundle-real-vite.mjs +959 -0
  406. package/scripts/bundle-runtime.mjs +600 -0
  407. package/scripts/bundle-tailwind-play.mjs +130 -0
  408. package/scripts/patch-lifo-ui.mjs +131 -0
@@ -0,0 +1,2580 @@
1
+ /**
2
+ * ViteDevServer v2.0 — lightweight Vite-compatible dev server for Nimbus.
3
+ *
4
+ * Not actual Vite (which is 200+ packages). This is a purpose-built dev server
5
+ * that implements the subset of Vite's behavior needed for serving modern
6
+ * web apps: TS/TSX/JSX transform, bare import rewriting, HMR full-reload,
7
+ * path alias resolution, TailwindCSS Play CDN, CSS-as-JS modules.
8
+ *
9
+ * Architecture:
10
+ * Browser iframe → /preview/* → DO fetch() → ViteDevServer.handleRequest()
11
+ * ├── / → serves index.html (injects HMR, Tailwind CDN, <base>)
12
+ * ├── /*.ts,*.tsx,*.jsx → esbuild transform → JS with import rewrites + alias resolution
13
+ * ├── /*.css → serve as text/css (with @import inlining, @tailwind stripping, @apply expansion)
14
+ * ├── /*.css?import → wrap CSS in JS that injects <style> tag
15
+ * ├── /@modules/<pkg> → resolve from node_modules, bundle via esbuild facet (synthetic-entry for barrels)
16
+ * ├── /@vite/client → HMR client script
17
+ * ├── /*.json (as module) → export default { ... }
18
+ * ├── /*.svg,*.png,... (as module) → export default "/preview/path/to/asset"
19
+ * └── /* → serve from VFS as-is (static assets)
20
+ *
21
+ * HMR: VFS events → ViteDevServer detects changes → sends {type:'hmr'}
22
+ * messages through the DO WebSocket → frontend dispatches to iframe.
23
+ */
24
+ import { getSharedRuntimeExternals, BUNDLER_VERSION } from '../runtime/esbuild-service.js';
25
+ import { NpmCache } from '../npm/cache.js';
26
+ import { countPackageFiles, BARREL_PKG_FILE_THRESHOLD, packageNameFromSpecifier } from '../runtime/barrel-detect.js';
27
+ import { scanNamedImports, buildSyntheticEntry, buildScopedSliceForSynthetic, syntheticEntryPath, } from '../runtime/barrel-synthesizer.js';
28
+ import { resolvePackageEntry, resolveExports } from '../_shared/exports-resolver.js';
29
+ import { injectRouterBasename, shouldProcessForRouter } from '../runtime/router-basename.js';
30
+ import { TAILWIND_PLAY_BUNDLE, TAILWIND_PLAY_VERSION, } from '../tailwind-play.generated.js';
31
+ // ── HMR client code ─────────────────────────────────────────────────────
32
+ // Injected into index.html. Listens for HMR messages from parent window
33
+ // (forwarded by the main Nimbus frontend from the WebSocket).
34
+ const HMR_CLIENT = `<script type="module">
35
+ // Nimbus HMR Client — listens for reload messages from parent
36
+ (function() {
37
+ window.__NIMBUS_HMR = true;
38
+ window.addEventListener('message', function(e) {
39
+ if (e.data && e.data.type === 'nimbus-hmr') {
40
+ if (e.data.event === 'full-reload') {
41
+ console.log('[nimbus-hmr] full reload');
42
+ location.reload();
43
+ } else if (e.data.event === 'css-update') {
44
+ // Reload all stylesheets
45
+ document.querySelectorAll('link[rel="stylesheet"]').forEach(function(l) {
46
+ var href = l.href;
47
+ l.href = '';
48
+ l.href = href + (href.includes('?') ? '&' : '?') + 't=' + Date.now();
49
+ });
50
+ // Re-fetch injected style tags
51
+ document.querySelectorAll('style[data-vite-dev-id]').forEach(function(s) {
52
+ var id = s.getAttribute('data-vite-dev-id');
53
+ if (id) {
54
+ fetch(id + '?import&t=' + Date.now())
55
+ .then(function(r) { return r.text(); })
56
+ .then(function(js) { try { new Function(js)(); } catch(e) {} });
57
+ }
58
+ });
59
+ }
60
+ }
61
+ });
62
+ console.log('[nimbus-hmr] connected');
63
+ })();
64
+ </script>`;
65
+ /**
66
+ * Runtime-error overlay injected into the served index.html.
67
+ *
68
+ * Catches:
69
+ * - window 'error' events (uncaught synchronous throws + <script type="module"> load failures)
70
+ * - window 'unhandledrejection' events (promise rejections)
71
+ *
72
+ * Renders a red banner at the top of the page with:
73
+ * - the error message
74
+ * - a hint to run `npm install` when the message looks like a module-resolution error
75
+ * - a Reload button
76
+ *
77
+ * Self-contained, classic <script> (runs synchronously before modules load
78
+ * so listeners are attached before user code begins). Uses a stable overlay
79
+ * id so repeated errors replace the previous banner instead of stacking.
80
+ *
81
+ * Cleared automatically on HMR reload because the whole page is re-rendered
82
+ * (HMR_CLIENT calls location.reload() on 'full-reload' events).
83
+ */
84
+ const ERROR_OVERLAY_CLIENT = `<script>(()=>{const s=(m)=>{let o=document.getElementById('nimbus-error-overlay');if(!o){o=document.createElement('div');o.id='nimbus-error-overlay';o.style.cssText='position:fixed;top:0;left:0;right:0;background:#b91c1c;color:white;padding:16px;font-family:monospace;font-size:13px;z-index:999999;max-height:50vh;overflow:auto;box-shadow:0 4px 12px rgba(0,0,0,.3)';document.body&&document.body.appendChild(o)}o.innerHTML='<strong>Preview crashed</strong><pre style="white-space:pre-wrap;margin:8px 0 0">'+String(m||'').replace(/</g,'&lt;')+'</pre>'+(/does not provide an export|Failed to resolve/.test(m||'')?'<div style="margin-top:8px;font-size:12px">Hint: run <code>npm install</code> in your project.</div>':'')+'<button onclick="location.reload()" style="margin-top:8px;padding:4px 12px;background:white;color:#b91c1c;border:0;border-radius:4px;cursor:pointer">Reload</button>'};window.addEventListener('error',e=>s((e.message||'')+' @ '+String(e.filename||'').replace(location.origin,'')+':'+e.lineno));window.addEventListener('unhandledrejection',e=>s(String(e.reason?.message||e.reason||'(rejection)').substring(0,500)))})();</script>`;
85
+ // ── MIME types ───────────────────────────────────────────────────────────
86
+ const MIME_TYPES = {
87
+ '.html': 'text/html; charset=utf-8',
88
+ '.js': 'application/javascript; charset=utf-8',
89
+ '.mjs': 'application/javascript; charset=utf-8',
90
+ '.cjs': 'application/javascript; charset=utf-8',
91
+ '.ts': 'application/javascript; charset=utf-8',
92
+ '.tsx': 'application/javascript; charset=utf-8',
93
+ '.jsx': 'application/javascript; charset=utf-8',
94
+ '.mts': 'application/javascript; charset=utf-8',
95
+ '.cts': 'application/javascript; charset=utf-8',
96
+ // SFC formats — served as JS so a future framework plugin can transform them.
97
+ // Without a compiler plugin, the raw source would be served and the browser
98
+ // would fail to parse it — that's a framework integration problem, not a
99
+ // resolver correctness problem. At least the MIME is right.
100
+ '.vue': 'application/javascript; charset=utf-8',
101
+ '.svelte': 'application/javascript; charset=utf-8',
102
+ '.css': 'text/css; charset=utf-8',
103
+ '.json': 'application/json; charset=utf-8',
104
+ '.svg': 'image/svg+xml',
105
+ '.png': 'image/png',
106
+ '.jpg': 'image/jpeg',
107
+ '.jpeg': 'image/jpeg',
108
+ '.gif': 'image/gif',
109
+ '.ico': 'image/x-icon',
110
+ '.webp': 'image/webp',
111
+ '.woff': 'font/woff',
112
+ '.woff2': 'font/woff2',
113
+ '.ttf': 'font/ttf',
114
+ '.otf': 'font/otf',
115
+ '.txt': 'text/plain; charset=utf-8',
116
+ '.md': 'text/plain; charset=utf-8',
117
+ '.wasm': 'application/wasm',
118
+ '.map': 'application/json; charset=utf-8',
119
+ '.mp4': 'video/mp4',
120
+ '.webm': 'video/webm',
121
+ '.mp3': 'audio/mpeg',
122
+ '.pdf': 'application/pdf',
123
+ };
124
+ /** File extensions that should be treated as static assets when imported from JS */
125
+ const ASSET_EXTS = new Set([
126
+ '.svg', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp',
127
+ '.woff', '.woff2', '.ttf', '.otf',
128
+ '.mp4', '.webm', '.mp3', '.pdf',
129
+ ]);
130
+ // ── CJS external-require → ESM import rewriting ─────────────────────────
131
+ /**
132
+ * esbuild, when bundling CJS source with `external` specifiers, leaves the
133
+ * `require("pkg")` calls in the output wrapped in a `__require()` helper that
134
+ * falls back to the global `require`. In the browser there is no global
135
+ * `require`, so every such call throws.
136
+ *
137
+ * The fix: detect all distinct `__require("X")` specifiers in the output,
138
+ * emit a top-level ESM `import * as __ns_X from "X"` for each, and replace
139
+ * every `__require("X")` call with a reference to that namespace (with
140
+ * default-export interop — `__ns_X.default ?? __ns_X`).
141
+ *
142
+ * Before:
143
+ * var __require = ((x) => typeof require !== "undefined" ? require : ...);
144
+ * var React = __require("react");
145
+ *
146
+ * After:
147
+ * import * as __nimbus_ext__react from "react";
148
+ * const __nimbus_req = (id) => {
149
+ * if (id === "react") return __nimbus_ext__react.default ?? __nimbus_ext__react;
150
+ * throw new Error("require: " + id);
151
+ * };
152
+ * var __require = ((x) => typeof require !== "undefined" ? require : ...);
153
+ * var React = __nimbus_req("react");
154
+ *
155
+ * We inject `__nimbus_req` but keep esbuild's `__require` definition so we
156
+ * don't have to rewrite its declaration — we just replace the call-sites.
157
+ */
158
+ function rewriteExternalRequires(code, basePath) {
159
+ // Find all `__require("specifier")` calls where the specifier is bare
160
+ // (not a relative/absolute path). Bare specifiers are the only ones that
161
+ // can be external.
162
+ const specifiers = new Set();
163
+ for (const m of code.matchAll(/__require\s*\(\s*["']([^"'.][^"']*)["']\s*\)/g)) {
164
+ specifiers.add(m[1]);
165
+ }
166
+ if (specifiers.size === 0)
167
+ return code;
168
+ // Generate namespace variable names — sanitize specifier for use in identifier.
169
+ const specMap = new Map(); // specifier → namespace var name
170
+ for (const spec of specifiers) {
171
+ const safe = spec.replace(/[^A-Za-z0-9_]/g, '_');
172
+ specMap.set(spec, '__nimbus_ext__' + safe);
173
+ }
174
+ // Build the import banner + dispatch function.
175
+ const imports = [];
176
+ const cases = [];
177
+ for (const [spec, ns] of specMap) {
178
+ // Build URL with base prefix (makeModuleUrl handles edge cases)
179
+ const url = makeModuleUrl(basePath, spec);
180
+ imports.push(`import * as ${ns} from ${JSON.stringify(url)};`);
181
+ cases.push(` if (id === ${JSON.stringify(spec)}) return ${ns}.default ?? ${ns};`);
182
+ }
183
+ const banner = imports.join('\n') + '\n' +
184
+ 'function __nimbus_req(id) {\n' +
185
+ cases.join('\n') + '\n' +
186
+ ' throw new Error("Cannot require external: " + id);\n' +
187
+ '}\n';
188
+ // Replace all `__require("specifier")` call-sites with `__nimbus_req("specifier")`.
189
+ // The replacement preserves the specifier string so the dispatch function
190
+ // looks up the correct namespace.
191
+ const rewritten = code.replace(/__require\s*\(\s*(["'])([^"'.][^"']*)\1\s*\)/g, (_m, q, s) => `__nimbus_req(${q}${s}${q})`);
192
+ return banner + rewritten;
193
+ }
194
+ // ── CJS → ESM named-exports synthesis ───────────────────────────────────
195
+ /**
196
+ * esbuild bundles CJS packages by wrapping them in __commonJS helpers.
197
+ * The resulting ESM bundle only has `export default require_X()` — no named
198
+ * exports. This breaks `import { createRoot } from "react-dom/client"`.
199
+ *
200
+ * We fix this by STATICALLY analyzing the bundled source to find CJS export
201
+ * patterns, then emitting named exports for each found name. We cannot use
202
+ * `new Function()` or `eval()` to get runtime export keys because the
203
+ * Cloudflare Workers runtime disallows string-to-code generation outside of
204
+ * module initialization.
205
+ *
206
+ * Patterns we detect (scanning the entire bundled text, not just the top level):
207
+ * - `exports.NAME = ...`
208
+ * - `exports["NAME"] = ...`
209
+ * - `Object.defineProperty(exports, "NAME", ...)`
210
+ * - `module.exports.NAME = ...`
211
+ * - `module.exports = { NAME, NAME2, ... }` (object literal)
212
+ *
213
+ * Input (esbuild output):
214
+ * var require_X = __commonJS({ "...": function(exports) { exports.jsx = ...; exports.jsxs = ...; } });
215
+ * export default require_X();
216
+ *
217
+ * Output (synthesized):
218
+ * var require_X = __commonJS({...});
219
+ * const __nimbus_ns = require_X();
220
+ * export default __nimbus_ns;
221
+ * export const jsx = __nimbus_ns.jsx;
222
+ * export const jsxs = __nimbus_ns.jsxs;
223
+ *
224
+ * Note: we emit `export const NAME = __nimbus_ns.NAME` per key rather than
225
+ * `export const { NAME, ... } = __nimbus_ns` destructuring. The former
226
+ * preserves live binding semantics slightly better and avoids issues when
227
+ * a key name happens to shadow a keyword or identifier.
228
+ *
229
+ * Returns the original code unchanged if:
230
+ * - The bundle already has named exports (not a CJS-only bundle)
231
+ * - No `export default` pattern found
232
+ * - No CJS export patterns found in the bundle source
233
+ */
234
+ function synthesizeCjsNamedExports(code) {
235
+ // Quick check: does the bundle already have named exports?
236
+ if (/(^|\n)\s*export\s+(?:const|let|var|function|class|\{|\*)\b/.test(code)) {
237
+ return code;
238
+ }
239
+ // Confirm we have `export default ...` pattern.
240
+ const defaultMatch = code.match(/(^|\n)\s*export\s+default\s+([^;]+);?\s*(?:\/\/[^\n]*)?$/m);
241
+ if (!defaultMatch)
242
+ return code;
243
+ // Extract named exports by scanning the bundled source for CJS patterns.
244
+ const names = extractCjsExportNames(code);
245
+ if (names.length === 0)
246
+ return code;
247
+ // Build the synthesized bundle. Replace `export default X` with:
248
+ // const __nimbus_ns = X;
249
+ // export default __nimbus_ns;
250
+ // export const NAME1 = __nimbus_ns.NAME1;
251
+ // export const NAME2 = __nimbus_ns.NAME2;
252
+ // ...
253
+ const namedExportLines = names
254
+ .map(n => `export const ${n} = __nimbus_ns.${n};`)
255
+ .join('\n');
256
+ const rewritten = code.replace(/(^|\n)\s*export\s+default\s+([^;]+);?\s*$/m, `$1const __nimbus_ns = ($2);\nexport default __nimbus_ns;\n${namedExportLines}\n`);
257
+ return rewritten;
258
+ }
259
+ /**
260
+ * Statically extract named export names from a CJS bundle source. Scans the
261
+ * entire text (not scoped — CJS exports appear throughout __commonJS wrappers)
262
+ * for `exports.X =`, `exports["X"] =`, `Object.defineProperty(exports, "X", ...)`,
263
+ * and `module.exports = { X, Y }` patterns.
264
+ *
265
+ * Returns a deduplicated array of valid ES identifier names, filtered to
266
+ * exclude reserved keywords and `default` (which is already the default export).
267
+ */
268
+ function extractCjsExportNames(code) {
269
+ const names = new Set();
270
+ // Pattern 1: exports.NAME = ...
271
+ // Matches `exports.createRoot = ...`, `exports . hydrateRoot = ...`
272
+ for (const m of code.matchAll(/(?:^|[^.\w$])exports\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*=/g)) {
273
+ names.add(m[1]);
274
+ }
275
+ // Pattern 2: exports["NAME"] = ... or exports['NAME'] = ...
276
+ for (const m of code.matchAll(/(?:^|[^.\w$])exports\s*\[\s*["']([A-Za-z_$][A-Za-z0-9_$]*)["']\s*\]\s*=/g)) {
277
+ names.add(m[1]);
278
+ }
279
+ // Pattern 3: Object.defineProperty(exports, "NAME", ...)
280
+ for (const m of code.matchAll(/Object\.defineProperty\s*\(\s*exports\s*,\s*["']([A-Za-z_$][A-Za-z0-9_$]*)["']/g)) {
281
+ names.add(m[1]);
282
+ }
283
+ // Pattern 4: module.exports.NAME = ...
284
+ for (const m of code.matchAll(/module\s*\.\s*exports\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*=/g)) {
285
+ names.add(m[1]);
286
+ }
287
+ // Pattern 5: module.exports = { NAME, NAME2: value, ... }
288
+ // Find `module.exports = {` then scan the balanced braces for keys.
289
+ const moduleExportsMatch = code.match(/module\s*\.\s*exports\s*=\s*\{/);
290
+ if (moduleExportsMatch) {
291
+ const startIdx = moduleExportsMatch.index + moduleExportsMatch[0].length;
292
+ // Find matching closing brace (simple depth tracking, ignoring strings/comments).
293
+ let depth = 1;
294
+ let i = startIdx;
295
+ while (i < code.length && depth > 0) {
296
+ const ch = code[i];
297
+ if (ch === '{')
298
+ depth++;
299
+ else if (ch === '}')
300
+ depth--;
301
+ else if (ch === '"' || ch === "'" || ch === '`') {
302
+ // Skip string literal
303
+ const quote = ch;
304
+ i++;
305
+ while (i < code.length && code[i] !== quote) {
306
+ if (code[i] === '\\')
307
+ i++;
308
+ i++;
309
+ }
310
+ }
311
+ i++;
312
+ }
313
+ if (depth === 0) {
314
+ const objBody = code.substring(startIdx, i - 1);
315
+ // Extract keys from object literal (identifier before `:` or `,`/`}`).
316
+ for (const m of objBody.matchAll(/(?:^|[,{])\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*(?=[,:}])/g)) {
317
+ names.add(m[1]);
318
+ }
319
+ // Also: quoted keys like "name": value
320
+ for (const m of objBody.matchAll(/(?:^|[,{])\s*["']([A-Za-z_$][A-Za-z0-9_$]*)["']\s*:/g)) {
321
+ names.add(m[1]);
322
+ }
323
+ }
324
+ }
325
+ // Filter: exclude `default` (already the default export), reserved words,
326
+ // and anything that's not a valid ES identifier.
327
+ return Array.from(names).filter(n => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(n) &&
328
+ n !== 'default' &&
329
+ !RESERVED_ES_KEYWORDS.has(n));
330
+ }
331
+ /** ES reserved keywords that can't be used as destructured variable names. */
332
+ const RESERVED_ES_KEYWORDS = new Set([
333
+ 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
334
+ 'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false',
335
+ 'finally', 'for', 'function', 'if', 'implements', 'import', 'in',
336
+ 'instanceof', 'interface', 'let', 'new', 'null', 'package', 'private',
337
+ 'protected', 'public', 'return', 'super', 'switch', 'static', 'this',
338
+ 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield',
339
+ ]);
340
+ // ── Import rewriting ────────────────────────────────────────────────────
341
+ /**
342
+ * Resolve a specifier against path aliases.
343
+ * Uses longest-match semantics (e.g. "@/lib" takes priority over "@").
344
+ * Returns the rewritten path (e.g. "/preview/src/components/Foo") or null.
345
+ *
346
+ * basePath must be the dev server's mount point without trailing slash
347
+ * (e.g. "/preview"). If basePath is "/" or empty, output is origin-rooted.
348
+ */
349
+ /**
350
+ * Collapse `..` and `.` segments in a VFS path.
351
+ *
352
+ * Essential for entries coming from package.json `main`/`module` that point
353
+ * OUTSIDE their own directory — e.g. react-remove-scroll-bar's nested
354
+ * `constants/package.json` has `"module": "../dist/es2015/constants.js"`,
355
+ * which when naively joined produces `constants/../dist/es2015/constants.js`.
356
+ * The VFS doesn't interpret `..` at lookup time — it treats it as a literal
357
+ * path component that doesn't exist, so resolution fails and we fall through
358
+ * to a bogus CDN wrapper.
359
+ *
360
+ * Leading slashes are preserved. Trailing slashes are stripped.
361
+ * Returns the empty string for a pure-dot path like "." or "./.".
362
+ */
363
+ function normalizePath(p) {
364
+ const leadingSlash = p.startsWith('/');
365
+ const segments = p.split('/').reduce((acc, seg) => {
366
+ if (seg === '..') {
367
+ // Pop the previous segment if there is one (don't pop past root).
368
+ if (acc.length > 0 && acc[acc.length - 1] !== '..')
369
+ acc.pop();
370
+ else if (!leadingSlash)
371
+ acc.push('..'); // preserve leading .. for relative paths
372
+ }
373
+ else if (seg !== '.' && seg !== '') {
374
+ acc.push(seg);
375
+ }
376
+ return acc;
377
+ }, []);
378
+ return (leadingSlash ? '/' : '') + segments.join('/');
379
+ }
380
+ function resolveAliasSpecifier(specifier, aliases, basePath) {
381
+ // Sort by alias length descending for longest-match-first
382
+ const sorted = Object.entries(aliases).sort((a, b) => b[0].length - a[0].length);
383
+ for (const [alias, target] of sorted) {
384
+ // Match exact alias or alias followed by /
385
+ if (specifier === alias || specifier.startsWith(alias + '/')) {
386
+ const rest = specifier.slice(alias.length); // e.g. "/components/Foo" or ""
387
+ const resolvedTarget = target.replace(/^\.\//, '').replace(/^\//, '');
388
+ // Normalize basePath: strip trailing slash so we always emit exactly one
389
+ // slash between base and target. Handle root ("/" or "") specially.
390
+ const base = basePath === '/' || basePath === '' ? '' : basePath.replace(/\/+$/, '');
391
+ return `${base}/${resolvedTarget}${rest}`;
392
+ }
393
+ }
394
+ return null;
395
+ }
396
+ /**
397
+ * Build an absolute URL for a /@modules/ endpoint, properly prefixed with the
398
+ * dev server's base path so the browser's origin-relative fetch hits the route.
399
+ *
400
+ * Examples:
401
+ * makeModuleUrl('/preview', 'react') → '/preview/@modules/react'
402
+ * makeModuleUrl('/preview/', 'react') → '/preview/@modules/react' (no double slash)
403
+ * makeModuleUrl('/', 'react') → '/@modules/react' (root base)
404
+ * makeModuleUrl('', 'react') → '/@modules/react' (empty/undefined)
405
+ * makeModuleUrl(undefined, 'react') → '/@modules/react' (undefined)
406
+ *
407
+ * This matters because the browser resolves `/@modules/...` against the origin
408
+ * (NOT against <base>), so without the prefix the request misses the
409
+ * `/preview/*` route and 404s.
410
+ */
411
+ function makeModuleUrl(basePath, specifier) {
412
+ if (!basePath || basePath === '/' || basePath === '') {
413
+ return `/@modules/${specifier}`;
414
+ }
415
+ // Strip trailing slash from basePath so we get exactly one slash before @modules
416
+ const base = basePath.replace(/\/+$/, '');
417
+ return `${base}/@modules/${specifier}`;
418
+ }
419
+ /**
420
+ * Node.js core builtins. Bare imports of these names should be rewritten to
421
+ * `node:<name>` rather than treated as bare npm specifiers — otherwise the
422
+ * browser dev server tries to fetch them from `/preview/@modules/crypto` and
423
+ * 404s. See audit/sections/03-resolver-gaps.md §3.6.
424
+ */
425
+ const NODE_BUILTINS = new Set([
426
+ 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'constants',
427
+ 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'fs/promises', 'http',
428
+ 'http2', 'https', 'inspector', 'module', 'net', 'os', 'path', 'path/posix',
429
+ 'path/win32', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline',
430
+ 'repl', 'stream', 'stream/consumers', 'stream/promises', 'stream/web',
431
+ 'string_decoder', 'sys', 'timers', 'timers/promises', 'tls', 'trace_events',
432
+ 'tty', 'url', 'util', 'util/types', 'v8', 'vm', 'wasi', 'worker_threads',
433
+ 'zlib',
434
+ ]);
435
+ /**
436
+ * Resolve a bare specifier: alias → resolved path, or bare → /<base>/@modules/pkg.
437
+ *
438
+ * `importerCtx` carries the importing-file context so we can resolve
439
+ * Node subpath imports (`#X`) per Node spec — `package.json#imports`
440
+ * is package-LOCAL, looked up via the closest enclosing package.json
441
+ * walking up from the importer's directory. Without it, `#X` survives
442
+ * the rewriter and crashes the browser preview (Markflow on prod
443
+ * 0a488bab: `Uncaught TypeError: Failed to resolve module specifier
444
+ * "#minpath"`).
445
+ *
446
+ * Returns null if specifier should not be rewritten.
447
+ */
448
+ function resolveBareSpecifier(specifier, aliases, basePath, importerCtx) {
449
+ // Skip already-rewritten or absolute URLs
450
+ if (specifier.startsWith('@modules/'))
451
+ return null;
452
+ if (specifier.startsWith('http://') || specifier.startsWith('https://'))
453
+ return null;
454
+ // Skip protocol-like specifiers: data:, blob:, virtual:, node:, etc.
455
+ if (/^[a-z][a-z0-9+.-]*:/i.test(specifier))
456
+ return null;
457
+ // Subpath imports (#X) — Node.js `package.json#imports` field. MUST
458
+ // be resolved against the importing file's owning package.json. If
459
+ // we have an importer context, walk up to resolve; otherwise leave
460
+ // the specifier alone (so the dev-server bundle path or the browser
461
+ // surfaces a meaningful error rather than a misleading /@modules/
462
+ // 404). See exports-resolver.ts for the spec impl.
463
+ if (specifier.startsWith('#')) {
464
+ if (!importerCtx)
465
+ return null;
466
+ const target = resolveHashImportFromImporter(specifier, importerCtx);
467
+ if (!target)
468
+ return null;
469
+ // Convert VFS path to a /preview-relative URL so the browser can
470
+ // load it without going through /@modules/. Strip the project root
471
+ // prefix so the URL is `${basePath}/<rel-from-root>`.
472
+ const rel = target.startsWith(importerCtx.root + '/')
473
+ ? target.substring(importerCtx.root.length + 1)
474
+ : target;
475
+ const base = (basePath || '').replace(/\/+$/, '');
476
+ return base + '/' + rel.replace(/^\/+/, '');
477
+ }
478
+ // Bare-builtin handling: a bare `crypto` (no protocol prefix) that names
479
+ // a Node core builtin must NOT be served from /@modules/ (which 404s in
480
+ // the browser — verified Mossaic regression). Returning null here leaves
481
+ // the literal `import 'crypto'` in place — matching the behaviour for
482
+ // already-prefixed `node:crypto` (skipped by the protocol regex above).
483
+ // Both surface a clear "Failed to resolve module 'crypto'" rather than a
484
+ // misleading 404. Browser-side shim wiring is the W7 deliverable per
485
+ // audit/sections/03-resolver-gaps.md §3.6.
486
+ if (NODE_BUILTINS.has(specifier)) {
487
+ return null; // leave the bare specifier untouched
488
+ }
489
+ // Check aliases (works for CSS specifiers too — alias takes priority)
490
+ if (aliases) {
491
+ const resolved = resolveAliasSpecifier(specifier, aliases, basePath || '/preview');
492
+ if (resolved)
493
+ return resolved;
494
+ }
495
+ // Don't rewrite CSS bare imports to /@modules/ (they should be file paths)
496
+ if (specifier.endsWith('.css') || specifier.includes('.css?'))
497
+ return null;
498
+ return makeModuleUrl(basePath, specifier);
499
+ }
500
+ /**
501
+ * Walk up from `importerCtx.importerVfsPath` looking for a
502
+ * `package.json` with an `imports` field that exposes `specifier`.
503
+ * Returns a VFS path to the resolved target, or null if no
504
+ * `package.json#imports` covers it. First package.json wins per Node
505
+ * spec — even if it has no `imports` field, we stop walking (the
506
+ * specifier is unresolved against the importing module's package).
507
+ *
508
+ * Conditions: ESM browser default order
509
+ * (`import` > `module` > `browser` > `default`) — matches what
510
+ * `serveTransformed` is doing (we're serving for the browser).
511
+ */
512
+ function resolveHashImportFromImporter(specifier, ctx) {
513
+ let dir = ctx.importerVfsPath;
514
+ const lastSlash = dir.lastIndexOf('/');
515
+ if (lastSlash <= 0)
516
+ return null;
517
+ dir = dir.substring(0, lastSlash);
518
+ const visited = new Set();
519
+ while (dir && !visited.has(dir)) {
520
+ visited.add(dir);
521
+ const pkgJsonPath = dir + '/package.json';
522
+ if (ctx.vfs.exists(pkgJsonPath)) {
523
+ try {
524
+ const pkg = JSON.parse(ctx.vfs.readFileString(pkgJsonPath));
525
+ if (pkg && pkg.imports) {
526
+ const target = resolveExports(pkg.imports, specifier);
527
+ if (target) {
528
+ const rel = target.replace(/^\.\//, '');
529
+ const candidate = dir + '/' + rel;
530
+ if (ctx.vfs.exists(candidate))
531
+ return candidate;
532
+ }
533
+ }
534
+ }
535
+ catch { /* malformed package.json — keep walking is wrong per
536
+ spec; first package.json wins. Fall through to break. */
537
+ }
538
+ return null;
539
+ }
540
+ const lastSlashDir = dir.lastIndexOf('/');
541
+ if (lastSlashDir <= 0)
542
+ break;
543
+ dir = dir.substring(0, lastSlashDir);
544
+ }
545
+ return null;
546
+ }
547
+ /**
548
+ * Rewrite all bare import/export specifiers in JS code.
549
+ *
550
+ * Handles ALL import forms including multi-line:
551
+ * 1. import "specifier" (side-effect)
552
+ * 2. import defaultExport from "specifier" (default)
553
+ * 3. import { named } from "specifier" (named, possibly multi-line)
554
+ * 4. import * as ns from "specifier" (namespace)
555
+ * 5. export { named } from "specifier" (re-export)
556
+ * 6. export * from "specifier" (re-export all)
557
+ * 7. import("specifier") (dynamic)
558
+ *
559
+ * Strategy: Three separate passes to avoid cross-statement regex issues.
560
+ * Pass 1: CSS side-effect imports (append ?import)
561
+ * Pass 2: Side-effect bare imports (import "specifier";)
562
+ * Pass 3: All "from" clause specifiers (works with multi-line imports)
563
+ * Pass 4: Dynamic imports (import("specifier"))
564
+ */
565
+ // Valid npm specifier character class. Allows package names (letters, digits,
566
+ // hyphens, underscores, dots), scoped packages (@scope/name), subpaths
567
+ // (slashes), and query strings (?v=123 for HMR invalidation). Crucially,
568
+ // it EXCLUDES spaces, operators (+, *, (, )), and other punctuation that
569
+ // would never appear in a real import specifier. Before this restriction,
570
+ // regexes like `/from "([^"]*)"/` matched inside string literals such as:
571
+ //
572
+ // throw new Error("called from " + fn.name + " here");
573
+ //
574
+ // treating "` + fn.name + `" as a specifier and corrupting the source code.
575
+ // After: only character sequences that look like valid specifiers match.
576
+ //
577
+ // Leading `#` is permitted so Node.js subpath-imports specifiers
578
+ // (`package.json#imports`, e.g. `import x from "#minpath"` from
579
+ // inside vfile/unified) are parsed by the rewriter and routed to the
580
+ // hash-resolver in `resolveBareSpecifier`. Without `#` in the leading
581
+ // class, `import x from "#minpath"` survived the rewriter literally
582
+ // and the browser crashed with `Failed to resolve module specifier
583
+ // "#minpath"` — Markflow regression on prod 0a488bab.
584
+ const SPECIFIER_WITH_QUERY = '[A-Za-z0-9_@#][A-Za-z0-9_@./?=&-]*';
585
+ function rewriteAllImports(code, aliases, basePath, importerCtx) {
586
+ // Pass 1: CSS side-effect imports: import "./foo.css" → import "./foo.css?import"
587
+ // import "@/index.css" → import "/preview/src/index.css?import"
588
+ code = code.replace(/import\s+(["'])([^"']+\.css)\1\s*;/g, (_match, quote, path) => {
589
+ if (aliases && !path.startsWith('.') && !path.startsWith('/')) {
590
+ const resolved = resolveAliasSpecifier(path, aliases, basePath || '/preview');
591
+ if (resolved)
592
+ return `import ${quote}${resolved}?import${quote};`;
593
+ }
594
+ return `import ${quote}${path}?import${quote};`;
595
+ });
596
+ // Pass 2: Side-effect bare imports: import "specifier";
597
+ code = code.replace(new RegExp(`import\\s+(["'])(${SPECIFIER_WITH_QUERY})\\1\\s*;`, 'g'), (match, quote, specifier) => {
598
+ const resolved = resolveBareSpecifier(specifier, aliases, basePath, importerCtx);
599
+ return resolved ? `import ${quote}${resolved}${quote};` : match;
600
+ });
601
+ // Pass 3: All "from" clause specifiers — handles single AND multi-line imports.
602
+ // Matches: import X from "pkg", import { X } from "pkg", export { X } from "pkg",
603
+ // import * as X from "pkg", export * from "pkg" — even multi-line.
604
+ code = code.replace(new RegExp(`(\\bfrom\\s+)(["'])(${SPECIFIER_WITH_QUERY})\\2`, 'g'), (match, fromPart, quote, specifier) => {
605
+ const resolved = resolveBareSpecifier(specifier, aliases, basePath, importerCtx);
606
+ return resolved ? `${fromPart}${quote}${resolved}${quote}` : match;
607
+ });
608
+ // Pass 4: Dynamic imports: import("specifier")
609
+ code = code.replace(new RegExp(`import\\(\\s*(["'])(${SPECIFIER_WITH_QUERY})\\1\\s*\\)`, 'g'), (match, quote, specifier) => {
610
+ const resolved = resolveBareSpecifier(specifier, aliases, basePath, importerCtx);
611
+ return resolved ? `import(${quote}${resolved}${quote})` : match;
612
+ });
613
+ return code;
614
+ }
615
+ // ── Tailwind @apply expander ────────────────────────────────────────────
616
+ /** Responsive breakpoints for Tailwind prefixes */
617
+ const TW_BREAKPOINTS = {
618
+ 'sm': '640px', 'md': '768px', 'lg': '1024px', 'xl': '1280px', '2xl': '1536px',
619
+ };
620
+ /** Standard Tailwind utility → CSS property map */
621
+ const TW_UTILITIES = {
622
+ // Display
623
+ 'block': 'display: block',
624
+ 'inline-block': 'display: inline-block',
625
+ 'inline': 'display: inline',
626
+ 'flex': 'display: flex',
627
+ 'inline-flex': 'display: inline-flex',
628
+ 'grid': 'display: grid',
629
+ 'hidden': 'display: none',
630
+ 'contents': 'display: contents',
631
+ // Position
632
+ 'static': 'position: static',
633
+ 'fixed': 'position: fixed',
634
+ 'absolute': 'position: absolute',
635
+ 'relative': 'position: relative',
636
+ 'sticky': 'position: sticky',
637
+ // Inset
638
+ 'inset-0': 'inset: 0px',
639
+ 'top-0': 'top: 0px',
640
+ 'right-0': 'right: 0px',
641
+ 'bottom-0': 'bottom: 0px',
642
+ 'left-0': 'left: 0px',
643
+ // Flex
644
+ 'flex-row': 'flex-direction: row',
645
+ 'flex-col': 'flex-direction: column',
646
+ 'flex-wrap': 'flex-wrap: wrap',
647
+ 'flex-nowrap': 'flex-wrap: nowrap',
648
+ 'flex-1': 'flex: 1 1 0%',
649
+ 'flex-auto': 'flex: 1 1 auto',
650
+ 'flex-initial': 'flex: 0 1 auto',
651
+ 'flex-none': 'flex: none',
652
+ 'flex-grow': 'flex-grow: 1',
653
+ 'flex-shrink': 'flex-shrink: 1',
654
+ 'grow': 'flex-grow: 1',
655
+ 'shrink': 'flex-shrink: 1',
656
+ 'shrink-0': 'flex-shrink: 0',
657
+ // Align / Justify
658
+ 'items-start': 'align-items: flex-start',
659
+ 'items-end': 'align-items: flex-end',
660
+ 'items-center': 'align-items: center',
661
+ 'items-baseline': 'align-items: baseline',
662
+ 'items-stretch': 'align-items: stretch',
663
+ 'justify-start': 'justify-content: flex-start',
664
+ 'justify-end': 'justify-content: flex-end',
665
+ 'justify-center': 'justify-content: center',
666
+ 'justify-between': 'justify-content: space-between',
667
+ 'justify-around': 'justify-content: space-around',
668
+ 'justify-evenly': 'justify-content: space-evenly',
669
+ 'self-auto': 'align-self: auto',
670
+ 'self-start': 'align-self: flex-start',
671
+ 'self-end': 'align-self: flex-end',
672
+ 'self-center': 'align-self: center',
673
+ 'self-stretch': 'align-self: stretch',
674
+ // Sizing
675
+ 'w-full': 'width: 100%',
676
+ 'w-screen': 'width: 100vw',
677
+ 'w-auto': 'width: auto',
678
+ 'w-fit': 'width: fit-content',
679
+ 'w-min': 'width: min-content',
680
+ 'w-max': 'width: max-content',
681
+ 'min-w-0': 'min-width: 0px',
682
+ 'min-w-full': 'min-width: 100%',
683
+ 'max-w-none': 'max-width: none',
684
+ 'max-w-full': 'max-width: 100%',
685
+ 'h-full': 'height: 100%',
686
+ 'h-screen': 'height: 100vh',
687
+ 'h-auto': 'height: auto',
688
+ 'h-fit': 'height: fit-content',
689
+ 'min-h-0': 'min-height: 0px',
690
+ 'min-h-full': 'min-height: 100%',
691
+ 'min-h-screen': 'min-height: 100vh',
692
+ // Overflow
693
+ 'overflow-auto': 'overflow: auto',
694
+ 'overflow-hidden': 'overflow: hidden',
695
+ 'overflow-visible': 'overflow: visible',
696
+ 'overflow-scroll': 'overflow: scroll',
697
+ 'overflow-x-auto': 'overflow-x: auto',
698
+ 'overflow-x-hidden': 'overflow-x: hidden',
699
+ 'overflow-y-auto': 'overflow-y: auto',
700
+ 'overflow-y-hidden': 'overflow-y: hidden',
701
+ // Typography
702
+ 'text-left': 'text-align: left',
703
+ 'text-center': 'text-align: center',
704
+ 'text-right': 'text-align: right',
705
+ 'text-justify': 'text-align: justify',
706
+ 'text-xs': 'font-size: 0.75rem; line-height: 1rem',
707
+ 'text-sm': 'font-size: 0.875rem; line-height: 1.25rem',
708
+ 'text-base': 'font-size: 1rem; line-height: 1.5rem',
709
+ 'text-lg': 'font-size: 1.125rem; line-height: 1.75rem',
710
+ 'text-xl': 'font-size: 1.25rem; line-height: 1.75rem',
711
+ 'text-2xl': 'font-size: 1.5rem; line-height: 2rem',
712
+ 'text-3xl': 'font-size: 1.875rem; line-height: 2.25rem',
713
+ 'text-4xl': 'font-size: 2.25rem; line-height: 2.5rem',
714
+ 'text-5xl': 'font-size: 3rem; line-height: 1.1',
715
+ 'text-6xl': 'font-size: 3.75rem; line-height: 1.1',
716
+ 'text-7xl': 'font-size: 4.5rem; line-height: 1.1',
717
+ 'text-8xl': 'font-size: 6rem; line-height: 1',
718
+ 'text-9xl': 'font-size: 8rem; line-height: 1',
719
+ 'font-thin': 'font-weight: 100',
720
+ 'font-extralight': 'font-weight: 200',
721
+ 'font-light': 'font-weight: 300',
722
+ 'font-normal': 'font-weight: 400',
723
+ 'font-medium': 'font-weight: 500',
724
+ 'font-semibold': 'font-weight: 600',
725
+ 'font-bold': 'font-weight: 700',
726
+ 'font-extrabold': 'font-weight: 800',
727
+ 'font-black': 'font-weight: 900',
728
+ 'italic': 'font-style: italic',
729
+ 'not-italic': 'font-style: normal',
730
+ 'underline': 'text-decoration-line: underline',
731
+ 'overline': 'text-decoration-line: overline',
732
+ 'line-through': 'text-decoration-line: line-through',
733
+ 'no-underline': 'text-decoration-line: none',
734
+ 'uppercase': 'text-transform: uppercase',
735
+ 'lowercase': 'text-transform: lowercase',
736
+ 'capitalize': 'text-transform: capitalize',
737
+ 'normal-case': 'text-transform: none',
738
+ 'truncate': 'overflow: hidden; text-overflow: ellipsis; white-space: nowrap',
739
+ 'whitespace-normal': 'white-space: normal',
740
+ 'whitespace-nowrap': 'white-space: nowrap',
741
+ 'whitespace-pre': 'white-space: pre',
742
+ 'whitespace-pre-line': 'white-space: pre-line',
743
+ 'whitespace-pre-wrap': 'white-space: pre-wrap',
744
+ 'break-words': 'overflow-wrap: break-word',
745
+ 'break-all': 'word-break: break-all',
746
+ 'leading-none': 'line-height: 1',
747
+ 'leading-tight': 'line-height: 1.25',
748
+ 'leading-snug': 'line-height: 1.375',
749
+ 'leading-normal': 'line-height: 1.5',
750
+ 'leading-relaxed': 'line-height: 1.625',
751
+ 'leading-loose': 'line-height: 2',
752
+ 'tracking-tighter': 'letter-spacing: -0.05em',
753
+ 'tracking-tight': 'letter-spacing: -0.025em',
754
+ 'tracking-normal': 'letter-spacing: 0em',
755
+ 'tracking-wide': 'letter-spacing: 0.025em',
756
+ 'tracking-wider': 'letter-spacing: 0.05em',
757
+ 'tracking-widest': 'letter-spacing: 0.1em',
758
+ 'antialiased': '-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale',
759
+ 'subpixel-antialiased': '-webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto',
760
+ // Font family (common)
761
+ 'font-sans': "font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
762
+ 'font-serif': "font-family: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif",
763
+ 'font-mono': "font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace",
764
+ // Border
765
+ 'border': 'border-width: 1px',
766
+ 'border-0': 'border-width: 0px',
767
+ 'border-2': 'border-width: 2px',
768
+ 'border-4': 'border-width: 4px',
769
+ 'border-8': 'border-width: 8px',
770
+ 'border-t': 'border-top-width: 1px',
771
+ 'border-r': 'border-right-width: 1px',
772
+ 'border-b': 'border-bottom-width: 1px',
773
+ 'border-l': 'border-left-width: 1px',
774
+ 'border-l-4': 'border-left-width: 4px',
775
+ 'border-solid': 'border-style: solid',
776
+ 'border-dashed': 'border-style: dashed',
777
+ 'border-dotted': 'border-style: dotted',
778
+ 'border-none': 'border-style: none',
779
+ 'border-transparent': 'border-color: transparent',
780
+ 'border-current': 'border-color: currentColor',
781
+ // Border radius
782
+ 'rounded-none': 'border-radius: 0px',
783
+ 'rounded': 'border-radius: 0.25rem',
784
+ 'rounded-xl': 'border-radius: 0.75rem',
785
+ 'rounded-2xl': 'border-radius: 1rem',
786
+ 'rounded-3xl': 'border-radius: 1.5rem',
787
+ 'rounded-full': 'border-radius: 9999px',
788
+ // Note: rounded-sm, rounded-md, rounded-lg use CSS variables for shadcn/ui compatibility
789
+ // They are resolved in resolveTwUtility() with var(--radius)
790
+ // Background
791
+ 'bg-transparent': 'background-color: transparent',
792
+ 'bg-current': 'background-color: currentColor',
793
+ 'bg-white': 'background-color: #ffffff',
794
+ 'bg-black': 'background-color: #000000',
795
+ // Opacity
796
+ 'opacity-0': 'opacity: 0',
797
+ 'opacity-5': 'opacity: 0.05',
798
+ 'opacity-10': 'opacity: 0.1',
799
+ 'opacity-25': 'opacity: 0.25',
800
+ 'opacity-50': 'opacity: 0.5',
801
+ 'opacity-75': 'opacity: 0.75',
802
+ 'opacity-100': 'opacity: 1',
803
+ // Shadow
804
+ 'shadow-sm': 'box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05)',
805
+ 'shadow': 'box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
806
+ 'shadow-md': 'box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
807
+ 'shadow-lg': 'box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
808
+ 'shadow-xl': 'box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
809
+ 'shadow-none': 'box-shadow: 0 0 #0000',
810
+ // Transition
811
+ 'transition': 'transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms',
812
+ 'transition-all': 'transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms',
813
+ 'transition-colors': 'transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms',
814
+ 'transition-opacity': 'transition-property: opacity; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms',
815
+ 'transition-shadow': 'transition-property: box-shadow; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms',
816
+ 'transition-transform': 'transition-property: transform; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms',
817
+ 'transition-none': 'transition-property: none',
818
+ 'ease-linear': 'transition-timing-function: linear',
819
+ 'ease-in': 'transition-timing-function: cubic-bezier(0.4, 0, 1, 1)',
820
+ 'ease-out': 'transition-timing-function: cubic-bezier(0, 0, 0.2, 1)',
821
+ 'ease-in-out': 'transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)',
822
+ // Transform
823
+ 'transform': 'transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))',
824
+ 'transform-none': 'transform: none',
825
+ // Cursor
826
+ 'cursor-auto': 'cursor: auto',
827
+ 'cursor-default': 'cursor: default',
828
+ 'cursor-pointer': 'cursor: pointer',
829
+ 'cursor-wait': 'cursor: wait',
830
+ 'cursor-text': 'cursor: text',
831
+ 'cursor-move': 'cursor: move',
832
+ 'cursor-not-allowed': 'cursor: not-allowed',
833
+ // Pointer events
834
+ 'pointer-events-none': 'pointer-events: none',
835
+ 'pointer-events-auto': 'pointer-events: auto',
836
+ // User select
837
+ 'select-none': 'user-select: none',
838
+ 'select-text': 'user-select: text',
839
+ 'select-all': 'user-select: all',
840
+ 'select-auto': 'user-select: auto',
841
+ // Z-index
842
+ 'z-0': 'z-index: 0',
843
+ 'z-10': 'z-index: 10',
844
+ 'z-20': 'z-index: 20',
845
+ 'z-30': 'z-index: 30',
846
+ 'z-40': 'z-index: 40',
847
+ 'z-50': 'z-index: 50',
848
+ 'z-auto': 'z-index: auto',
849
+ // List style
850
+ 'list-none': 'list-style-type: none',
851
+ 'list-disc': 'list-style-type: disc',
852
+ 'list-decimal': 'list-style-type: decimal',
853
+ 'list-inside': 'list-style-position: inside',
854
+ 'list-outside': 'list-style-position: outside',
855
+ // Visibility
856
+ 'visible': 'visibility: visible',
857
+ 'invisible': 'visibility: hidden',
858
+ // Scroll
859
+ 'scroll-smooth': 'scroll-behavior: smooth',
860
+ 'scroll-auto': 'scroll-behavior: auto',
861
+ // Object fit
862
+ 'object-contain': 'object-fit: contain',
863
+ 'object-cover': 'object-fit: cover',
864
+ 'object-fill': 'object-fit: fill',
865
+ 'object-none': 'object-fit: none',
866
+ // Aspect ratio
867
+ 'aspect-auto': 'aspect-ratio: auto',
868
+ 'aspect-square': 'aspect-ratio: 1 / 1',
869
+ 'aspect-video': 'aspect-ratio: 16 / 9',
870
+ // Outline
871
+ 'outline-none': 'outline: 2px solid transparent; outline-offset: 2px',
872
+ 'outline': 'outline-style: solid',
873
+ // Ring (common)
874
+ 'ring-0': 'box-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color)',
875
+ // Appearance
876
+ 'appearance-none': 'appearance: none',
877
+ // Resize
878
+ 'resize-none': 'resize: none',
879
+ 'resize': 'resize: both',
880
+ 'resize-x': 'resize: horizontal',
881
+ 'resize-y': 'resize: vertical',
882
+ // Text decoration
883
+ 'hover\\:underline': 'text-decoration-line: underline',
884
+ };
885
+ /**
886
+ * Resolve a single Tailwind utility class to CSS.
887
+ * Handles standard utilities, spacing (p-4, m-2, gap-4), colors (text-*, bg-*),
888
+ * and custom theme colors (bg-dark-navy → rgb(var(--color-dark-navy))).
889
+ */
890
+ function resolveTwUtility(cls) {
891
+ // Direct lookup
892
+ if (TW_UTILITIES[cls])
893
+ return TW_UTILITIES[cls];
894
+ // Duration: duration-150, duration-300, etc.
895
+ let m = cls.match(/^duration-(\d+)$/);
896
+ if (m)
897
+ return `transition-duration: ${m[1]}ms`;
898
+ // Delay: delay-150, delay-300, etc.
899
+ m = cls.match(/^delay-(\d+)$/);
900
+ if (m)
901
+ return `transition-delay: ${m[1]}ms`;
902
+ // Spacing: p-{n}, px-{n}, py-{n}, pt-{n}, m-{n}, mx-{n}, my-{n}, mt-{n}, gap-{n}, space-x-{n}
903
+ m = cls.match(/^([pm])([xytblr])?-(\d+(?:\.\d+)?)$/);
904
+ if (m) {
905
+ const prop = m[1] === 'p' ? 'padding' : 'margin';
906
+ const value = `${parseFloat(m[3]) * 0.25}rem`;
907
+ const side = m[2];
908
+ if (!side)
909
+ return `${prop}: ${value}`;
910
+ if (side === 'x')
911
+ return `${prop}-left: ${value}; ${prop}-right: ${value}`;
912
+ if (side === 'y')
913
+ return `${prop}-top: ${value}; ${prop}-bottom: ${value}`;
914
+ const sideMap = { t: 'top', b: 'bottom', l: 'left', r: 'right' };
915
+ return `${prop}-${sideMap[side]}: ${value}`;
916
+ }
917
+ // mx-auto, my-auto
918
+ if (cls === 'mx-auto')
919
+ return 'margin-left: auto; margin-right: auto';
920
+ if (cls === 'my-auto')
921
+ return 'margin-top: auto; margin-bottom: auto';
922
+ if (cls === 'ml-auto')
923
+ return 'margin-left: auto';
924
+ if (cls === 'mr-auto')
925
+ return 'margin-right: auto';
926
+ // Negative spacing: -m-{n}, -mt-{n}, etc.
927
+ m = cls.match(/^-([pm])([xytblr])?-(\d+(?:\.\d+)?)$/);
928
+ if (m) {
929
+ const prop = m[1] === 'p' ? 'padding' : 'margin';
930
+ const value = `-${parseFloat(m[3]) * 0.25}rem`;
931
+ const side = m[2];
932
+ if (!side)
933
+ return `${prop}: ${value}`;
934
+ if (side === 'x')
935
+ return `${prop}-left: ${value}; ${prop}-right: ${value}`;
936
+ if (side === 'y')
937
+ return `${prop}-top: ${value}; ${prop}-bottom: ${value}`;
938
+ const sideMap = { t: 'top', b: 'bottom', l: 'left', r: 'right' };
939
+ return `${prop}-${sideMap[side]}: ${value}`;
940
+ }
941
+ // Gap: gap-{n}
942
+ m = cls.match(/^gap-(\d+(?:\.\d+)?)$/);
943
+ if (m)
944
+ return `gap: ${parseFloat(m[1]) * 0.25}rem`;
945
+ m = cls.match(/^gap-x-(\d+(?:\.\d+)?)$/);
946
+ if (m)
947
+ return `column-gap: ${parseFloat(m[1]) * 0.25}rem`;
948
+ m = cls.match(/^gap-y-(\d+(?:\.\d+)?)$/);
949
+ if (m)
950
+ return `row-gap: ${parseFloat(m[1]) * 0.25}rem`;
951
+ // Width/Height with numbers: w-{n}, h-{n}
952
+ m = cls.match(/^w-(\d+(?:\.\d+)?)$/);
953
+ if (m)
954
+ return `width: ${parseFloat(m[1]) * 0.25}rem`;
955
+ m = cls.match(/^h-(\d+(?:\.\d+)?)$/);
956
+ if (m)
957
+ return `height: ${parseFloat(m[1]) * 0.25}rem`;
958
+ m = cls.match(/^max-w-(\d+)xl$/);
959
+ if (m)
960
+ return `max-width: ${parseInt(m[1]) * 36}rem`;
961
+ if (cls === 'max-w-xs')
962
+ return 'max-width: 20rem';
963
+ if (cls === 'max-w-sm')
964
+ return 'max-width: 24rem';
965
+ if (cls === 'max-w-md')
966
+ return 'max-width: 28rem';
967
+ if (cls === 'max-w-lg')
968
+ return 'max-width: 32rem';
969
+ if (cls === 'max-w-xl')
970
+ return 'max-width: 36rem';
971
+ if (cls === 'max-w-2xl')
972
+ return 'max-width: 42rem';
973
+ if (cls === 'max-w-3xl')
974
+ return 'max-width: 48rem';
975
+ if (cls === 'max-w-4xl')
976
+ return 'max-width: 56rem';
977
+ if (cls === 'max-w-5xl')
978
+ return 'max-width: 64rem';
979
+ if (cls === 'max-w-6xl')
980
+ return 'max-width: 72rem';
981
+ if (cls === 'max-w-7xl')
982
+ return 'max-width: 80rem';
983
+ if (cls === 'max-w-screen-sm')
984
+ return 'max-width: 640px';
985
+ if (cls === 'max-w-screen-md')
986
+ return 'max-width: 768px';
987
+ if (cls === 'max-w-screen-lg')
988
+ return 'max-width: 1024px';
989
+ if (cls === 'max-w-screen-xl')
990
+ return 'max-width: 1280px';
991
+ if (cls === 'max-w-screen-2xl')
992
+ return 'max-width: 1536px';
993
+ if (cls === 'max-w-prose')
994
+ return 'max-width: 65ch';
995
+ // Top/right/bottom/left: top-{n}, inset-x-{n}, etc.
996
+ m = cls.match(/^(top|right|bottom|left)-(\d+(?:\.\d+)?)$/);
997
+ if (m)
998
+ return `${m[1]}: ${parseFloat(m[2]) * 0.25}rem`;
999
+ // Fractional width: w-1/2, w-1/3, etc.
1000
+ m = cls.match(/^w-(\d+)\/(\d+)$/);
1001
+ if (m)
1002
+ return `width: ${(parseInt(m[1]) / parseInt(m[2]) * 100).toFixed(6)}%`;
1003
+ // Line height with numbers
1004
+ m = cls.match(/^leading-(\d+)$/);
1005
+ if (m)
1006
+ return `line-height: ${parseFloat(m[1]) * 0.25}rem`;
1007
+ // Z-index dynamic
1008
+ m = cls.match(/^z-(\d+)$/);
1009
+ if (m)
1010
+ return `z-index: ${m[1]}`;
1011
+ // Min-height with numbers
1012
+ m = cls.match(/^min-h-\[([^\]]+)\]$/);
1013
+ if (m)
1014
+ return `min-height: ${m[1]}`;
1015
+ // Arbitrary values: w-[200px], p-[10px], text-[14px], bg-[#ff0000]
1016
+ m = cls.match(/^([a-z-]+)-\[([^\]]+)\]$/);
1017
+ if (m) {
1018
+ const propMap = {
1019
+ 'w': 'width', 'h': 'height', 'p': 'padding', 'm': 'margin',
1020
+ 'top': 'top', 'right': 'right', 'bottom': 'bottom', 'left': 'left',
1021
+ 'text': 'font-size', 'bg': 'background-color', 'border': 'border-color',
1022
+ 'rounded': 'border-radius', 'gap': 'gap', 'max-w': 'max-width',
1023
+ 'min-w': 'min-width', 'max-h': 'max-height', 'min-h': 'min-height',
1024
+ 'opacity': 'opacity', 'z': 'z-index', 'leading': 'line-height',
1025
+ };
1026
+ if (propMap[m[1]])
1027
+ return `${propMap[m[1]]}: ${m[2]}`;
1028
+ }
1029
+ // Grid columns
1030
+ m = cls.match(/^grid-cols-(\d+)$/);
1031
+ if (m)
1032
+ return `grid-template-columns: repeat(${m[1]}, minmax(0, 1fr))`;
1033
+ m = cls.match(/^col-span-(\d+)$/);
1034
+ if (m)
1035
+ return `grid-column: span ${m[1]} / span ${m[1]}`;
1036
+ // Text colors — theme-aware custom colors (rgb-based CSS vars)
1037
+ // bg-dark-navy, text-slate, text-lightest-slate, border-lightest-navy, etc.
1038
+ const themeColors = [
1039
+ 'dark-navy', 'navy', 'light-navy', 'lightest-navy',
1040
+ 'slate', 'light-slate', 'lightest-slate',
1041
+ 'accent', 'accent-tint', 'green', 'green-tint',
1042
+ ];
1043
+ for (const tc of themeColors) {
1044
+ if (cls === `text-${tc}`)
1045
+ return `color: rgb(var(--color-${tc}))`;
1046
+ if (cls === `bg-${tc}`)
1047
+ return `background-color: rgb(var(--color-${tc}))`;
1048
+ if (cls === `border-${tc}`)
1049
+ return `border-color: rgb(var(--color-${tc}))`;
1050
+ }
1051
+ // shadcn/ui HSL-based CSS variable colors
1052
+ const hslColors = {
1053
+ 'background': '--background', 'foreground': '--foreground',
1054
+ 'primary': '--primary', 'primary-foreground': '--primary-foreground',
1055
+ 'secondary': '--secondary', 'secondary-foreground': '--secondary-foreground',
1056
+ 'muted': '--muted', 'muted-foreground': '--muted-foreground',
1057
+ 'accent': '--accent', 'accent-foreground': '--accent-foreground',
1058
+ 'destructive': '--destructive', 'destructive-foreground': '--destructive-foreground',
1059
+ 'border': '--border', 'input': '--input', 'ring': '--ring',
1060
+ 'card': '--card', 'card-foreground': '--card-foreground',
1061
+ 'popover': '--popover', 'popover-foreground': '--popover-foreground',
1062
+ };
1063
+ for (const [name, cssVar] of Object.entries(hslColors)) {
1064
+ if (cls === `text-${name}`)
1065
+ return `color: hsl(var(${cssVar}))`;
1066
+ if (cls === `bg-${name}`)
1067
+ return `background-color: hsl(var(${cssVar}))`;
1068
+ if (cls === `border-${name}`)
1069
+ return `border-color: hsl(var(${cssVar}))`;
1070
+ if (cls === `ring-${name}`)
1071
+ return `--tw-ring-color: hsl(var(${cssVar}))`;
1072
+ }
1073
+ // Standard Tailwind color palette (bg-red-500, text-blue-600, etc.)
1074
+ const twColorPalette = {
1075
+ 'white': { DEFAULT: '#ffffff' }, 'black': { DEFAULT: '#000000' },
1076
+ 'red': { '50': '#fef2f2', '100': '#fee2e2', '200': '#fecaca', '300': '#fca5a5', '400': '#f87171', '500': '#ef4444', '600': '#dc2626', '700': '#b91c1c', '800': '#991b1b', '900': '#7f1d1d' },
1077
+ 'orange': { '50': '#fff7ed', '100': '#ffedd5', '200': '#fed7aa', '300': '#fdba74', '400': '#fb923c', '500': '#f97316', '600': '#ea580c', '700': '#c2410c', '800': '#9a3412', '900': '#7c2d12' },
1078
+ 'yellow': { '50': '#fefce8', '100': '#fef9c3', '200': '#fef08a', '300': '#fde047', '400': '#facc15', '500': '#eab308', '600': '#ca8a04', '700': '#a16207', '800': '#854d0e', '900': '#713f12' },
1079
+ 'green': { '50': '#f0fdf4', '100': '#dcfce7', '200': '#bbf7d0', '300': '#86efac', '400': '#4ade80', '500': '#22c55e', '600': '#16a34a', '700': '#15803d', '800': '#166534', '900': '#14532d' },
1080
+ 'blue': { '50': '#eff6ff', '100': '#dbeafe', '200': '#bfdbfe', '300': '#93c5fd', '400': '#60a5fa', '500': '#3b82f6', '600': '#2563eb', '700': '#1d4ed8', '800': '#1e40af', '900': '#1e3a8a' },
1081
+ 'gray': { '50': '#f9fafb', '100': '#f3f4f6', '200': '#e5e7eb', '300': '#d1d5db', '400': '#9ca3af', '500': '#6b7280', '600': '#4b5563', '700': '#374151', '800': '#1f2937', '900': '#111827' },
1082
+ 'zinc': { '50': '#fafafa', '100': '#f4f4f5', '200': '#e4e4e7', '300': '#d4d4d8', '400': '#a1a1aa', '500': '#71717a', '600': '#52525b', '700': '#3f3f46', '800': '#27272a', '900': '#18181b' },
1083
+ 'neutral': { '50': '#fafafa', '100': '#f5f5f5', '200': '#e5e5e5', '300': '#d4d4d4', '400': '#a3a3a3', '500': '#737373', '600': '#525252', '700': '#404040', '800': '#262626', '900': '#171717' },
1084
+ };
1085
+ m = cls.match(/^(text|bg|border)-([a-z]+)-(\d{2,3})$/);
1086
+ if (m && twColorPalette[m[2]] && twColorPalette[m[2]][m[3]]) {
1087
+ const propMap = { text: 'color', bg: 'background-color', border: 'border-color' };
1088
+ return `${propMap[m[1]]}: ${twColorPalette[m[2]][m[3]]}`;
1089
+ }
1090
+ // Rounded with CSS variable (shadcn pattern): rounded-lg → var(--radius)
1091
+ if (cls === 'rounded-lg')
1092
+ return 'border-radius: var(--radius)';
1093
+ if (cls === 'rounded-md')
1094
+ return 'border-radius: calc(var(--radius) - 2px)';
1095
+ if (cls === 'rounded-sm')
1096
+ return 'border-radius: calc(var(--radius) - 4px)';
1097
+ return null;
1098
+ }
1099
+ /**
1100
+ * Expand @apply directives in CSS.
1101
+ * Handles responsive prefixes (md:, lg:, etc.) and pseudo-class prefixes (hover:, focus:).
1102
+ */
1103
+ function expandApplyDirectives(css) {
1104
+ return css.replace(/@apply\s+([^;]+);/g, (_match, classList) => {
1105
+ const classes = classList.trim().split(/\s+/);
1106
+ const baseCss = [];
1107
+ const mediaQueries = {};
1108
+ for (const cls of classes) {
1109
+ // Check for responsive prefix: md:text-3xl, lg:flex, etc.
1110
+ let actualCls = cls;
1111
+ let breakpoint = null;
1112
+ const bpMatch = cls.match(/^(sm|md|lg|xl|2xl):(.+)$/);
1113
+ if (bpMatch) {
1114
+ breakpoint = bpMatch[1];
1115
+ actualCls = bpMatch[2];
1116
+ }
1117
+ // Check for pseudo-class prefix: hover:underline, focus:ring, etc. — skip for now
1118
+ if (actualCls.includes(':')) {
1119
+ baseCss.push(`/* @apply: pseudo "${cls}" skipped */`);
1120
+ continue;
1121
+ }
1122
+ const resolved = resolveTwUtility(actualCls);
1123
+ if (resolved) {
1124
+ if (breakpoint && TW_BREAKPOINTS[breakpoint]) {
1125
+ if (!mediaQueries[breakpoint])
1126
+ mediaQueries[breakpoint] = [];
1127
+ mediaQueries[breakpoint].push(resolved);
1128
+ }
1129
+ else {
1130
+ baseCss.push(resolved);
1131
+ }
1132
+ }
1133
+ else {
1134
+ baseCss.push(`/* @apply: unknown "${cls}" */`);
1135
+ }
1136
+ }
1137
+ let result = baseCss.map(p => p + ';').join(' ');
1138
+ // Note: media queries inside @apply can't properly be emitted inline inside
1139
+ // a rule. We emit them as comments with the CSS so users can see what's intended.
1140
+ // The Tailwind Play CDN handles responsive classes in HTML class attributes.
1141
+ for (const [bp, props] of Object.entries(mediaQueries)) {
1142
+ result += ` /* @media(min-width:${TW_BREAKPOINTS[bp]}) { ${props.join('; ')} } */`;
1143
+ }
1144
+ return result;
1145
+ });
1146
+ }
1147
+ /**
1148
+ * Process CSS for Tailwind: strip @tailwind directives, expand @apply, strip @layer wrappers.
1149
+ */
1150
+ function processTailwindCss(css) {
1151
+ // Strip @tailwind directives (Play CDN handles them)
1152
+ css = css.replace(/@tailwind\s+(base|components|utilities)\s*;/g, '/* $& — handled by Tailwind Play CDN */');
1153
+ // Expand @apply directives
1154
+ css = expandApplyDirectives(css);
1155
+ // Unwrap @layer blocks — keep the inner CSS
1156
+ css = css.replace(/@layer\s+(base|components|utilities)\s*\{/g, '/* @layer $1 { */');
1157
+ // Fix matching closing braces — this is tricky for nested braces.
1158
+ // Simple approach: just replace the @layer opening, the browser will handle extra }
1159
+ // Actually, let's use a balanced brace approach
1160
+ css = unwrapLayerBlocks(css);
1161
+ return css;
1162
+ }
1163
+ /**
1164
+ * Unwrap @layer blocks: @layer base { ...css... } → ...css...
1165
+ */
1166
+ function unwrapLayerBlocks(css) {
1167
+ const layerRegex = /@layer\s+(?:base|components|utilities)\s*\{/g;
1168
+ let result = '';
1169
+ let lastIdx = 0;
1170
+ let match;
1171
+ while ((match = layerRegex.exec(css)) !== null) {
1172
+ result += css.slice(lastIdx, match.index);
1173
+ // Find matching closing brace
1174
+ let depth = 1;
1175
+ let i = match.index + match[0].length;
1176
+ while (i < css.length && depth > 0) {
1177
+ if (css[i] === '{')
1178
+ depth++;
1179
+ else if (css[i] === '}')
1180
+ depth--;
1181
+ i++;
1182
+ }
1183
+ // Extract inner content (between { and matching })
1184
+ result += css.slice(match.index + match[0].length, i - 1);
1185
+ lastIdx = i;
1186
+ }
1187
+ result += css.slice(lastIdx);
1188
+ return result;
1189
+ }
1190
+ // ── ViteDevServer ───────────────────────────────────────────────────────
1191
+ export class ViteDevServer {
1192
+ vfs;
1193
+ esbuild;
1194
+ root;
1195
+ onHmrMessage;
1196
+ port;
1197
+ basePath;
1198
+ running = false;
1199
+ moduleCache = new Map();
1200
+ unsubVfs = null;
1201
+ /** True if index.html has an importmap (browser handles bare specifiers) */
1202
+ hasImportmap = false;
1203
+ /** Path aliases from vite.config.ts (e.g. { "@": "./src" }) */
1204
+ aliases;
1205
+ /** Define replacements for esbuild (e.g. { "global": "globalThis" }) */
1206
+ define;
1207
+ /** Whether this project uses TailwindCSS */
1208
+ hasTailwind = false;
1209
+ /** Parsed tailwind config JS (for CDN injection) */
1210
+ tailwindConfigJs = null;
1211
+ /** NPM cache for pre-bundled ESM modules (optional). */
1212
+ npmCache = null;
1213
+ /** Inject React Router basename into entry files? Default: true. */
1214
+ injectBasename;
1215
+ /** Worker env (LOADER, ctx-exports) for the on-demand-bundle facet path.
1216
+ * Null = legacy in-supervisor esbuild fallback. */
1217
+ env;
1218
+ ctx = null;
1219
+ /** Lazily-constructed pool for on-demand bundling. Mirrors the
1220
+ * pre-bundle pool's wasm-modules-map shape. Created on first
1221
+ * cold-path /preview/@modules/<spec> request. */
1222
+ onDemandPool = null;
1223
+ onDemandPoolPromise = null;
1224
+ /**
1225
+ * In-flight on-demand-bundle coalescing map. When the browser fires
1226
+ * multiple parallel requests for the same /preview/@modules/<spec>
1227
+ * (which happens on first preview load — every imported module
1228
+ * resolves concurrently), we want exactly ONE bundle attempt per
1229
+ * spec. The map holds the in-flight Promise<Response> keyed by
1230
+ * cacheKey; subsequent fetches return the same promise. Entry is
1231
+ * deleted when the promise settles so repeat-after-cache-expiry
1232
+ * goes through the cold path again.
1233
+ *
1234
+ * Without coalescing, N parallel requests each build a slice
1235
+ * (~28 MiB) and submit N facet RPCs in parallel. With a shared DO
1236
+ * isolate (Mini-PRD: DO shared isolate issues), the supervisor's
1237
+ * peak heap during page-load = N × slice_size + baseline, which
1238
+ * crashes the supervisor for N≥3 on a busy isolate.
1239
+ */
1240
+ pendingBundles = new Map();
1241
+ /**
1242
+ * Single-slot semaphore for the on-demand bundle slow path. Serializes
1243
+ * slice-walk + facet-dispatch ACROSS DIFFERENT specs so the
1244
+ * supervisor holds at most ONE 28 MiB slice in memory at any time
1245
+ * during a flurry of /preview/@modules/* requests. Coupled with
1246
+ * pendingBundles (same-spec coalescing) this caps peak supervisor
1247
+ * slice memory at 28 MiB regardless of browser parallelism.
1248
+ *
1249
+ * Implementation: a chain of Promise<void> — each waiter awaits the
1250
+ * previous, runs its critical section, then releases. Latency
1251
+ * impact is bounded by per-spec bundle wall time (typically <1 s
1252
+ * for non-barrel packages); the browser's module-fetch parallelism
1253
+ * just becomes serialized at the bundler boundary, not at the wire.
1254
+ */
1255
+ onDemandQueue = Promise.resolve();
1256
+ /**
1257
+ * Primitives wave (P8): the supervisor's per-PID log store. When set
1258
+ * (alongside `pid`), every diagnostic emitted by the dev server is
1259
+ * appended here on the 'stderr' stream so the Process tab UI shows
1260
+ * a real, scrolling, trace of bundler activity. When unset, falls
1261
+ * back to console.warn / console.error only (legacy behaviour).
1262
+ */
1263
+ logPid = null;
1264
+ logStore = null;
1265
+ constructor(opts) {
1266
+ this.vfs = opts.vfs;
1267
+ this.esbuild = opts.esbuild;
1268
+ this.injectBasename = opts.injectBasename !== false;
1269
+ this.logPid = (opts.pid != null) ? opts.pid : null;
1270
+ this.logStore = opts.processLogs || null;
1271
+ // Normalize root: resolve ./, collapse //, strip leading/trailing slashes
1272
+ this.root = opts.root
1273
+ .replace(/\/\.\//g, '/') // /./ → /
1274
+ .replace(/\/\.$/, '') // trailing /.
1275
+ .replace(/\/+/g, '/') // collapse //
1276
+ .replace(/^\/+/, '') // leading /
1277
+ .replace(/\/+$/, ''); // trailing /
1278
+ this.onHmrMessage = opts.onHmrMessage;
1279
+ this.port = opts.port || 5173;
1280
+ this.basePath = (opts.basePath || '/preview').replace(/\/+$/, '');
1281
+ this.aliases = opts.aliases || {};
1282
+ this.env = opts.env;
1283
+ this.ctx = opts.ctx ?? null;
1284
+ if (opts.sql) {
1285
+ this.npmCache = new NpmCache(opts.sql);
1286
+ }
1287
+ // Merge user defines with standard Vite defines.
1288
+ // process.env.NODE_ENV is set explicitly so bundled CJS packages (like React's
1289
+ // `if (process.env.NODE_ENV !== "production")` guards) get the correct branch.
1290
+ // Without this, many packages emit warnings or fail when evaluated in the browser.
1291
+ // BASE_URL reflects the actual URL prefix we're mounted at. With
1292
+ // per-session routing this becomes e.g. `/s/nimble-otter-4271/preview/`,
1293
+ // which user code that does `new URL(path, import.meta.env.BASE_URL)`
1294
+ // or `<img src={import.meta.env.BASE_URL + 'logo.png'}>` needs to work.
1295
+ const baseUrlValue = JSON.stringify((this.basePath === '' || this.basePath === '/') ? '/' : this.basePath + '/');
1296
+ this.define = {
1297
+ 'import.meta.env.DEV': 'true',
1298
+ 'import.meta.env.PROD': 'false',
1299
+ 'import.meta.env.MODE': '"development"',
1300
+ 'import.meta.env.SSR': 'false',
1301
+ 'import.meta.env.BASE_URL': baseUrlValue,
1302
+ 'process.env.NODE_ENV': '"development"',
1303
+ 'global': 'globalThis',
1304
+ ...(opts.define || {}),
1305
+ };
1306
+ this.detectTailwind();
1307
+ }
1308
+ /**
1309
+ * Lazily construct the NimbusLoaderPool used for on-demand bundling
1310
+ * of /preview/@modules/<spec> requests that miss both the in-memory
1311
+ * and pkg_esm_bundles caches. Mirrors the pre-bundle pool's
1312
+ * configuration: 1 worker, internal pLimit not needed (one bundle
1313
+ * per request), wasm shipped via wasmModules.
1314
+ *
1315
+ * Returns null when env/ctx aren't available (legacy fallback used).
1316
+ */
1317
+ async ensureOnDemandPool() {
1318
+ if (this.onDemandPool)
1319
+ return this.onDemandPool;
1320
+ if (this.onDemandPoolPromise)
1321
+ return this.onDemandPoolPromise;
1322
+ if (!this.env || !this.ctx)
1323
+ return null;
1324
+ this.onDemandPoolPromise = (async () => {
1325
+ const { NimbusLoaderPool } = await import('../loaders/loader-pool.js');
1326
+ const { PRE_BUNDLE_PREAMBLE } = await import('../loaders/pre-bundle-preamble.js');
1327
+ const { fetchEsbuildWasmBytes } = await import('../runtime/esbuild-wasm-bytes.js');
1328
+ const wasmBytes = await fetchEsbuildWasmBytes(this.env);
1329
+ const pool = new NimbusLoaderPool(this.env, this.ctx, {
1330
+ concurrency: 1,
1331
+ timeoutMs: 60_000,
1332
+ retries: 0,
1333
+ // Use a distinct tag so the on-demand pool's cached worker
1334
+ // doesn't collide with the install-time pre-bundle pool.
1335
+ // Sharing fnHash + preamble + wasm fingerprint between pools
1336
+ // would otherwise alias them in workerd's loader cache.
1337
+ tag: 'on-demand-bundle',
1338
+ preamble: PRE_BUNDLE_PREAMBLE,
1339
+ wasmModules: { 'esbuild.wasm': wasmBytes },
1340
+ });
1341
+ this.onDemandPool = pool;
1342
+ return pool;
1343
+ })();
1344
+ try {
1345
+ return await this.onDemandPoolPromise;
1346
+ }
1347
+ catch (e) {
1348
+ this.onDemandPoolPromise = null;
1349
+ throw e;
1350
+ }
1351
+ }
1352
+ /** Detect TailwindCSS usage in the project */
1353
+ detectTailwind() {
1354
+ // Check for tailwind config files
1355
+ const configFiles = ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.cjs', 'tailwind.config.mjs'];
1356
+ for (const cfg of configFiles) {
1357
+ const cfgPath = this.root + '/' + cfg;
1358
+ if (this.vfs.exists(cfgPath)) {
1359
+ this.hasTailwind = true;
1360
+ try {
1361
+ let code = this.vfs.readFileString(cfgPath);
1362
+ // Strip require() calls and module.exports, convert to plain object
1363
+ code = code.replace(/require\s*\(\s*["'][^"']*["']\s*\)/g, '[]');
1364
+ code = code.replace(/import\s+.*?from\s+["'][^"']*["']\s*;?/g, '');
1365
+ // Extract the config object — look for export default or module.exports
1366
+ const exportMatch = code.match(/(?:export\s+default|module\.exports\s*=)\s*(\{[\s\S]*\})\s*;?\s*$/);
1367
+ if (exportMatch) {
1368
+ this.tailwindConfigJs = exportMatch[1];
1369
+ }
1370
+ }
1371
+ catch { }
1372
+ break;
1373
+ }
1374
+ }
1375
+ // Also check package.json for tailwindcss dependency
1376
+ if (!this.hasTailwind) {
1377
+ try {
1378
+ const pkgPath = this.root + '/package.json';
1379
+ if (this.vfs.exists(pkgPath)) {
1380
+ const pkg = JSON.parse(this.vfs.readFileString(pkgPath));
1381
+ if (pkg.dependencies?.tailwindcss || pkg.devDependencies?.tailwindcss) {
1382
+ this.hasTailwind = true;
1383
+ }
1384
+ }
1385
+ }
1386
+ catch { }
1387
+ }
1388
+ }
1389
+ /**
1390
+ * Rewrite absolute paths in HTML so they resolve under the basePath.
1391
+ */
1392
+ rewriteHtmlPaths(html) {
1393
+ if (!this.basePath || this.basePath === '/')
1394
+ return html;
1395
+ const base = this.basePath;
1396
+ html = html.replace(/(\s(?:src|href|action)=)(["'])(\/(?!\/)[^"']*)\2/gi, (match, attr, quote, path) => {
1397
+ if (path.startsWith(base + '/') || path === base)
1398
+ return match;
1399
+ return `${attr}${quote}${base}${path}${quote}`;
1400
+ });
1401
+ return html;
1402
+ }
1403
+ /** Start the dev server (subscribe to VFS events for HMR). */
1404
+ start() {
1405
+ if (this.running)
1406
+ return;
1407
+ this.running = true;
1408
+ this.moduleCache.clear();
1409
+ // Subscribe to VFS events for HMR
1410
+ this.unsubVfs = this.vfs.events.on((events) => {
1411
+ this.handleVfsEvents(events);
1412
+ });
1413
+ }
1414
+ /** Stop the dev server. */
1415
+ stop() {
1416
+ this.running = false;
1417
+ if (this.unsubVfs) {
1418
+ this.unsubVfs();
1419
+ this.unsubVfs = null;
1420
+ }
1421
+ this.moduleCache.clear();
1422
+ }
1423
+ get isRunning() { return this.running; }
1424
+ /**
1425
+ * Primitives wave (P8): single chokepoint for dev-server diagnostics.
1426
+ * Always writes to the workerd console (so wrangler tail / Worker
1427
+ * logs see it for ops triage) AND appends to the supervisor's
1428
+ * per-PID ProcessLogStore when one is wired (so the Process tab in
1429
+ * the browser shows the same line, on the same stderr stream, with
1430
+ * the same timestamp ordering). Callers no longer have to remember
1431
+ * to do both.
1432
+ *
1433
+ * Levels:
1434
+ * - 'info': stdout stream of the Process tab; NOT echoed to the
1435
+ * workerd console (would spam wrangler tail). Used for normal
1436
+ * activity — request served, module bundled, HMR fired.
1437
+ * Without this level the Process tab was silent past the
1438
+ * synchronous `vite` builtin banner: the only call sites for
1439
+ * log() were error / warn from cold-bundle failures, so on a
1440
+ * clean Markflow run NOTHING reached subscribers and the tab
1441
+ * froze on the banner content. Markflow regression on prod
1442
+ * 0a488bab.
1443
+ * - 'warn' / 'error': stderr stream + console.warn/error. Used
1444
+ * for cold-path bundle failures, synthetic-entry errors, and
1445
+ * other diagnostics worth surfacing on the workerd console
1446
+ * for ops triage.
1447
+ *
1448
+ * Trailing newline is added if missing so the log buffer is line-
1449
+ * oriented (the Process-tab UI splits on `\n`).
1450
+ */
1451
+ log(level, msg) {
1452
+ if (level !== 'info') {
1453
+ const fn = level === 'error' ? console.error : console.warn;
1454
+ try {
1455
+ fn(msg);
1456
+ }
1457
+ catch { }
1458
+ }
1459
+ if (this.logPid != null && this.logStore) {
1460
+ const text = msg.endsWith('\n') ? msg : msg + '\n';
1461
+ const stream = level === 'info' ? 'stdout' : 'stderr';
1462
+ try {
1463
+ this.logStore.append(this.logPid, stream, text);
1464
+ }
1465
+ catch { }
1466
+ }
1467
+ }
1468
+ /** Handle VFS change events → trigger HMR. */
1469
+ handleVfsEvents(events) {
1470
+ if (!this.running)
1471
+ return;
1472
+ let needsReload = false;
1473
+ let cssOnly = true;
1474
+ let nodeModulesChanged = false;
1475
+ for (const event of events) {
1476
+ if (event.type === 'addDir' || event.type === 'unlinkDir')
1477
+ continue;
1478
+ const path = event.path;
1479
+ this.moduleCache.delete(path);
1480
+ this.moduleCache.delete(this.root + '/' + path);
1481
+ if (path.startsWith(this.root + '/')) {
1482
+ this.moduleCache.delete(path.substring(this.root.length + 1));
1483
+ }
1484
+ if (path.includes('node_modules/')) {
1485
+ nodeModulesChanged = true;
1486
+ }
1487
+ if (path.startsWith(this.root + '/') || path.startsWith(this.root)) {
1488
+ needsReload = true;
1489
+ if (!path.endsWith('.css'))
1490
+ cssOnly = false;
1491
+ }
1492
+ }
1493
+ if (nodeModulesChanged) {
1494
+ const toDelete = [];
1495
+ for (const key of this.moduleCache.keys()) {
1496
+ if (key.startsWith('@modules/'))
1497
+ toDelete.push(key);
1498
+ }
1499
+ for (const k of toDelete)
1500
+ this.moduleCache.delete(k);
1501
+ }
1502
+ if (needsReload) {
1503
+ if (cssOnly) {
1504
+ this.onHmrMessage({ type: 'nimbus-hmr', event: 'css-update' });
1505
+ this.log('info', `[hmr] css-update (${events.length} event${events.length === 1 ? '' : 's'})`);
1506
+ }
1507
+ else {
1508
+ this.onHmrMessage({ type: 'nimbus-hmr', event: 'full-reload' });
1509
+ this.log('info', `[hmr] full-reload (${events.length} event${events.length === 1 ? '' : 's'})`);
1510
+ }
1511
+ }
1512
+ }
1513
+ /** Normalize and sanitize a preview pathname to prevent traversal. */
1514
+ sanitizePath(pathname) {
1515
+ const segments = pathname.split('/').reduce((acc, seg) => {
1516
+ if (seg === '..')
1517
+ acc.pop();
1518
+ else if (seg !== '.' && seg !== '')
1519
+ acc.push(seg);
1520
+ return acc;
1521
+ }, []);
1522
+ return '/' + segments.join('/');
1523
+ }
1524
+ /**
1525
+ * Handle an HTTP request to the dev server.
1526
+ * Called from the DO's fetch() handler for /preview/* paths.
1527
+ *
1528
+ * Wraps `_handleRequestInner` so EVERY served request appears in
1529
+ * the Process tab's stdout stream (status + path + elapsed). Without
1530
+ * this, the tab was silent past the synchronous banner — the dev
1531
+ * server happily processed requests, but no per-request signal
1532
+ * reached subscribers and the user saw a frozen tab. Markflow
1533
+ * regression on prod 0a488bab.
1534
+ */
1535
+ async handleRequest(request, pathname) {
1536
+ const t0 = Date.now();
1537
+ const resp = await this._handleRequestInner(request, pathname);
1538
+ const elapsed = Date.now() - t0;
1539
+ // Strip query for the log line — keeps it scannable. The original
1540
+ // pathname (with query) is what was served; we trim purely for
1541
+ // display.
1542
+ const qIdx = pathname.indexOf('?');
1543
+ const displayPath = qIdx >= 0 ? pathname.substring(0, qIdx) : pathname;
1544
+ this.log('info', `${resp.status} ${displayPath} (${elapsed}ms)`);
1545
+ return resp;
1546
+ }
1547
+ async _handleRequestInner(request, pathname) {
1548
+ const safePath = this.sanitizePath(pathname);
1549
+ if (!safePath) {
1550
+ return new Response('400 Bad Request', { status: 400 });
1551
+ }
1552
+ pathname = safePath;
1553
+ // Strip query parameters for path resolution, keep for logic
1554
+ const queryIdx = pathname.indexOf('?');
1555
+ const query = queryIdx >= 0 ? pathname.substring(queryIdx) : '';
1556
+ if (queryIdx >= 0)
1557
+ pathname = pathname.substring(0, queryIdx);
1558
+ const headers = {
1559
+ 'Access-Control-Allow-Origin': '*',
1560
+ 'Cache-Control': 'no-store',
1561
+ };
1562
+ try {
1563
+ // /@vite/client → HMR client script
1564
+ if (pathname === '/@vite/client' || pathname === '/@vite/client.js') {
1565
+ return new Response('// Nimbus HMR — connected via postMessage\nconsole.log("[nimbus-hmr] client loaded");\n', {
1566
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
1567
+ });
1568
+ }
1569
+ // /__nimbus_assets/tailwind-play.js → vendored Tailwind Play CDN
1570
+ // Served INSTEAD of cdn.tailwindcss.com so preview pages stay
1571
+ // 100% on edge (no third-party CDN fetch from the browser).
1572
+ // Bundle is embedded at build time via scripts/bundle-tailwind-play.mjs;
1573
+ // see src/tailwind-play.generated.ts for the source URL + integrity.
1574
+ if (pathname === '/__nimbus_assets/tailwind-play.js') {
1575
+ return new Response(TAILWIND_PLAY_BUNDLE, {
1576
+ headers: {
1577
+ ...headers,
1578
+ 'Content-Type': 'application/javascript; charset=utf-8',
1579
+ // Immutable: the bundle is version-pinned at build time.
1580
+ // Override the default no-store so browsers cache it
1581
+ // across preview reloads.
1582
+ 'Cache-Control': 'public, max-age=31536000, immutable',
1583
+ 'X-Tailwind-Play-Version': TAILWIND_PLAY_VERSION,
1584
+ },
1585
+ });
1586
+ }
1587
+ // /@modules/<pkg> → resolve from node_modules, transform
1588
+ if (pathname.startsWith('/@modules/')) {
1589
+ const specifier = pathname.substring('/@modules/'.length);
1590
+ // Check if this is actually an alias that got misrouted
1591
+ if (this.aliases) {
1592
+ const resolved = resolveAliasSpecifier(specifier, this.aliases, this.basePath);
1593
+ if (resolved) {
1594
+ // Redirect to the correct path
1595
+ return new Response(null, { status: 302, headers: { ...headers, 'Location': resolved } });
1596
+ }
1597
+ }
1598
+ return this.serveModule(specifier, headers);
1599
+ }
1600
+ // / → serve index.html
1601
+ if (pathname === '/' || pathname === '/index.html') {
1602
+ return this.serveIndexHtml(headers);
1603
+ }
1604
+ // public/ directory — serve static assets as-is
1605
+ const publicPath = this.root + '/public' + pathname;
1606
+ if (this.vfs.exists(publicPath) && !this.vfs.isDirectory(publicPath)) {
1607
+ const ext = pathname.includes('.') ? '.' + pathname.split('.').pop().toLowerCase() : '';
1608
+ const data = this.vfs.readFile(publicPath);
1609
+ return new Response(data, {
1610
+ headers: { ...headers, 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' },
1611
+ });
1612
+ }
1613
+ // Serve from VFS (with transforms for TS/TSX/JSX)
1614
+ return this.serveFile(request, pathname, query, headers);
1615
+ }
1616
+ catch (e) {
1617
+ return new Response(`500 Internal Server Error: ${e?.message}`, {
1618
+ status: 500, headers: { ...headers, 'Content-Type': 'text/plain' },
1619
+ });
1620
+ }
1621
+ }
1622
+ // ── index.html ────────────────────────────────────────────────────────
1623
+ serveIndexHtml(headers) {
1624
+ const htmlPath = this.root + '/index.html';
1625
+ if (!this.vfs.exists(htmlPath)) {
1626
+ return new Response('<!DOCTYPE html><html><body><h1>No index.html found</h1><p>Create index.html in your project root.</p></body></html>', {
1627
+ headers: { ...headers, 'Content-Type': 'text/html; charset=utf-8' },
1628
+ });
1629
+ }
1630
+ let html = this.vfs.readFileString(htmlPath);
1631
+ // Detect importmap
1632
+ this.hasImportmap = html.includes('"importmap"') || html.includes("'importmap'");
1633
+ // Build head injections
1634
+ let headInjections = '';
1635
+ // 1. <base> tag for SPA router support
1636
+ if (this.basePath && this.basePath !== '/') {
1637
+ headInjections += `<base href="${this.basePath}/">\n`;
1638
+ }
1639
+ // 2. Tailwind: serve the vendored Play CDN bundle from our edge.
1640
+ // Was previously `<script src="https://cdn.tailwindcss.com/...">`
1641
+ // — that violated the 100% edge contract by making the browser
1642
+ // fetch a third-party CDN on every preview load. The bundle is
1643
+ // now embedded in the supervisor at build time
1644
+ // (scripts/bundle-tailwind-play.mjs) and served from
1645
+ // /<basePath>/__nimbus_assets/tailwind-play.js with immutable
1646
+ // cache headers. The tailwind.config inline-script is still
1647
+ // placed AFTER the bundle script so the IIFE picks it up.
1648
+ if (this.hasTailwind) {
1649
+ const twUrl = (this.basePath && this.basePath !== '/' ? this.basePath : '') +
1650
+ '/__nimbus_assets/tailwind-play.js';
1651
+ headInjections += `<script src="${twUrl}"></script>\n`;
1652
+ if (this.tailwindConfigJs) {
1653
+ // Inject tailwind config
1654
+ headInjections += `<script>\ntailwind.config = ${this.tailwindConfigJs}\n</script>\n`;
1655
+ }
1656
+ }
1657
+ // 3. HMR client + runtime error overlay.
1658
+ // Overlay is injected alongside HMR because both need to attach global
1659
+ // window listeners before any user modules start loading. The overlay
1660
+ // catches uncaught runtime errors (including "does not provide an export"
1661
+ // module-link failures that leave the root div empty with no console
1662
+ // output) and surfaces them as a red banner so users never see a
1663
+ // mysteriously blank preview.
1664
+ headInjections += ERROR_OVERLAY_CLIENT + '\n';
1665
+ headInjections += HMR_CLIENT + '\n';
1666
+ // Inject before </head>
1667
+ if (html.includes('</head>')) {
1668
+ html = html.replace('</head>', headInjections + '</head>');
1669
+ }
1670
+ else if (html.includes('<body')) {
1671
+ html = html.replace('<body', headInjections + '<body');
1672
+ }
1673
+ else {
1674
+ html = headInjections + html;
1675
+ }
1676
+ // Rewrite absolute paths to include basePath prefix
1677
+ html = this.rewriteHtmlPaths(html);
1678
+ return new Response(html, {
1679
+ headers: { ...headers, 'Content-Type': 'text/html; charset=utf-8' },
1680
+ });
1681
+ }
1682
+ // ── Module serving (/@modules/<pkg>) ──────────────────────────────────
1683
+ async serveModule(specifier, headers) {
1684
+ const JS_CT = 'application/javascript; charset=utf-8';
1685
+ const cacheKey = `@modules/${specifier}`;
1686
+ // 1. In-memory cache (hot path — already bundled)
1687
+ const memCached = this.moduleCache.get(cacheKey);
1688
+ if (memCached) {
1689
+ return new Response(memCached.code, {
1690
+ headers: { ...headers, 'Content-Type': JS_CT },
1691
+ });
1692
+ }
1693
+ // 2. SQLite pre-bundle cache (warm path — from npm install pre-bundling)
1694
+ // Pre-bundled modules may contain leaked bare imports if they were built
1695
+ // with `external` peer deps (see npm-installer.prebundleUsedModules).
1696
+ // Run them through rewriteAllImports so any remaining bare specifiers
1697
+ // get the correct /preview/@modules/ URL prefix, then synthesize named
1698
+ // exports if the bundle only exports default (CJS-only packages).
1699
+ if (this.npmCache) {
1700
+ const esmBundle = this.npmCache.getEsmBundle(specifier);
1701
+ // Only use cached bundles built with the current bundler version.
1702
+ // Stale bundles (from older bundler versions) are treated as missing
1703
+ // and re-bundled on the cold path.
1704
+ if (esmBundle && esmBundle.bundleHash === BUNDLER_VERSION) {
1705
+ let code = esmBundle.esmCode;
1706
+ // Rewrite __require("external") calls to ESM imports so externalized
1707
+ // packages (react, scheduler) actually work in the browser.
1708
+ code = rewriteExternalRequires(code, this.basePath);
1709
+ code = rewriteAllImports(code, this.aliases, this.basePath);
1710
+ code = synthesizeCjsNamedExports(code);
1711
+ this.moduleCache.set(cacheKey, { code, timestamp: Date.now() });
1712
+ return new Response(code, {
1713
+ headers: { ...headers, 'Content-Type': JS_CT },
1714
+ });
1715
+ }
1716
+ }
1717
+ // ── Cold path coalescing + serialization ──────────────────────
1718
+ // Multiple parallel browser requests for the same module are
1719
+ // common on first preview load; coalesce so exactly ONE bundle
1720
+ // attempt runs per spec. Across DIFFERENT specs, serialize via a
1721
+ // single-slot semaphore so peak supervisor slice memory stays at
1722
+ // ~28 MiB regardless of browser parallelism. See
1723
+ // `pendingBundles` and `onDemandQueue` field docs for context.
1724
+ const inflight = this.pendingBundles.get(cacheKey);
1725
+ if (inflight)
1726
+ return inflight;
1727
+ const coldPromise = (async () => {
1728
+ const prev = this.onDemandQueue;
1729
+ let releaseSlot;
1730
+ this.onDemandQueue = new Promise((res) => { releaseSlot = res; });
1731
+ try {
1732
+ await prev;
1733
+ return await this.serveModuleCold(specifier, headers);
1734
+ }
1735
+ finally {
1736
+ releaseSlot();
1737
+ }
1738
+ })();
1739
+ this.pendingBundles.set(cacheKey, coldPromise);
1740
+ coldPromise.finally(() => {
1741
+ // Drop the coalescing entry once settled. Subsequent requests
1742
+ // for the same spec will hit the moduleCache (set inside the
1743
+ // cold path) on the hot path, not re-enter the slow path.
1744
+ this.pendingBundles.delete(cacheKey);
1745
+ });
1746
+ return coldPromise;
1747
+ }
1748
+ /**
1749
+ * Cold path of serveModule: package resolution → on-demand facet
1750
+ * bundle (synthetic-entry for barrels) → hard-error if bundle fails.
1751
+ * NO CDN fallback (100% edge contract). Extracted so the coalescing
1752
+ * + semaphore wrapper in serveModule() reads cleanly. Always runs
1753
+ * inside the on-demand semaphore — see serveModule's wrapper.
1754
+ */
1755
+ async serveModuleCold(specifier, headers) {
1756
+ const JS_CT = 'application/javascript; charset=utf-8';
1757
+ const cacheKey = `@modules/${specifier}`;
1758
+ // 3. On-demand bundle (cold path — resolve from node_modules, bundle via esbuild)
1759
+ //
1760
+ // Architecture: this used to call this.esbuild.build(...) in the
1761
+ // supervisor isolate. For large modules (lucide-react, ~18 MiB
1762
+ // unpacked) that OOM'd the supervisor and surfaced as CF error
1763
+ // 1101 on /preview/@modules/lucide-react, taking down the entire
1764
+ // preview. We now dispatch the bundle work to a NimbusLoaderPool
1765
+ // isolate via its own 128 MiB heap — same pattern as install-time
1766
+ // pre-bundling. Supervisor never bundles esbuild for any path.
1767
+ //
1768
+ // Falls back to in-supervisor esbuild ONLY if env/ctx aren't
1769
+ // available (e.g. legacy callers / tests).
1770
+ const resolved = this.resolvePackage(specifier);
1771
+ // Barrel packages (lucide-react, @phosphor-icons/react, react-icons,
1772
+ // @mui/icons-material, …) ship hundreds/thousands of tiny re-export
1773
+ // files. Bundling the whole barrel OOMs esbuild's 128 MiB facet.
1774
+ // Instead we synthesize a tiny entry from the user's actual named
1775
+ // imports (`export { Home, FileText } from 'pkg'`) and bundle THAT
1776
+ // through the standard facet path — esbuild tree-shakes the rest.
1777
+ // 100% edge: NO esm.sh / CDN fallback.
1778
+ if (resolved) {
1779
+ const pkgName = packageNameFromSpecifier(specifier);
1780
+ const pkgDir = this.root + '/node_modules/' + pkgName;
1781
+ const fileCount = countPackageFiles(this.vfs, pkgDir);
1782
+ const isBarrel = fileCount > BARREL_PKG_FILE_THRESHOLD && specifier === pkgName;
1783
+ let bundled = null;
1784
+ const externals = getSharedRuntimeExternals(specifier);
1785
+ const onDemandPool = await this.ensureOnDemandPool().catch(() => null);
1786
+ // Determine the entry path the facet should bundle:
1787
+ // - non-barrel: the package's resolved main entry
1788
+ // - barrel: a synthesized entry written to VFS that
1789
+ // re-exports just the named imports the user's
1790
+ // source uses. esbuild tree-shakes the rest.
1791
+ let bundleEntryPath = resolved;
1792
+ let synthetic = false;
1793
+ let syntheticReferencedFiles = null;
1794
+ if (isBarrel) {
1795
+ const namedImports = scanNamedImports(this.vfs, this.root);
1796
+ const names = namedImports.get(pkgName);
1797
+ if (!names || names.size === 0) {
1798
+ // No statically-resolvable imports. We refuse to CDN-fallback
1799
+ // (100% edge contract) AND we refuse to bundle the whole
1800
+ // barrel (would OOM the facet). Primitives wave (P6): return
1801
+ // a 200 module that throws on first use so the runtime
1802
+ // error surfaces in the BROWSER console (where the user is
1803
+ // looking) AND on the dev-server log (Process tab) — instead
1804
+ // of an opaque 500 in the network panel.
1805
+ const diag = `[vite-dev] cannot bundle ${specifier}: barrel package (${fileCount} files) ` +
1806
+ `with no static named imports detected in user source. ` +
1807
+ `Dynamic imports / computed-name access can't be tree-shaken. ` +
1808
+ `Add explicit imports like ` +
1809
+ `\`import { IconName } from '${pkgName}'\` so Nimbus can synthesize ` +
1810
+ `a tree-shakable entry.`;
1811
+ this.log('error', diag);
1812
+ const escaped = JSON.stringify(diag);
1813
+ return new Response(`// nimbus: ${diag}\n` +
1814
+ `const __err = new Error(${escaped});\n` +
1815
+ `console.error(${escaped});\n` +
1816
+ `export default new Proxy({}, { get: () => { throw __err; } });\n`, {
1817
+ status: 200,
1818
+ headers: { ...headers, 'Content-Type': JS_CT, 'X-Nimbus-Bundle-Status': 'no-static-imports' },
1819
+ });
1820
+ }
1821
+ const nmDirOnDemand = this.root + '/node_modules';
1822
+ const synth = buildSyntheticEntry(this.vfs, nmDirOnDemand, pkgName, names);
1823
+ if (!synth) {
1824
+ // Primitives wave (P6): emit an honest 200 stub instead of a
1825
+ // 500. The stub has the same effect at runtime (any access
1826
+ // throws), but the network response is a parseable JS module
1827
+ // so the browser's module loader doesn't crash with an
1828
+ // unrelated "failed to fetch" error that obscures the real
1829
+ // diagnostic.
1830
+ const diag = `Nimbus: synthetic entry generation failed for ${specifier} (barrel: ${fileCount} files; ${names.size} static imports). The package's index does not match any pattern Nimbus's barrel synthesizer understands (export {…} from, export * from, top-level decls). File a bug.`;
1831
+ this.log('error', diag);
1832
+ const escaped = JSON.stringify(diag);
1833
+ return new Response(`// nimbus: synthetic entry generation returned null for ${specifier}\n` +
1834
+ `console.error(${escaped});\n` +
1835
+ `const __err = new Error(${escaped});\n` +
1836
+ `export default new Proxy({}, { get: () => { throw __err; } });\n`, { status: 200, headers: { ...headers, 'Content-Type': JS_CT, 'X-Nimbus-Bundle-Status': 'synth-null' } });
1837
+ }
1838
+ const synthPath = syntheticEntryPath(this.root, pkgName);
1839
+ try {
1840
+ this.vfs.mkdir(synthPath.substring(0, synthPath.lastIndexOf('/')), { recursive: true });
1841
+ this.vfs.writeFile(synthPath, synth.code);
1842
+ }
1843
+ catch (e) {
1844
+ // Primitives wave (P6): same 200-stub pattern as the synth-null
1845
+ // branch above. VFS write failure is rare but distinct from a
1846
+ // synthesizer gap, so we keep the diagnostic header value
1847
+ // separate for log triage.
1848
+ const msg = `[vite-dev] failed to write synthetic entry for ${specifier}: ${e?.message || e}`;
1849
+ this.log('error', msg);
1850
+ const escaped = JSON.stringify(msg);
1851
+ return new Response(`// nimbus: ${msg}\nconsole.error(${escaped});\nconst __err = new Error(${escaped});\nexport default new Proxy({}, { get: () => { throw __err; } });\n`, { status: 200, headers: { ...headers, 'Content-Type': JS_CT, 'X-Nimbus-Bundle-Status': 'vfs-write-failed' } });
1852
+ }
1853
+ bundleEntryPath = synthPath;
1854
+ synthetic = true;
1855
+ // Stash the referenced files so the slice-build below uses
1856
+ // a SCOPED slice instead of the standard whole-package walk.
1857
+ syntheticReferencedFiles = synth.referencedFiles;
1858
+ this.log('warn', `[vite-dev] synthesized entry for ${specifier} ` +
1859
+ `(barrel: ${fileCount} files; ${names.size} static imports → tree-shaken bundle)`);
1860
+ }
1861
+ if (onDemandPool) {
1862
+ // Facet path — supervisor stays at 0 esbuild bytes.
1863
+ try {
1864
+ const { buildSliceForSpecifierWithCap, prebundleOne, BUNDLER_VERSION, } = await import('../npm/pre-bundle-facet.js');
1865
+ const SLICE_CAP_BYTES = 28 * 1024 * 1024;
1866
+ const projDir = this.root;
1867
+ const nmDir = projDir + '/node_modules';
1868
+ let slice = null;
1869
+ if (synthetic && syntheticReferencedFiles) {
1870
+ // SCOPED slice: only the files the synthetic entry directly
1871
+ // references (+ transitive relative imports + package.json).
1872
+ // Skips the full package walk so icon-libraries with
1873
+ // thousands of files don't blow the 28 MiB cap.
1874
+ const scoped = buildScopedSliceForSynthetic(this.vfs, nmDir, pkgName, syntheticReferencedFiles);
1875
+ const built = {
1876
+ slice: scoped.entries,
1877
+ totalBytes: scoped.totalBytes,
1878
+ };
1879
+ try {
1880
+ const bytes = this.vfs.readFile(bundleEntryPath);
1881
+ const parentDir = bundleEntryPath.substring(0, bundleEntryPath.lastIndexOf('/'));
1882
+ built.slice.push({ path: '/' + parentDir.replace(/^\/+/, ''), isDir: true });
1883
+ built.slice.push({
1884
+ path: '/' + bundleEntryPath.replace(/^\/+/, ''),
1885
+ bytes,
1886
+ isDir: false,
1887
+ });
1888
+ built.totalBytes += bytes.length + bundleEntryPath.length;
1889
+ }
1890
+ catch (e) {
1891
+ this.log('error', '[vite-dev] synthetic entry unreadable for ' + specifier + ': ' + (e?.message || e));
1892
+ }
1893
+ slice = built;
1894
+ }
1895
+ else {
1896
+ slice = buildSliceForSpecifierWithCap(this.vfs, specifier, nmDir, SLICE_CAP_BYTES);
1897
+ }
1898
+ if (slice) {
1899
+ // Build the spec, then drop our supervisor-side handle to
1900
+ // the slice array immediately — `spec` is the only thing
1901
+ // that needs to keep it alive until the RPC structured-clone
1902
+ // completes. Mirrors the install-time runSlot pattern (see
1903
+ // commit 40cfc01) so peak supervisor heap during a flurry of
1904
+ // /preview/@modules/* requests stays at <(1 × 28 MiB).
1905
+ let spec = {
1906
+ specifier,
1907
+ entryPath: bundleEntryPath,
1908
+ externals,
1909
+ slice: slice.slice,
1910
+ bundlerVersion: BUNDLER_VERSION,
1911
+ define: this.define,
1912
+ };
1913
+ slice = null;
1914
+ let result = null;
1915
+ try {
1916
+ result = await onDemandPool.submit(prebundleOne, spec);
1917
+ }
1918
+ finally {
1919
+ spec = null;
1920
+ }
1921
+ if (result && result.ok && result.esmCode) {
1922
+ bundled = result.esmCode;
1923
+ }
1924
+ else if (result && result.errorText) {
1925
+ this.log('error', '[vite-dev] facet bundle failed for ' + specifier + ': ' + result.errorText);
1926
+ }
1927
+ result = null;
1928
+ }
1929
+ else {
1930
+ this.log('error', '[vite-dev] slice walker exceeded cap for ' + specifier);
1931
+ }
1932
+ }
1933
+ catch (e) {
1934
+ this.log('error', '[vite-dev] on-demand facet dispatch failed for ' + specifier + ': ' + (e?.message || e));
1935
+ }
1936
+ }
1937
+ else {
1938
+ // Legacy fallback — in-supervisor esbuild. Used only when
1939
+ // env/ctx weren't passed in (no facet pool can be built).
1940
+ try {
1941
+ const result = await this.esbuild.build([bundleEntryPath], {
1942
+ bundle: true,
1943
+ format: 'esm',
1944
+ platform: 'browser',
1945
+ target: 'esnext',
1946
+ define: this.define,
1947
+ external: externals.length > 0 ? externals : undefined,
1948
+ });
1949
+ if (result.outputFiles?.length) {
1950
+ bundled = result.outputFiles[0].contents;
1951
+ }
1952
+ }
1953
+ catch (e) {
1954
+ this.log('error', '[vite-dev] esbuild bundle failed for ' + specifier + ': ' + (e?.message || e));
1955
+ }
1956
+ }
1957
+ if (bundled !== null) {
1958
+ let code = bundled;
1959
+ // Convert `__require("external")` calls (from CJS source with esbuild
1960
+ // externals) into ESM `import * as` + dispatch. Without this, the
1961
+ // browser throws when it hits __require("react") at runtime.
1962
+ code = rewriteExternalRequires(code, this.basePath);
1963
+ // Rewrite any bare imports that esbuild marked external (e.g., from
1964
+ // the VFS plugin's exports-field-unaware fallback). Without this, a
1965
+ // bundled output like `import X from "scheduler"` would 404 in the
1966
+ // browser because the specifier doesn't include the base prefix.
1967
+ code = rewriteAllImports(code, this.aliases, this.basePath);
1968
+ // For CJS-only packages (react, react-dom), esbuild's __commonJS
1969
+ // wrapper only emits `export default` — named imports like
1970
+ // `import { createRoot } from "react-dom/client"` would fail. Statically
1971
+ // scan the bundled source for CJS export patterns and synthesize
1972
+ // named exports.
1973
+ code = synthesizeCjsNamedExports(code);
1974
+ // Cache ONLY successful bundles. Caching a failed build (where
1975
+ // `code` was the raw unbundled source) would poison the cache
1976
+ // and break the module forever.
1977
+ if (this.npmCache) {
1978
+ try {
1979
+ this.npmCache.putEsmBundle({
1980
+ specifier,
1981
+ bundleHash: BUNDLER_VERSION,
1982
+ esmCode: code,
1983
+ builtAt: Date.now(),
1984
+ inputHash: '',
1985
+ });
1986
+ }
1987
+ catch { /* non-fatal */ }
1988
+ }
1989
+ this.moduleCache.set(cacheKey, { code, timestamp: Date.now() });
1990
+ return new Response(code, {
1991
+ headers: { ...headers, 'Content-Type': JS_CT },
1992
+ });
1993
+ }
1994
+ // Bundle failed — fall through to the hard-error response below.
1995
+ // Previously fell through to a CDN fallback (esm.sh) but that
1996
+ // violated the 100% edge contract.
1997
+ }
1998
+ // 4. NOT-INSTALLED → STUB; BUNDLE-FAILED → HARD ERROR.
1999
+ // NO CDN fallback (100% edge contract).
2000
+ //
2001
+ // Two distinct failure modes need different responses:
2002
+ //
2003
+ // a. Package not installed locally. This is common for OPTIONAL
2004
+ // peer-deps surfaced by transitive imports — e.g. framer-motion
2005
+ // does `try { import('@emotion/is-prop-valid') } catch {}` for a
2006
+ // runtime feature flag. Pre-bundle's slice walker marks the
2007
+ // unresolved import as external; the browser then fetches
2008
+ // /preview/@modules/@emotion/is-prop-valid. Returning a 503
2009
+ // with `throw new Error(...)` would fail the entire module graph
2010
+ // BEFORE framer-motion's try/catch runs (the throw happens at
2011
+ // module-load time, not at function-call time). Instead we serve
2012
+ // a tiny stub:
2013
+ //
2014
+ // export default undefined;
2015
+ // export const __nimbus_optional_dep_stub = true;
2016
+ //
2017
+ // The browser-side library's runtime feature-detect picks up the
2018
+ // undefined and gracefully degrades. NO crash, NO CDN hit, NO
2019
+ // accidental "you forgot to install X" silent failure (we log
2020
+ // a clear warning to the supervisor for visibility).
2021
+ //
2022
+ // b. Package IS installed but bundling failed (slice cap, OOM,
2023
+ // unresolvable internal import). Return 503 with throwing
2024
+ // module body — the user needs to see the failure to fix it.
2025
+ const pkgName = packageNameFromSpecifier(specifier);
2026
+ const installed = this.vfs.exists(this.root + '/node_modules/' + pkgName);
2027
+ if (!installed) {
2028
+ const stubMsg = `[vite-dev] serving stub for /preview/@modules/${specifier}: ` +
2029
+ `package not installed locally (likely an optional transitive peer-dep). ` +
2030
+ `If your code calls into ${specifier} directly, add it to package.json ` +
2031
+ `and run \`npm install\`.`;
2032
+ this.log('warn', stubMsg);
2033
+ const stubCode = `// nimbus: ${stubMsg}\n` +
2034
+ `export default undefined;\n` +
2035
+ `export const __nimbus_optional_dep_stub = true;\n`;
2036
+ // Cache so repeated transitive references don't re-walk + re-warn.
2037
+ this.moduleCache.set(cacheKey, { code: stubCode, timestamp: Date.now() });
2038
+ return new Response(stubCode, {
2039
+ headers: { ...headers, 'Content-Type': JS_CT },
2040
+ });
2041
+ }
2042
+ const diag = `[vite-dev] cannot serve /preview/@modules/${specifier}: ` +
2043
+ `on-demand bundle failed (esbuild OOM, slice cap, or unresolved internal import). ` +
2044
+ `Check supervisor logs for the underlying error. ` +
2045
+ `If the package is a "barrel" (icon library, etc.), Nimbus ` +
2046
+ `auto-tree-shakes from your static named imports — make sure ` +
2047
+ `you're using \`import { Foo } from '${pkgName}'\` syntax ` +
2048
+ `(not \`import * as X\` or dynamic \`import()\`).`;
2049
+ this.log('error', diag);
2050
+ // Primitives wave (P6): same 200-stub pattern. The supervisor's
2051
+ // own bundle pipeline failed — surface the diagnostic in the
2052
+ // browser console where the user is looking, not in a 503 the
2053
+ // browser's module loader treats as "module-not-found".
2054
+ const escaped = JSON.stringify(diag);
2055
+ const errCode = `// nimbus: ${diag}\nconsole.error(${escaped});\n` +
2056
+ `const __err = new Error(${escaped});\n` +
2057
+ `export default new Proxy({}, { get: () => { throw __err; } });\n`;
2058
+ return new Response(errCode, {
2059
+ status: 200,
2060
+ headers: {
2061
+ ...headers,
2062
+ 'Content-Type': JS_CT,
2063
+ 'Cache-Control': 'no-store',
2064
+ 'X-Nimbus-Bundle-Status': 'bundle-failed',
2065
+ },
2066
+ });
2067
+ }
2068
+ /**
2069
+ * Resolve a bare package specifier (possibly with subpath) to a VFS file path.
2070
+ *
2071
+ * Algorithm:
2072
+ * 1. Parse into pkgName + subpath (e.g. "pkg/sub/deep" → pkg="pkg", subpath="sub/deep")
2073
+ * 2. Walk search dirs looking for node_modules/<pkg>/
2074
+ * 3. PREFER exports-field resolution (modern packages) with conditions
2075
+ * [import, module, browser, default]
2076
+ * 4. FALL BACK to legacy resolution (packages without exports field):
2077
+ * a. For subpath: try <nmDir>/<subpath>.{js,mjs,cjs,jsx,ts,tsx} — direct file
2078
+ * b. For subpath: try <nmDir>/<subpath>/index.{js,mjs,cjs,jsx,ts,tsx}
2079
+ * c. For subpath: try <nmDir>/<subpath>/package.json → read module/main
2080
+ * d. For root: try pkg.module / pkg.main, then tryResolveFile
2081
+ * e. For root: try <nmDir>/index.{ext}
2082
+ *
2083
+ * Step 4c is what makes `react-remove-scroll-bar/constants` work for legacy
2084
+ * packages without an exports field — the subpath directory has its own
2085
+ * package.json (or just an index.js that we pick up in 4b).
2086
+ */
2087
+ resolvePackage(specifier) {
2088
+ let pkgName;
2089
+ let subpath;
2090
+ if (specifier.startsWith('@')) {
2091
+ const parts = specifier.split('/');
2092
+ pkgName = parts.slice(0, 2).join('/');
2093
+ subpath = parts.slice(2).join('/');
2094
+ }
2095
+ else {
2096
+ const parts = specifier.split('/');
2097
+ pkgName = parts[0];
2098
+ subpath = parts.slice(1).join('/');
2099
+ }
2100
+ const searchDirs = [this.root, 'home/user', 'usr/share/pkg'];
2101
+ for (const dir of searchDirs) {
2102
+ const nmDir = dir + '/node_modules/' + pkgName;
2103
+ if (!this.vfs.exists(nmDir))
2104
+ continue;
2105
+ // Read the owning package.json once.
2106
+ const pkgJsonPath = nmDir + '/package.json';
2107
+ let pkg = null;
2108
+ if (this.vfs.exists(pkgJsonPath)) {
2109
+ try {
2110
+ pkg = JSON.parse(this.vfs.readFileString(pkgJsonPath));
2111
+ }
2112
+ catch { /* malformed */ }
2113
+ }
2114
+ // 1. EXPORTS-FIELD RESOLUTION (modern packages like zustand, vfile).
2115
+ // Only attempt if the package actually declares exports. For a subpath
2116
+ // that isn't in exports, resolveExports returns null — we fall through
2117
+ // to legacy resolution rather than treating the miss as fatal.
2118
+ if (pkg?.exports) {
2119
+ const entry = resolvePackageEntry(pkg, subpath ? './' + subpath : '.');
2120
+ if (entry) {
2121
+ // Normalize to collapse any ../ segments from relative entry paths.
2122
+ const resolved = this.tryResolveFile(normalizePath(nmDir + '/' + entry.replace(/^\.\//, '')));
2123
+ if (resolved)
2124
+ return resolved;
2125
+ }
2126
+ }
2127
+ // 2. LEGACY SUBPATH (no exports field, or exports didn't cover this subpath).
2128
+ // Packages like react-remove-scroll-bar expose their structure directly
2129
+ // through `files` and flat directory layout. This block walks the
2130
+ // usual Node.js legacy resolution:
2131
+ // a) <subpath>.{ext} — direct file
2132
+ // b) <subpath>/index.{ext} — directory with index
2133
+ // c) <subpath>/package.json — nested package.json (pkg/module/main)
2134
+ //
2135
+ // Branch (c) is critical for packages like react-remove-scroll-bar
2136
+ // whose `constants/package.json` points OUT of the directory with
2137
+ // `"module": "../dist/es2015/constants.js"`. The resulting path
2138
+ // `constants/../dist/es2015/constants.js` MUST be normalized to
2139
+ // `dist/es2015/constants.js` before VFS lookup.
2140
+ if (subpath) {
2141
+ // (a) + (b) combined — tryResolveFile handles both
2142
+ const direct = this.tryResolveFile(normalizePath(nmDir + '/' + subpath));
2143
+ if (direct)
2144
+ return direct;
2145
+ // (c) nested package.json
2146
+ const nestedPkgPath = nmDir + '/' + subpath + '/package.json';
2147
+ if (this.vfs.exists(nestedPkgPath)) {
2148
+ try {
2149
+ const nested = JSON.parse(this.vfs.readFileString(nestedPkgPath));
2150
+ const entry = nested.module || nested.main || 'index.js';
2151
+ // Normalize to collapse ../ segments — critical when a nested
2152
+ // package.json redirects to a sibling directory (common pattern
2153
+ // for legacy packages shipping both es5 and es2015 builds).
2154
+ const resolved = this.tryResolveFile(normalizePath(nmDir + '/' + subpath + '/' + entry.replace(/^\.\//, '')));
2155
+ if (resolved)
2156
+ return resolved;
2157
+ }
2158
+ catch { /* malformed */ }
2159
+ }
2160
+ }
2161
+ // 3. ROOT ENTRY (no subpath) via module/main fields.
2162
+ if (!subpath && pkg) {
2163
+ const entry = pkg.module || pkg.main;
2164
+ if (entry) {
2165
+ const resolved = this.tryResolveFile(normalizePath(nmDir + '/' + entry.replace(/^\.\//, '')));
2166
+ if (resolved)
2167
+ return resolved;
2168
+ }
2169
+ }
2170
+ // 4. Last resort — <pkgRoot>/index.{ext}
2171
+ const fallback = this.tryResolveFile(nmDir + '/index');
2172
+ if (fallback)
2173
+ return fallback;
2174
+ }
2175
+ return null;
2176
+ }
2177
+ tryResolveFile(base) {
2178
+ // Defense in depth: normalize input even if callers already did. The VFS
2179
+ // treats `..` as a literal path component (no traversal resolution at
2180
+ // lookup time), so any un-normalized `../` in the path will miss.
2181
+ const norm = normalizePath(base);
2182
+ // Covers .cjs and .mts/.cts too — legacy packages use .cjs,
2183
+ // some modern packages use .mts for their ESM build.
2184
+ const exts = ['', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.mts', '.cts', '.json'];
2185
+ for (const ext of exts) {
2186
+ const p = norm + ext;
2187
+ if (this.vfs.exists(p) && !this.vfs.isDirectory(p))
2188
+ return p;
2189
+ }
2190
+ // Directory index fallback.
2191
+ if (this.vfs.exists(norm) && this.vfs.isDirectory(norm)) {
2192
+ const idxExts = ['index.js', 'index.mjs', 'index.cjs', 'index.ts', 'index.tsx', 'index.jsx'];
2193
+ for (const idx of idxExts) {
2194
+ const p = norm + '/' + idx;
2195
+ if (this.vfs.exists(p) && !this.vfs.isDirectory(p))
2196
+ return p;
2197
+ }
2198
+ }
2199
+ return null;
2200
+ }
2201
+ // ── File serving ──────────────────────────────────────────────────────
2202
+ /**
2203
+ * Try to resolve a URL pathname to an actual file in the VFS by applying
2204
+ * Vite/webpack-style extension resolution. This is critical for ES module
2205
+ * imports like `import App from "./App"` where the browser requests
2206
+ * /preview/src/App with no extension.
2207
+ *
2208
+ * Resolution order (matches Vite's default `resolve.extensions` plus .vue/.svelte):
2209
+ * 1. Exact file path
2210
+ * 2. path + .tsx, .ts, .jsx, .js, .mjs, .cjs, .vue, .svelte, .json
2211
+ * 3. path as directory → path/index.{ext} (covering .html for static sites)
2212
+ * 4. For .js/.mjs/.cjs/.jsx specifiers that don't resolve, try .ts/.tsx/.mts/.cts
2213
+ * fallback — common in TypeScript projects with NodeNext module resolution
2214
+ * (imports written as "./bar.js" while source is "./bar.ts")
2215
+ *
2216
+ * Note: `sanitizePath` strips trailing slashes before this runs, so requests
2217
+ * like `/utils/` arrive here as `/utils` — they hit the directory-index branch
2218
+ * via the `isDirectory` check, which is correct.
2219
+ */
2220
+ resolveFileCandidate(pathname) {
2221
+ const basePath = this.root + pathname;
2222
+ // Extension candidates, in preference order (TSX first — most common in React projects).
2223
+ // .vue and .svelte are included for resolver completeness — serving/transforming
2224
+ // them still requires a framework plugin, but at least the resolver finds them.
2225
+ const extensions = ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs', '.vue', '.svelte', '.json'];
2226
+ const indexFiles = [
2227
+ '/index.tsx', '/index.ts', '/index.jsx', '/index.js', '/index.mjs', '/index.cjs',
2228
+ '/index.vue', '/index.svelte', '/index.json', '/index.html',
2229
+ ];
2230
+ // 1. Exact path match
2231
+ if (this.vfs.exists(basePath) && !this.vfs.isDirectory(basePath)) {
2232
+ return { vfsPath: basePath, pathname };
2233
+ }
2234
+ // Determine whether the final path segment already has an extension.
2235
+ // We compute lastDot and lastSlash relative to the path to handle cases like
2236
+ // `/src/v1.0/App` where a dot appears in a DIRECTORY name (lastDot < lastSlash).
2237
+ const lastSlash = pathname.lastIndexOf('/');
2238
+ const lastDot = pathname.lastIndexOf('.');
2239
+ const hasExtension = lastDot > lastSlash && lastDot < pathname.length - 1;
2240
+ // 2. Append extensions if the final segment has no extension.
2241
+ if (!hasExtension) {
2242
+ for (const ext of extensions) {
2243
+ const candidate = basePath + ext;
2244
+ if (this.vfs.exists(candidate) && !this.vfs.isDirectory(candidate)) {
2245
+ return { vfsPath: candidate, pathname: pathname + ext };
2246
+ }
2247
+ }
2248
+ }
2249
+ else {
2250
+ // 4. TypeScript .js → .ts / .tsx fallback for NodeNext projects.
2251
+ const currentExt = pathname.substring(lastDot);
2252
+ const jsToTs = {
2253
+ '.js': ['.ts', '.tsx'],
2254
+ '.mjs': ['.mts', '.ts'],
2255
+ '.cjs': ['.cts', '.ts'],
2256
+ '.jsx': ['.tsx'],
2257
+ };
2258
+ const fallbacks = jsToTs[currentExt];
2259
+ if (fallbacks) {
2260
+ const withoutExt = pathname.substring(0, lastDot);
2261
+ for (const tsExt of fallbacks) {
2262
+ const candidate = this.root + withoutExt + tsExt;
2263
+ if (this.vfs.exists(candidate) && !this.vfs.isDirectory(candidate)) {
2264
+ return { vfsPath: candidate, pathname: withoutExt + tsExt };
2265
+ }
2266
+ }
2267
+ }
2268
+ }
2269
+ // 3. Directory → /index.{ext}
2270
+ if (this.vfs.exists(basePath) && this.vfs.isDirectory(basePath)) {
2271
+ for (const idx of indexFiles) {
2272
+ const candidate = basePath + idx;
2273
+ if (this.vfs.exists(candidate) && !this.vfs.isDirectory(candidate)) {
2274
+ return { vfsPath: candidate, pathname: pathname + idx };
2275
+ }
2276
+ }
2277
+ }
2278
+ return null;
2279
+ }
2280
+ /**
2281
+ * Decide whether a 404 on this request should fall back to index.html (SPA
2282
+ * routing) or stay as 404. We MUST NOT return HTML for JS module requests —
2283
+ * the browser rejects them with a MIME-type error and the whole app breaks.
2284
+ *
2285
+ * Strategy: trust `Sec-Fetch-Dest` (set by all modern browsers) as the
2286
+ * primary signal. Fall back to `Accept` header analysis + tight source-path
2287
+ * heuristics for edge cases like old clients or non-browser fetchers.
2288
+ *
2289
+ * We intentionally DO NOT treat every path under `/api/`, `/hooks/`,
2290
+ * `/components/`, etc. as a module — those are common client-side route
2291
+ * names in real React/Vue apps, and marking them as module would 404 legit
2292
+ * navigation. Only paths rooted under unambiguous build-system directories
2293
+ * (`/src/`, `/node_modules/`, `/@vite/`, `/@modules/`, `/@fs/`, `/public/`,
2294
+ * `/assets/`) are treated as definitely-not-SPA.
2295
+ */
2296
+ isModuleRequest(request, pathname, query) {
2297
+ // 1. Explicit module query marker (?import)
2298
+ if (query.includes('import'))
2299
+ return true;
2300
+ // 2. Sec-Fetch-Dest — most reliable signal. All modern browsers set this:
2301
+ // 'document' / 'iframe' → top-level navigation (SPA fallback OK)
2302
+ // 'script' / 'style' / 'font' / 'image' / 'audio' / 'video' / 'worker'
2303
+ // / 'object' / 'embed' / 'manifest' / 'track' → subresource (no HTML)
2304
+ // 'empty' → fetch() — treat as module to avoid MIME errors
2305
+ const secFetchDest = request.headers.get('Sec-Fetch-Dest');
2306
+ if (secFetchDest) {
2307
+ if (secFetchDest === 'document' || secFetchDest === 'iframe') {
2308
+ // Explicit navigation — not a module request
2309
+ return false;
2310
+ }
2311
+ // Anything else (script, style, empty, etc.) is a subresource.
2312
+ return true;
2313
+ }
2314
+ // 3. Has-extension check — any recognizable file extension means
2315
+ // "this is a specific file, not a SPA route". Catches .js, .css,
2316
+ // .svg, .png, .woff2, etc. Uses slash-aware dot detection to avoid
2317
+ // misidentifying paths with dotted directory segments.
2318
+ const lastSlash = pathname.lastIndexOf('/');
2319
+ const lastDot = pathname.lastIndexOf('.');
2320
+ if (lastDot > lastSlash && lastDot < pathname.length - 1) {
2321
+ return true;
2322
+ }
2323
+ // 4. Unambiguous build-system source directories. Trimmed from the prior
2324
+ // aggressive list — `/components/`, `/pages/`, `/hooks/`, `/utils/`,
2325
+ // `/api/` etc. are common SPA route names in real apps and should NOT
2326
+ // force module treatment based on path alone.
2327
+ const sourcePatterns = [
2328
+ '/src/', '/node_modules/', '/@vite/', '/@modules/', '/@fs/',
2329
+ '/public/', '/assets/',
2330
+ ];
2331
+ for (const pat of sourcePatterns) {
2332
+ if (pathname.includes(pat))
2333
+ return true;
2334
+ }
2335
+ // 5. Accept header. A genuine browser navigation always includes `text/html`.
2336
+ // fetch()'s default '*/*' indicates a programmatic request — treat as module.
2337
+ const accept = request.headers.get('Accept') || '';
2338
+ if (accept.includes('text/html'))
2339
+ return false;
2340
+ if (accept === '*/*' || accept === '')
2341
+ return true;
2342
+ if (accept.includes('application/javascript') || accept.includes('application/json'))
2343
+ return true;
2344
+ if (accept.includes('text/css'))
2345
+ return true;
2346
+ return false;
2347
+ }
2348
+ async serveFile(request, pathname, query, headers) {
2349
+ let vfsPath = this.root + pathname;
2350
+ // If the exact path doesn't exist, try Vite-style extension resolution.
2351
+ // This is essential for ES module imports like `import X from "./foo"` —
2352
+ // the browser fetches /preview/src/foo with no extension, and we need to
2353
+ // try .tsx/.ts/.jsx/.js/.mjs/.cjs/.json and directory index files.
2354
+ if (!this.vfs.exists(vfsPath) || (this.vfs.isDirectory(vfsPath) && !pathname.endsWith('/'))) {
2355
+ const resolved = this.resolveFileCandidate(pathname);
2356
+ if (resolved) {
2357
+ vfsPath = resolved.vfsPath;
2358
+ pathname = resolved.pathname;
2359
+ }
2360
+ else {
2361
+ // Nothing matched. Only fall back to index.html for true navigation
2362
+ // requests — never for module/asset/style fetches (which would get a
2363
+ // MIME-type error in the browser and break everything).
2364
+ if (this.isModuleRequest(request, pathname, query)) {
2365
+ return new Response('404 Not Found: ' + pathname, {
2366
+ status: 404, headers: { ...headers, 'Content-Type': 'text/plain' },
2367
+ });
2368
+ }
2369
+ return this.serveIndexHtml(headers);
2370
+ }
2371
+ }
2372
+ if (this.vfs.isDirectory(vfsPath)) {
2373
+ const indexPath = vfsPath + '/index.html';
2374
+ if (this.vfs.exists(indexPath)) {
2375
+ let html = this.vfs.readFileString(indexPath);
2376
+ if (html.includes('</head>')) {
2377
+ html = html.replace('</head>', ERROR_OVERLAY_CLIENT + '\n' + HMR_CLIENT + '\n</head>');
2378
+ }
2379
+ html = this.rewriteHtmlPaths(html);
2380
+ return new Response(html, {
2381
+ headers: { ...headers, 'Content-Type': 'text/html; charset=utf-8' },
2382
+ });
2383
+ }
2384
+ return new Response('403 Directory listing not supported', {
2385
+ status: 403, headers,
2386
+ });
2387
+ }
2388
+ // Extract extension using slash-aware dot detection. Naive `split('.').pop()`
2389
+ // returns garbage for paths with dotted directories like `/src/v1.0/App`
2390
+ // (would produce `ext = '.0/app'`). We only accept a dot that occurs AFTER
2391
+ // the final slash and has at least one character after it.
2392
+ const pExtLastSlash = pathname.lastIndexOf('/');
2393
+ const pExtLastDot = pathname.lastIndexOf('.');
2394
+ const ext = (pExtLastDot > pExtLastSlash && pExtLastDot < pathname.length - 1)
2395
+ ? pathname.substring(pExtLastDot).toLowerCase()
2396
+ : '';
2397
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream';
2398
+ // Transform TS/TSX/JSX/MTS/CTS files
2399
+ if (ext === '.ts' || ext === '.tsx' || ext === '.jsx' || ext === '.mts' || ext === '.cts') {
2400
+ return this.serveTransformed(vfsPath, ext, headers);
2401
+ }
2402
+ // CSS handling
2403
+ if (ext === '.css') {
2404
+ let css = this.vfs.readFileString(vfsPath);
2405
+ // Resolve @import "./other.css" (but pass through @import url(...) for Google Fonts etc.)
2406
+ const cssDir = vfsPath.includes('/') ? vfsPath.substring(0, vfsPath.lastIndexOf('/')) : this.root;
2407
+ css = css.replace(/@import\s+["']([^"']+)["']\s*;/g, (_match, importPath) => {
2408
+ if (importPath.startsWith('http'))
2409
+ return _match;
2410
+ const resolvedPath = importPath.startsWith('/')
2411
+ ? this.root + importPath
2412
+ : cssDir + '/' + importPath;
2413
+ try {
2414
+ return this.vfs.readFileString(resolvedPath);
2415
+ }
2416
+ catch {
2417
+ return `/* @import "${importPath}" not found */`;
2418
+ }
2419
+ });
2420
+ // Process Tailwind CSS directives if project uses Tailwind
2421
+ if (this.hasTailwind) {
2422
+ css = processTailwindCss(css);
2423
+ }
2424
+ // CSS-as-JS module: if ?import query, wrap in JS that injects <style>
2425
+ if (query.includes('import')) {
2426
+ const escapedCss = css.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
2427
+ const jsCode = `(function() {
2428
+ var id = ${JSON.stringify(this.basePath + pathname)};
2429
+ var existing = document.querySelector('style[data-vite-dev-id="' + id + '"]');
2430
+ if (existing) { existing.textContent = \`${escapedCss}\`; return; }
2431
+ var s = document.createElement('style');
2432
+ s.setAttribute('data-vite-dev-id', id);
2433
+ s.textContent = \`${escapedCss}\`;
2434
+ document.head.appendChild(s);
2435
+ })();`;
2436
+ return new Response(jsCode, {
2437
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2438
+ });
2439
+ }
2440
+ return new Response(css, {
2441
+ headers: { ...headers, 'Content-Type': 'text/css; charset=utf-8' },
2442
+ });
2443
+ }
2444
+ // JSON files: serve as ES module when imported from JS
2445
+ if (ext === '.json') {
2446
+ const accept = request.headers.get('Accept') || '';
2447
+ // If requested as a module (via import), wrap as ES module
2448
+ if (accept.includes('application/javascript') || query.includes('import')) {
2449
+ const json = this.vfs.readFileString(vfsPath);
2450
+ const code = `export default ${json};`;
2451
+ return new Response(code, {
2452
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2453
+ });
2454
+ }
2455
+ // Otherwise serve as plain JSON
2456
+ const data = this.vfs.readFile(vfsPath);
2457
+ return new Response(data, {
2458
+ headers: { ...headers, 'Content-Type': contentType },
2459
+ });
2460
+ }
2461
+ // JS files: rewrite imports (cached)
2462
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
2463
+ const cached = this.moduleCache.get(vfsPath);
2464
+ if (cached) {
2465
+ return new Response(cached.code, {
2466
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2467
+ });
2468
+ }
2469
+ let code = this.vfs.readFileString(vfsPath);
2470
+ if (!this.hasImportmap) {
2471
+ // Pass importer context so `#X` subpath imports in this user JS
2472
+ // file resolve against its owning package.json.
2473
+ const importerCtx = {
2474
+ importerVfsPath: vfsPath,
2475
+ root: this.root,
2476
+ vfs: this.vfs,
2477
+ };
2478
+ code = rewriteAllImports(code, this.aliases, this.basePath, importerCtx);
2479
+ }
2480
+ this.moduleCache.set(vfsPath, { code, timestamp: Date.now() });
2481
+ return new Response(code, {
2482
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2483
+ });
2484
+ }
2485
+ // Asset imports from JS: serve as URL-exporting module
2486
+ if (ASSET_EXTS.has(ext) && query.includes('import')) {
2487
+ const url = this.basePath + pathname;
2488
+ const code = `export default ${JSON.stringify(url)};`;
2489
+ return new Response(code, {
2490
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2491
+ });
2492
+ }
2493
+ // Other files: serve as-is (binary)
2494
+ const data = this.vfs.readFile(vfsPath);
2495
+ return new Response(data, { headers: { ...headers, 'Content-Type': contentType } });
2496
+ }
2497
+ // ── TS/TSX/JSX transform ──────────────────────────────────────────────
2498
+ async serveTransformed(vfsPath, ext, headers) {
2499
+ const cached = this.moduleCache.get(vfsPath);
2500
+ if (cached) {
2501
+ return new Response(cached.code, {
2502
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2503
+ });
2504
+ }
2505
+ let code = this.vfs.readFileString(vfsPath);
2506
+ // Auto-inject React Router `basename` into entry files so user links
2507
+ // like <NavLink to="/x"> correctly resolve to `${basePath}/x`. Safe
2508
+ // no-op if the file doesn't reference createBrowserRouter/<BrowserRouter>
2509
+ // or the user has opted out (explicit basename / line-leading comment /
2510
+ // vite.config.ts nimbusInjectBasename: false).
2511
+ if (this.injectBasename && shouldProcessForRouter(vfsPath)) {
2512
+ try {
2513
+ code = injectRouterBasename(code, this.basePath);
2514
+ }
2515
+ catch (e) {
2516
+ // Never let the transform break serving — log and continue with original.
2517
+ console.warn('[vite-dev] basename injection skipped for', vfsPath, ':', e?.message);
2518
+ }
2519
+ }
2520
+ const loader = ext === '.tsx' ? 'tsx' : ext === '.jsx' ? 'jsx' : 'ts';
2521
+ try {
2522
+ const hasPreact = code.includes('from "preact"') || code.includes("from 'preact'") || code.includes('from "preact/');
2523
+ const jsxOpts = hasPreact
2524
+ ? { jsx: 'transform', jsxFactory: 'h', jsxFragment: 'Fragment' }
2525
+ : { jsx: 'automatic' };
2526
+ const result = await this.esbuild.transform(code, {
2527
+ loader,
2528
+ format: 'esm',
2529
+ target: 'esnext',
2530
+ ...jsxOpts,
2531
+ define: this.define,
2532
+ sourcemap: 'inline',
2533
+ });
2534
+ code = result.code;
2535
+ }
2536
+ catch (e) {
2537
+ const errMsg = (e?.message || String(e)).replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
2538
+ code = `
2539
+ console.error(\`[nimbus-vite] Transform error:\\n${errMsg}\`);
2540
+ if (!document.getElementById('nimbus-error-overlay')) {
2541
+ const d = document.createElement('div');
2542
+ d.id = 'nimbus-error-overlay';
2543
+ d.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.85);color:#ff6b6b;font-family:monospace;padding:32px;overflow:auto;white-space:pre-wrap;font-size:14px;line-height:1.6';
2544
+ d.innerHTML = '<div style="max-width:800px;margin:0 auto"><h2 style="color:#ff6b6b;margin-bottom:16px">Transform Error</h2><pre style="color:#e4e4e7;background:#1a1a2e;padding:16px;border-radius:8px;overflow-x:auto">' + \`${errMsg}\`.replace(/</g,'&lt;') + '</pre><p style="color:#666;margin-top:16px">Fix the error and save. The page will reload.</p></div>';
2545
+ d.onclick = () => d.remove();
2546
+ document.body.appendChild(d);
2547
+ }\n`;
2548
+ return new Response(code, {
2549
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2550
+ });
2551
+ }
2552
+ // Rewrite all imports: CSS ?import, aliases, bare → /@modules/,
2553
+ // dynamic, AND `package.json#imports` (subpath imports `#X`).
2554
+ // Pass the importer context so `#X` resolves against the user's
2555
+ // owning package.json — the rare-but-real case where a user .ts/
2556
+ // .tsx file directly uses a Node subpath import.
2557
+ if (!this.hasImportmap) {
2558
+ const importerCtx = {
2559
+ importerVfsPath: vfsPath,
2560
+ root: this.root,
2561
+ vfs: this.vfs,
2562
+ };
2563
+ code = rewriteAllImports(code, this.aliases, this.basePath, importerCtx);
2564
+ }
2565
+ this.moduleCache.set(vfsPath, { code, timestamp: Date.now() });
2566
+ return new Response(code, {
2567
+ headers: { ...headers, 'Content-Type': 'application/javascript; charset=utf-8' },
2568
+ });
2569
+ }
2570
+ get stats() {
2571
+ return {
2572
+ running: this.running,
2573
+ port: this.port,
2574
+ root: this.root,
2575
+ cachedModules: this.moduleCache.size,
2576
+ hasTailwind: this.hasTailwind,
2577
+ aliases: Object.keys(this.aliases),
2578
+ };
2579
+ }
2580
+ }