@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,2681 @@
1
+ /**
2
+ * facets/manager.ts — Lifecycle for "user node script as a child DO".
3
+ *
4
+ * `node script.js` from the shell prompt has to run somewhere isolated
5
+ * — same memory bound as the supervisor (128 MiB) but separate so a
6
+ * runaway script can't take the supervisor down. The script is also
7
+ * stateful: `fs.readFileSync` writes inside the script need to flow
8
+ * back to the VFS, and `http.createServer` registers a port the
9
+ * preview iframe later proxies to. That makes a stateless Worker
10
+ * Loader insufficient — we need a child DO with its own SQLite for
11
+ * port registration and exit reporting.
12
+ *
13
+ * The pattern:
14
+ * 1. LOADER.get(codeHash, makeConfig) — dynamic worker w/ user script
15
+ * 2. worker.getDurableObjectClass('NodeProcess') — class from that worker
16
+ * 3. ctx.facets.get(`proc-${pid}`, {class}) — child DO Facet
17
+ * 4. facet.run(argsJson) — RPC executes the script
18
+ * 5. ctx.facets.delete(name) — cleanup, even on throw
19
+ *
20
+ * The codeHash includes `ctx.id.toString()` to scope the LOADER cache
21
+ * per-supervisor-DO. Without that, two sessions executing identical
22
+ * code would share an isolate, and the warm slot's baked-in
23
+ * env.SUPERVISOR stub would point at whichever DO instantiated it
24
+ * first — every other session's stdout would silently route to the
25
+ * wrong terminal. (Same cross-session-slot-sharing fix b225db3
26
+ * applied to install-time facets.)
27
+ *
28
+ * `_execViaLoader` is the local-dev fallback: when the runtime doesn't
29
+ * support `ctx.facets.get`, fall back to `LOADER.load()` +
30
+ * `getEntrypoint().fetch()` with a plain fetch-handler shape. The
31
+ * supervisor's environment is the same; only the lifecycle primitive
32
+ * differs.
33
+ */
34
+ import { generateShimsCode } from '../runtime/node-shims.js';
35
+ import { getRealNodeImportsCode } from '../_shared/real-node-imports.js';
36
+ import { getCtxExports } from '../session/ctx-exports.js';
37
+ import { prefetchForRequire } from '../runtime/require-resolver.js';
38
+ import { recordFailure, getLastRpcFrame, getLastFacetId } from '../observability/oom-discriminator.js';
39
+ import { classifyError } from '../observability/oom-classify.js';
40
+ import { EsbuildService } from '../runtime/esbuild-service.js';
41
+ import { CF_COMPAT_DATE, FACET_TIMEOUT_MS, VFS_BUNDLE_MAX_FILES, VFS_BUNDLE_MAX_BYTES, BUNDLE_MAX_ENCODED_BYTES, } from '../constants.js';
42
+ /**
43
+ * Detect & restore a Uint8Array that's been JSON-mangled to a
44
+ * {"0":n,"1":n,...} object during the result-envelope round-trip.
45
+ * String inputs and already-Uint8Array inputs pass through unchanged.
46
+ *
47
+ * Heuristic: a plain object whose keys are dense non-negative integers
48
+ * starting at 0 and whose values are byte-sized integers is treated as
49
+ * a serialized Uint8Array. False-positive risk is negligible because
50
+ * (a) only `__vfsWrites` cells reach this path and (b) the only types
51
+ * that ever land in `__vfsWrites` are string and Uint8Array.
52
+ */
53
+ function _reviveVfsWriteCell(v) {
54
+ if (typeof v === 'string')
55
+ return v;
56
+ if (v instanceof Uint8Array)
57
+ return v;
58
+ if (v && typeof v === 'object') {
59
+ const o = v;
60
+ const keys = Object.keys(o);
61
+ if (keys.length === 0)
62
+ return new Uint8Array(0);
63
+ // Quick bail-out: not all keys are non-negative integers.
64
+ let maxIdx = -1;
65
+ for (const k of keys) {
66
+ const n = Number(k);
67
+ if (!Number.isInteger(n) || n < 0)
68
+ return String(v);
69
+ if (n > maxIdx)
70
+ maxIdx = n;
71
+ }
72
+ // Dense check: keys.length === maxIdx + 1
73
+ if (keys.length !== maxIdx + 1)
74
+ return String(v);
75
+ const out = new Uint8Array(keys.length);
76
+ for (let i = 0; i < keys.length; i++) {
77
+ const b = o[String(i)];
78
+ if (typeof b !== 'number' || b < 0 || b > 255)
79
+ return String(v);
80
+ out[i] = b;
81
+ }
82
+ return out;
83
+ }
84
+ return String(v);
85
+ }
86
+ // ── Code generators ─────────────────────────────────────────────────────
87
+ const SHIMS = generateShimsCode();
88
+ /**
89
+ * Static `import * as __real_X from 'node:X'` block. Prepended to both
90
+ * facet-code templates (NodeProcess + LOADER.load fallback) so the
91
+ * SHIMS string can forward to workerd's real `node:*` builtins.
92
+ * See src/_shared/real-node-imports.ts for the rationale + matrix.
93
+ */
94
+ const REAL_NODE_IMPORTS = getRealNodeImportsCode();
95
+ /** Simple hash for deduplicating identical code across invocations. */
96
+ function hashCode(s) {
97
+ let h = 0;
98
+ for (let i = 0; i < s.length; i++)
99
+ h = ((h << 5) - h + s.charCodeAt(i)) | 0;
100
+ return 'code-' + (h >>> 0).toString(36);
101
+ }
102
+ /**
103
+ * Generate vite dev server facet code.
104
+ * Long-running dynamic worker that serves files via SUPERVISOR RPC.
105
+ * Transforms TS/TSX/JSX via SUPERVISOR.transform() (esbuild in supervisor).
106
+ */
107
+ function generateViteFacetCode(root, basePath) {
108
+ const safeRoot = JSON.stringify(root);
109
+ const safeBase = JSON.stringify(basePath);
110
+ return `
111
+ const ROOT = ${safeRoot};
112
+ const BASE = ${safeBase};
113
+
114
+ const MIME = {
115
+ '.html':'text/html;charset=utf-8', '.js':'application/javascript;charset=utf-8',
116
+ '.mjs':'application/javascript;charset=utf-8', '.ts':'application/javascript;charset=utf-8',
117
+ '.tsx':'application/javascript;charset=utf-8', '.jsx':'application/javascript;charset=utf-8',
118
+ '.css':'text/css;charset=utf-8', '.json':'application/json;charset=utf-8',
119
+ '.svg':'image/svg+xml', '.png':'image/png', '.jpg':'image/jpeg',
120
+ '.ico':'image/x-icon', '.woff2':'font/woff2', '.txt':'text/plain;charset=utf-8',
121
+ };
122
+
123
+ const HMR_CLIENT = '<script type="module">window.addEventListener("message",e=>{if(e.data?.type==="nimbus-hmr"){if(e.data.event==="full-reload")location.reload();if(e.data.event==="css-update")document.querySelectorAll("link[rel=stylesheet]").forEach(l=>{l.href=l.href.split("?")[0]+"?t="+Date.now();})}});console.log("[nimbus-hmr] connected");</script>';
124
+
125
+ function ext(p) { const i = p.lastIndexOf('.'); return i > 0 ? p.substring(i) : ''; }
126
+ function strip(p) { return p.replace(/^\\/+/,''); }
127
+
128
+ export default {
129
+ async fetch(request, workerEnv) {
130
+ const sup = workerEnv?.SUPERVISOR;
131
+ const url = new URL(request.url);
132
+ let pathname = url.pathname;
133
+ const headers = { 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-store' };
134
+
135
+ try {
136
+ // / or /index.html → serve HTML with HMR + path rewriting
137
+ if (pathname === '/' || pathname === '/index.html') {
138
+ const htmlPath = strip(ROOT + '/index.html');
139
+ let html = sup ? await sup.readFile(htmlPath) : null;
140
+ if (!html) return new Response('<!DOCTYPE html><html><body><h1>No index.html</h1></body></html>', { headers: {...headers, 'Content-Type': 'text/html;charset=utf-8'} });
141
+ // Inject HMR client
142
+ if (html.includes('</head>')) html = html.replace('</head>', HMR_CLIENT + '</head>');
143
+ else html = HMR_CLIENT + html;
144
+ // Rewrite absolute paths to include basePath
145
+ if (BASE && BASE !== '/') {
146
+ html = html.replace(/(\\s(?:src|href|action)=)(["'])(\\/((?!\\/)[^"']*))(\\2)/gi, (m,attr,q,path) => {
147
+ if (path.startsWith(BASE+'/') || path === BASE) return m;
148
+ return attr + q + BASE + path + q;
149
+ });
150
+ }
151
+ // Detect importmap (skip bare import rewriting if present)
152
+ const hasImportmap = html.includes('"importmap"');
153
+ return new Response(html, { headers: {...headers, 'Content-Type': 'text/html;charset=utf-8'}, status: 200 });
154
+ }
155
+
156
+ // Static/transformed file serving
157
+ const vfsPath = strip(ROOT + pathname);
158
+ const e = ext(pathname);
159
+
160
+ // TS/TSX/JSX → transform via SUPERVISOR.transform()
161
+ if (e === '.ts' || e === '.tsx' || e === '.jsx') {
162
+ let code = sup ? await sup.readFile(vfsPath) : null;
163
+ if (!code) return new Response('Not found: ' + pathname, { status: 404, headers });
164
+ // Detect JSX framework
165
+ const hasPreact = code.includes('from "preact"') || code.includes("from 'preact'");
166
+ const loader = e === '.tsx' ? 'tsx' : e === '.jsx' ? 'jsx' : 'ts';
167
+ if (sup) {
168
+ try {
169
+ const result = await sup.transform(code, loader);
170
+ if (result) code = result.code;
171
+ } catch {}
172
+ }
173
+ return new Response(code, { headers: {...headers, 'Content-Type': 'application/javascript;charset=utf-8'} });
174
+ }
175
+
176
+ // CSS
177
+ if (e === '.css') {
178
+ const css = sup ? await sup.readFile(vfsPath) : null;
179
+ if (!css) return new Response('Not found', { status: 404, headers });
180
+ return new Response(css, { headers: {...headers, 'Content-Type': 'text/css;charset=utf-8'} });
181
+ }
182
+
183
+ // JS files
184
+ if (e === '.js' || e === '.mjs') {
185
+ const code = sup ? await sup.readFile(vfsPath) : null;
186
+ if (!code) return new Response('Not found', { status: 404, headers });
187
+ return new Response(code, { headers: {...headers, 'Content-Type': 'application/javascript;charset=utf-8'} });
188
+ }
189
+
190
+ // Other files
191
+ if (sup) {
192
+ const content = await sup.readFile(vfsPath);
193
+ if (content) {
194
+ const ct = MIME[e] || 'application/octet-stream';
195
+ return new Response(content, { headers: {...headers, 'Content-Type': ct} });
196
+ }
197
+ }
198
+
199
+ // SPA fallback
200
+ const accept = request.headers.get('Accept') || '';
201
+ if (!pathname.includes('.') && (accept.includes('text/html') || accept.includes('*/*'))) {
202
+ // Recurse to serve index.html
203
+ return this.fetch(new Request(url.origin + '/', request), workerEnv);
204
+ }
205
+
206
+ return new Response('Not found: ' + pathname, { status: 404, headers });
207
+ } catch (e) {
208
+ return new Response('Vite facet error: ' + (e?.message || e), { status: 500, headers });
209
+ }
210
+ }
211
+ };
212
+ `;
213
+ }
214
+ /**
215
+ * Generate dynamic worker code with a DurableObject class.
216
+ * This is used with LOADER.get() + ctx.facets.get() (production path).
217
+ */
218
+ function generateFacetCode(userCode, vfsState) {
219
+ const safeCode = JSON.stringify(userCode);
220
+ // hardening-r5: route through binary-safe serializer so Uint8Array
221
+ // cells survive the module-text round-trip.
222
+ const safeBundle = _serializeBundleForFacet(vfsState.bundle);
223
+ const safeManifest = JSON.stringify(vfsState.manifest);
224
+ return `
225
+ import { DurableObject } from "cloudflare:workers";
226
+ ${REAL_NODE_IMPORTS}
227
+
228
+ const USER_CODE = ${safeCode};
229
+
230
+ // X.5-S: conditional-param-rename wrap for new Function. esbuild ESM→CJS
231
+ // transform output (W3.5 Fix B) preserves any source-level
232
+ // \`const __dirname = path.dirname(fileURLToPath(import.meta.url))\` line
233
+ // (vite's open@10 idiom), which collides at parse time with a hardcoded
234
+ // \`__dirname\` parameter. Rename the param slot to a placeholder when
235
+ // the body declares the same name — slot alignment is preserved
236
+ // (callers pass positional args; dropping would mis-align downstream
237
+ // slots), and the body's own binding becomes the single declarer.
238
+ //
239
+ // Generalised by nuxt-process-redeclare wave (2026-05-11) to also cover
240
+ // the 7 extra-param shim names (\`process\`, \`console\`, \`Buffer\`,
241
+ // \`setTimeout\`, \`setInterval\`, \`clearTimeout\`, \`clearInterval\`).
242
+ // Pre-fix, the two-pass esbuild ESM→CJS path emitted \`const process =
243
+ // (() => { const _m = require("node:process"); ... })();\` in the
244
+ // requires block for \`import process from 'node:process'\` (nuxi.mjs
245
+ // shape). That const declaration sits at the top-level of the wrapped
246
+ // function body and collides with the \`process\` param at parse time:
247
+ // SyntaxError: Identifier 'process' has already been declared.
248
+ // Same class for the other 6 shim names. esbuild's single-pass CJS
249
+ // path is unaffected (it renames the user's binding to \`process$1\`).
250
+ //
251
+ // Regex extended from (const|let|var) to (const|let|var|function|class)
252
+ // as defensive coverage for future shapes — current convertEsmImportsToRequire
253
+ // only emits \`const\` in the requires block, but the broader regex
254
+ // guards against drift.
255
+ //
256
+ // See VERIFY-23417C5 §4 #1, audit/sections/X5S-plan.md §3,
257
+ // .seal-internal/2026-05-11-nuxt-process-redeclare/plan.md §6.
258
+ function __mkCompiledFn(code, extraParams) {
259
+ function renameIfDeclared(name) {
260
+ const re = new RegExp("(?:^|\\\\n|;)\\\\s*(?:const|let|var|function|class)\\\\s+" + name + "\\\\b", "m");
261
+ return re.test(code) ? name + "__nimbus_unused" : name;
262
+ }
263
+ const baseParams = [
264
+ "exports", "require", "module",
265
+ renameIfDeclared("__filename"),
266
+ renameIfDeclared("__dirname"),
267
+ ];
268
+ const renamedExtras = (extraParams || []).map(renameIfDeclared);
269
+ return new Function(...baseParams, ...renamedExtras, code);
270
+ }
271
+
272
+ const __compiledFn = __mkCompiledFn(USER_CODE, [
273
+ "console", "process", "Buffer",
274
+ "setTimeout", "setInterval", "clearTimeout", "clearInterval",
275
+ "global", "__args",
276
+ ]);
277
+
278
+ // VFS bundle + manifest + pre-compiled modules — all at module level (startup time).
279
+ // __MODULE_VFS_BUNDLE = capped path→content (workerd module-size budget).
280
+ // __MODULE_VFS_MANIFEST = uncapped path→child-names map for directory shape
281
+ // (W2.5b root-cause fix — see facet-manager.ts:453).
282
+ const __MODULE_VFS_BUNDLE = ${safeBundle};
283
+ const __MODULE_VFS_MANIFEST = ${safeManifest};
284
+ const __compiledModules = new Map();
285
+ // W3.5 Fix C: stop swallowing pre-compile errors. Record the path → error
286
+ // message so __loadModule can surface the real reason (typically
287
+ // SyntaxError on ESM source) instead of the misleading "file was not
288
+ // pre-bundled" message at request time.
289
+ const __compileFailures = new Map();
290
+ // G2/G3 (runtime-pkg wave): sub-modules receive the SAME shim params
291
+ // as the entry. Pre-fix, sub-module precompiled fns took only
292
+ // (exports, require, module, __filename, __dirname); their references
293
+ // to process, console, Buffer resolved up the V8 scope chain to
294
+ // workerd's GLOBAL bindings — so process.exit() inside any require'd
295
+ // file hit workerd's real process API and produced 'Canceling the
296
+ // request', and console.log bypassed our supervisor streaming.
297
+ // Adding the extras to the param list shadows globals inside every
298
+ // sub-module body. require.main is also threaded so bins doing
299
+ // 'if (require.main === module)' route correctly through the
300
+ // scopedRequire chain (G2 cowsay).
301
+ for (const [__p, __c] of Object.entries(__MODULE_VFS_BUNDLE)) {
302
+ if (__p.endsWith(".js") || __p.endsWith(".mjs") || __p.endsWith(".cjs")) {
303
+ try {
304
+ __compiledModules.set(__p, __mkCompiledFn(__c, [
305
+ "console", "process", "Buffer",
306
+ "setTimeout", "setInterval", "clearTimeout", "clearInterval",
307
+ ]));
308
+ } catch (__e) {
309
+ __compileFailures.set(__p, __e && __e.message ? __e.message : String(__e));
310
+ }
311
+ }
312
+ }
313
+
314
+ class __ProcessExit extends Error {
315
+ constructor(code) { super("process.exit(" + code + ")"); this.code = code; }
316
+ }
317
+
318
+ export class NodeProcess extends DurableObject {
319
+ async run(argsJson) {
320
+ const args = JSON.parse(argsJson);
321
+ const { argv, env, cwd: _cwd, filename, dirname } = args;
322
+ const __vfsBundle = __MODULE_VFS_BUNDLE;
323
+ const __vfsManifest = __MODULE_VFS_MANIFEST;
324
+ const __supervisor = this.env?.SUPERVISOR || null;
325
+ const __pendingIO = [];
326
+ // Fix 6 orphan-detection counters: every supervisor RPC push catches
327
+ // its own rejection (necessary for allSettled semantics), but that
328
+ // previously swallowed the drop silently. Count them so reportExit
329
+ // can include a summary in its tail, and the supervisor's ring
330
+ // buffer has proof that output was lost.
331
+ let __rpcDrops = 0;
332
+ let __rpcDropBytes = 0;
333
+ let __rpcLastError = "";
334
+ const __onRpcDrop = (bytes, e) => {
335
+ __rpcDrops++;
336
+ __rpcDropBytes += bytes | 0;
337
+ if (e) { __rpcLastError = (e && e.message) || String(e); }
338
+ };
339
+ let cwd = _cwd || "/home/user";
340
+ let stdout = "", stderr = "";
341
+ let exitCode = 0;
342
+ const __vfsWrites = {};
343
+ const __vfsDirs = {};
344
+
345
+ ${SHIMS}
346
+
347
+ // Override console AND process.stdout/stderr for live SUPERVISOR streaming
348
+ if (__supervisor) {
349
+ __consoleMod.log = (...a) => { const s = __utilMod.format(...a) + "\\n"; stdout += s; __pendingIO.push(__supervisor.stdout(s).catch((e) => __onRpcDrop(s.length, e))); };
350
+ __consoleMod.error = (...a) => { const s = __utilMod.format(...a) + "\\n"; stderr += s; __pendingIO.push(__supervisor.stderr(s).catch((e) => __onRpcDrop(s.length, e))); };
351
+ __consoleMod.warn = __consoleMod.error;
352
+ __consoleMod.info = __consoleMod.log;
353
+ __consoleMod.debug = __consoleMod.log;
354
+ // Hook process.stdout/stderr.write for live streaming (libraries use this directly)
355
+ __processMod.stdout.write = (d) => { const s = String(d); stdout += s; __pendingIO.push(__supervisor.stdout(s).catch((e) => __onRpcDrop(s.length, e))); return true; };
356
+ __processMod.stderr.write = (d) => { const s = String(d); stderr += s; __pendingIO.push(__supervisor.stderr(s).catch((e) => __onRpcDrop(s.length, e))); return true; };
357
+ }
358
+
359
+ // ── console-facet (2026-05-12): expose the shimmed console on
360
+ // globalThis so user code can reach it via BOTH \`console.log(...)\`
361
+ // (resolved through the __compiledFn positional 'console' param)
362
+ // AND \`globalThis.console.log(...)\` (resolved through the global
363
+ // scope). Pre-fix, only the positional binding was patched; calls
364
+ // through globalThis hit workerd's native console which writes
365
+ // to the worker log (NOT our supervisor stdout/stderr stream) so
366
+ // the output was lost from the user's perspective.
367
+ //
368
+ // Verbatim repro on prod:
369
+ // $ node -e 'console.log("bare")' -> "bare"
370
+ // $ node -e 'globalThis.console.log("global")' -> (silent)
371
+ // $ node -e 'console.log(globalThis.console === console)' -> false
372
+ //
373
+ // workerd's globalThis.console descriptor is writable+configurable
374
+ // (verified empirically — see smoketest-globalthis-write.mjs in
375
+ // /workspace/.seal-internal/2026-05-12-console-facet/), so a plain
376
+ // assignment is sufficient. Do NOT touch globalThis.process /
377
+ // globalThis.Buffer here — Pyodide / Ruby bootstrap paths detect a
378
+ // Node-shaped env via globalThis.process and would take the wrong
379
+ // code path; see AGENTS.md gotcha #4.
380
+ try { globalThis.console = __consoleMod; } catch { /* readonly fallback */ }
381
+
382
+ // ── Unhandled rejection / uncaught error capture ──────────────────
383
+ //
384
+ // The try/catch around __compiledFn below catches only SYNCHRONOUS
385
+ // exceptions. Async fire-and-forget rejections (e.g.
386
+ // import('./missing').then(({main}) => main()) // no .catch
387
+ // ) fire during the microtask drain and are neither caught nor
388
+ // reported \u2014 pre-fix the facet exits exitCode=0 with empty stderr,
389
+ // and the W5 zero-silent-OOM contract only catches non-zero exits.
390
+ //
391
+ // workerd's globalThis supports the standard WHATWG events:
392
+ // - 'unhandledrejection' \u2014 fired on .reject() with no .catch
393
+ // - 'error' \u2014 uncaught synchronous errors on the
394
+ // global scope (e.g. errors thrown in
395
+ // setTimeout-scheduled callbacks that
396
+ // escape the __compiledFn try/catch)
397
+ //
398
+ // This listener converts each event into a stderr line + bumps
399
+ // exitCode to 1 (only on first event; subsequent events still
400
+ // append stderr). Mirrors real Node's default
401
+ // unhandled-rejection-is-fatal behaviour.
402
+ //
403
+ // See .seal-internal/2026-05-11-unhandled-rejection/audit.md.
404
+ let __unhandledFired = false;
405
+ const __reportUnhandled = (label, reason) => {
406
+ // framework-fixes-F3 (2026-05-12): suppress reports whose reason
407
+ // is a __ProcessExit. process.exit(N) throws __ProcessExit by
408
+ // design (see __processMod.exit in node-shims.ts:2158) to halt
409
+ // user code. The outer try/catch at __compiledFn unwraps it for
410
+ // the SYNCHRONOUS case, but async fire-and-forget paths (e.g.
411
+ // create-remix's deprecation banner that calls process.exit(0)
412
+ // from an async main() with no .catch()) bubble it to the
413
+ // unhandled-rejection listener. Pre-fix that produced a noisy
414
+ // 'Unhandled promise rejection: Error: process.exit(0)' line
415
+ // alongside the expected exit. exitCode is already set correctly
416
+ // by the __processMod.exit shim AND by the catch in main run loop.
417
+ //
418
+ // Concrete repro: npm create remix@latest prints a deprecation
419
+ // banner from upstream + process.exit(0). User sees the banner
420
+ // (the user-facing semantic the upstream tool emits) and exit 0
421
+ // (correct), but ALSO the rejection noise (artifact of our shim
422
+ // shape). This filter removes the noise without changing exit
423
+ // semantics.
424
+ //
425
+ // The filter matches BOTH the class identity (reason instanceof
426
+ // __ProcessExit) AND the message shape (reason.message starts
427
+ // with 'process.exit(') because in async paths the unhandled-
428
+ // rejection event sometimes loses the prototype chain across
429
+ // microtask hops.
430
+ if (reason instanceof __ProcessExit) return;
431
+ if (reason && typeof reason.message === "string" &&
432
+ /^process\\.exit\\(/.test(reason.message)) return;
433
+ const text = reason && reason.stack
434
+ ? String(reason.stack)
435
+ : (reason && reason.message ? String(reason.message) : String(reason));
436
+ const line = label + ": " + text + "\\n";
437
+ stderr += line;
438
+ if (__supervisor) {
439
+ try {
440
+ __pendingIO.push(__supervisor.stderr(line).catch((e) =>
441
+ __onRpcDrop(line.length, e)));
442
+ } catch {}
443
+ }
444
+ if (!__unhandledFired) {
445
+ __unhandledFired = true;
446
+ if (exitCode === 0) exitCode = 1;
447
+ }
448
+ };
449
+ globalThis.addEventListener("unhandledrejection", (event) => {
450
+ __reportUnhandled("Unhandled promise rejection", event && event.reason);
451
+ });
452
+ globalThis.addEventListener("error", (event) => {
453
+ // ErrorEvent \u2014 uncaught synchronous error on the global scope.
454
+ // The try/catch around __compiledFn captures errors thrown DURING
455
+ // its synchronous execution; this listener catches errors from
456
+ // setTimeout/setInterval/microtask callbacks that escape the
457
+ // entry scope.
458
+ __reportUnhandled("Uncaught", event && (event.error || event.message));
459
+ });
460
+
461
+ const mod = { exports: {} };
462
+ // G2 (runtime-pkg wave): bins commonly check
463
+ // if (require.main === module) { main(); }
464
+ // — the canonical 'is this file being executed directly?' guard.
465
+ // Pre-fix, __require.main was null; the check was always false;
466
+ // bins like cowsay silently treated themselves as imported and
467
+ // never ran main(). Setting require.main = entry's module here
468
+ // (and to the loaded module on each __loadModule call below)
469
+ // makes the check true exactly when the file is the entry.
470
+ __require.main = mod;
471
+ try {
472
+ __compiledFn(
473
+ mod.exports, __require, mod, filename || "/home/user/script.js", dirname || "/home/user",
474
+ __consoleMod, __processMod, __BufferMod,
475
+ globalThis.setTimeout, globalThis.setInterval, globalThis.clearTimeout, globalThis.clearInterval,
476
+ globalThis, argv || []
477
+ );
478
+ } catch (e) {
479
+ if (e instanceof __ProcessExit) { exitCode = e.code; }
480
+ else {
481
+ const trace = (e && e.stack) || (e && e.message) || String(e);
482
+ stderr += trace + "\\n";
483
+ exitCode = 1;
484
+ // Stream the trace live so it lands in the supervisor ring buffer.
485
+ // Without this, the error is visible only in the local 'stderr'
486
+ // string which gets zeroed below when __supervisor is attached,
487
+ // leaving the user with an empty prompt after a crash.
488
+ if (__supervisor) {
489
+ try { __pendingIO.push(__supervisor.stderr(trace + "\\n").catch((e2) => __onRpcDrop((trace || "").length + 1, e2))); } catch {}
490
+ }
491
+ }
492
+ }
493
+
494
+ // Drain microtasks
495
+ await new Promise(r => setTimeout(r, 0));
496
+
497
+ // Flush writes via SUPERVISOR RPC if available (live VFS writes)
498
+ const __failedWrites = {};
499
+ if (__supervisor && Object.keys(__vfsWrites).length > 0) {
500
+ for (const [path, content] of Object.entries(__vfsWrites)) {
501
+ __pendingIO.push(
502
+ __supervisor.writeFile(path, content).catch(() => { __failedWrites[path] = content; })
503
+ );
504
+ }
505
+ }
506
+
507
+ // Await all pending I/O (live stdout, writes)
508
+ if (__pendingIO.length > 0) {
509
+ await Promise.allSettled(__pendingIO);
510
+ }
511
+
512
+ // Fix 6: a second drain pass. If any setTimeout-scheduled callback
513
+ // fired during the first allSettled and pushed more writes, they sit
514
+ // on __pendingIO past the first drain. Yield once more, then re-settle
515
+ // if new items arrived. Capped at one additional round so a run-away
516
+ // setInterval can't hold the facet open forever.
517
+ const __preSecondDrain = __pendingIO.length;
518
+ await new Promise(r => setTimeout(r, 0));
519
+ if (__pendingIO.length > __preSecondDrain) {
520
+ await Promise.allSettled(__pendingIO.slice(__preSecondDrain));
521
+ }
522
+
523
+ // W8 BLOCKER-1 fix: parent-exit synchronous flush of any live
524
+ // child_process children. Without this, output from spawn-and-forget
525
+ // children (e.g., concurrently 'echo a' 'echo b' from a parent that
526
+ // exits before its data listeners drain) gets dropped between the
527
+ // last cpReadOutput poll and reportExit. __cpDrainAllChildren issues
528
+ // a single cpDrainOutput RPC per live child to flush remaining
529
+ // buffers; idempotent on already-exited children.
530
+ try {
531
+ if (__childProcessMod && typeof __childProcessMod.__cpDrainAllChildren === "function") {
532
+ await __childProcessMod.__cpDrainAllChildren();
533
+ }
534
+ } catch (e) {
535
+ // Best-effort. Drain failure must not block reportExit.
536
+ }
537
+
538
+ // Report exit AFTER I/O drains so the ring buffer has everything the
539
+ // facet wrote before the dump fires on the supervisor side.
540
+ //
541
+ // Fix 6: include an orphan-drop tail in the exit report so the
542
+ // supervisor knows how many writes (and bytes) were lost, plus the
543
+ // last RPC error for debugging. Empty string when clean. Pid is
544
+ // intentionally omitted — the supervisor already knows the pid
545
+ // (it's the RPC parameter) and prepends its own banner.
546
+ if (__supervisor) {
547
+ let __tail = "";
548
+ if (__rpcDrops > 0) {
549
+ __tail = "[orphan output: " + __rpcDrops + " dropped RPC write(s), ~" +
550
+ __rpcDropBytes + " bytes lost" +
551
+ (__rpcLastError ? "; last error: " + __rpcLastError : "") + "]\\n";
552
+ }
553
+ try { await __supervisor.reportExit(exitCode, __tail); } catch {}
554
+ }
555
+
556
+ // Return results:
557
+ // - When SUPERVISOR streamed output live, return empty stdout/stderr
558
+ // (prevents double display in the terminal)
559
+ // - Failed writes fall back to the old vfsWrites path for supervisor-side flush
560
+ return JSON.stringify({
561
+ exitCode,
562
+ stdout: __supervisor ? "" : stdout,
563
+ stderr: __supervisor ? "" : stderr,
564
+ vfsWrites: __supervisor ? __failedWrites : __vfsWrites,
565
+ });
566
+ }
567
+ }
568
+ `;
569
+ }
570
+ /**
571
+ * Generate fallback code with a plain fetch handler.
572
+ * Used with LOADER.load() when facets are unavailable (local dev).
573
+ */
574
+ function generateEntrypointCode(userCode, vfsState) {
575
+ const safeCode = JSON.stringify(userCode);
576
+ // hardening-r5: same binary-safe serializer as generateFacetCode.
577
+ const safeBundle = _serializeBundleForFacet(vfsState.bundle);
578
+ const safeManifest = JSON.stringify(vfsState.manifest);
579
+ return `
580
+ ${REAL_NODE_IMPORTS}
581
+
582
+ const USER_CODE = ${safeCode};
583
+
584
+ // X.5-S: conditional-param-rename wrap. Kept byte-equivalent to
585
+ // generateFacetCode's helper so both pre-compile loops see the same
586
+ // diagnostic surface. See generateFacetCode for the rationale and the
587
+ // nuxt-process-redeclare-wave generalisation rationale.
588
+ function __mkCompiledFn(code, extraParams) {
589
+ function renameIfDeclared(name) {
590
+ const re = new RegExp("(?:^|\\\\n|;)\\\\s*(?:const|let|var|function|class)\\\\s+" + name + "\\\\b", "m");
591
+ return re.test(code) ? name + "__nimbus_unused" : name;
592
+ }
593
+ const baseParams = [
594
+ "exports", "require", "module",
595
+ renameIfDeclared("__filename"),
596
+ renameIfDeclared("__dirname"),
597
+ ];
598
+ const renamedExtras = (extraParams || []).map(renameIfDeclared);
599
+ return new Function(...baseParams, ...renamedExtras, code);
600
+ }
601
+
602
+ const __compiledFn = __mkCompiledFn(USER_CODE, [
603
+ "console", "process", "Buffer",
604
+ "setTimeout", "setInterval", "clearTimeout", "clearInterval",
605
+ "global", "__args",
606
+ ]);
607
+
608
+ // VFS bundle + manifest + pre-compiled modules — all at module level (startup time).
609
+ // See generateFacetCode for the rationale; this is the fallback (LOADER.load)
610
+ // path used when facets API is unavailable.
611
+ const __MODULE_VFS_BUNDLE = ${safeBundle};
612
+ const __MODULE_VFS_MANIFEST = ${safeManifest};
613
+ const __compiledModules = new Map();
614
+ // W3.5 Fix C: keep this template byte-equivalent to generateFacetCode's
615
+ // pre-compile loop so __loadModule sees the same diagnostic surface.
616
+ // G2/G3 (runtime-pkg wave): same extra-params shim as the facet API
617
+ // path above — see comment block there for the rationale.
618
+ const __compileFailures = new Map();
619
+ for (const [__p, __c] of Object.entries(__MODULE_VFS_BUNDLE)) {
620
+ if (__p.endsWith(".js") || __p.endsWith(".mjs") || __p.endsWith(".cjs")) {
621
+ try {
622
+ __compiledModules.set(__p, __mkCompiledFn(__c, [
623
+ "console", "process", "Buffer",
624
+ "setTimeout", "setInterval", "clearTimeout", "clearInterval",
625
+ ]));
626
+ } catch (__e) {
627
+ __compileFailures.set(__p, __e && __e.message ? __e.message : String(__e));
628
+ }
629
+ }
630
+ }
631
+
632
+ class __ProcessExit extends Error {
633
+ constructor(code) { super("process.exit(" + code + ")"); this.code = code; }
634
+ }
635
+
636
+ export default {
637
+ async fetch(request, workerEnv) {
638
+ const args = await request.json();
639
+ const { argv, env, cwd: _cwd, filename, dirname } = args;
640
+ const __vfsBundle = __MODULE_VFS_BUNDLE;
641
+ const __vfsManifest = __MODULE_VFS_MANIFEST;
642
+ const __supervisor = workerEnv?.SUPERVISOR || null;
643
+ const __pendingIO = [];
644
+ // Fix 6 orphan counters (same as NodeProcess.run) — count RPC writes
645
+ // that get dropped during isolate teardown so reportExit can report them.
646
+ let __rpcDrops = 0;
647
+ let __rpcDropBytes = 0;
648
+ let __rpcLastError = "";
649
+ const __onRpcDrop = (bytes, e) => {
650
+ __rpcDrops++;
651
+ __rpcDropBytes += bytes | 0;
652
+ if (e) { __rpcLastError = (e && e.message) || String(e); }
653
+ };
654
+ let cwd = _cwd || "/home/user";
655
+ let stdout = "", stderr = "";
656
+ let exitCode = 0;
657
+ const __vfsWrites = {};
658
+ const __vfsDirs = {};
659
+
660
+ ${SHIMS}
661
+
662
+ // Override console AND process.stdout/stderr for live SUPERVISOR streaming
663
+ if (__supervisor) {
664
+ __consoleMod.log = (...a) => { const s = __utilMod.format(...a) + "\\n"; stdout += s; __pendingIO.push(__supervisor.stdout(s).catch((e) => __onRpcDrop(s.length, e))); };
665
+ __consoleMod.error = (...a) => { const s = __utilMod.format(...a) + "\\n"; stderr += s; __pendingIO.push(__supervisor.stderr(s).catch((e) => __onRpcDrop(s.length, e))); };
666
+ __consoleMod.warn = __consoleMod.error;
667
+ __consoleMod.info = __consoleMod.log;
668
+ __consoleMod.debug = __consoleMod.log;
669
+ __processMod.stdout.write = (d) => { const s = String(d); stdout += s; __pendingIO.push(__supervisor.stdout(s).catch((e) => __onRpcDrop(s.length, e))); return true; };
670
+ __processMod.stderr.write = (d) => { const s = String(d); stderr += s; __pendingIO.push(__supervisor.stderr(s).catch((e) => __onRpcDrop(s.length, e))); return true; };
671
+ }
672
+
673
+ // console-facet (2026-05-12): mirror the shimmed console onto
674
+ // globalThis so user code can reach it via globalThis.console.log
675
+ // as well as bare console.log. See generateFacetCode for the full
676
+ // rationale (verbatim repro + workerd descriptor empirical check).
677
+ try { globalThis.console = __consoleMod; } catch { /* readonly fallback */ }
678
+
679
+ const mod = { exports: {} };
680
+ // G2 (runtime-pkg wave): see corresponding comment in NodeProcess.run.
681
+ __require.main = mod;
682
+ try {
683
+ __compiledFn(
684
+ mod.exports, __require, mod, filename || "/home/user/script.js", dirname || "/home/user",
685
+ __consoleMod, __processMod, __BufferMod,
686
+ globalThis.setTimeout, globalThis.setInterval, globalThis.clearTimeout, globalThis.clearInterval,
687
+ globalThis, argv || []
688
+ );
689
+ } catch (e) {
690
+ if (e instanceof __ProcessExit) { exitCode = e.code; }
691
+ else {
692
+ const trace = (e && e.stack) || (e && e.message) || String(e);
693
+ stderr += trace + "\\n";
694
+ exitCode = 1;
695
+ if (__supervisor) {
696
+ try { __pendingIO.push(__supervisor.stderr(trace + "\\n").catch((e2) => __onRpcDrop((trace || "").length + 1, e2))); } catch {}
697
+ }
698
+ }
699
+ }
700
+
701
+ await new Promise(r => setTimeout(r, 0));
702
+
703
+ const __failedWrites = {};
704
+ if (__supervisor && Object.keys(__vfsWrites).length > 0) {
705
+ for (const [path, content] of Object.entries(__vfsWrites)) {
706
+ __pendingIO.push(__supervisor.writeFile(path, content).catch(() => { __failedWrites[path] = content; }));
707
+ }
708
+ }
709
+ if (__pendingIO.length > 0) await Promise.allSettled(__pendingIO);
710
+
711
+ // Fix 6: second drain pass for any setTimeout-scheduled writes that
712
+ // landed after the first allSettled. Bounded to one additional round.
713
+ const __preSecondDrain = __pendingIO.length;
714
+ await new Promise(r => setTimeout(r, 0));
715
+ if (__pendingIO.length > __preSecondDrain) {
716
+ await Promise.allSettled(__pendingIO.slice(__preSecondDrain));
717
+ }
718
+
719
+ // W8 BLOCKER-1 fix: parent-exit synchronous flush of child_process
720
+ // children. See generateFacetCode for the rationale.
721
+ try {
722
+ if (__childProcessMod && typeof __childProcessMod.__cpDrainAllChildren === "function") {
723
+ await __childProcessMod.__cpDrainAllChildren();
724
+ }
725
+ } catch (e) { /* best-effort */ }
726
+
727
+ // Report exit after draining so the ring buffer is complete before
728
+ // the supervisor decides whether to emit a dump. Fix 6: include an
729
+ // orphan-drop tail if RPC writes were lost during teardown.
730
+ if (__supervisor) {
731
+ let __tail = "";
732
+ if (__rpcDrops > 0) {
733
+ __tail = "[orphan output: " + __rpcDrops + " dropped RPC write(s), ~" +
734
+ __rpcDropBytes + " bytes lost" +
735
+ (__rpcLastError ? "; last error: " + __rpcLastError : "") + "]\\n";
736
+ }
737
+ try { await __supervisor.reportExit(exitCode, __tail); } catch {}
738
+ }
739
+
740
+ return Response.json({
741
+ exitCode,
742
+ stdout: __supervisor ? "" : stdout,
743
+ stderr: __supervisor ? "" : stderr,
744
+ vfsWrites: __supervisor ? __failedWrites : __vfsWrites,
745
+ });
746
+ }
747
+ };
748
+ `;
749
+ }
750
+ /**
751
+ * Generate a long-running Node entrypoint.
752
+ *
753
+ * Same core shim/VFS machinery as foreground node execution, but the
754
+ * compiled user entry is booted once and the exported entrypoint keeps
755
+ * serving HTTP requests from the shimmed http.Server registry.
756
+ */
757
+ function generateLongRunningNodeCode(userCode, vfsState, opts) {
758
+ const safeCode = JSON.stringify(userCode);
759
+ const safeArgs = JSON.stringify({
760
+ argv: opts.argv || [],
761
+ env: opts.env || {},
762
+ cwd: opts.cwd || '/home/user',
763
+ filename: opts.filename || '<script>',
764
+ dirname: opts.dirname || opts.cwd || '/home/user',
765
+ });
766
+ const safeBundle = _serializeBundleForFacet(vfsState.bundle);
767
+ const safeManifest = JSON.stringify(vfsState.manifest);
768
+ return `
769
+ ${REAL_NODE_IMPORTS}
770
+
771
+ const USER_CODE = ${safeCode};
772
+ const __NIMBUS_ARGS = ${safeArgs};
773
+
774
+ function __mkCompiledFn(code, extraParams) {
775
+ function renameIfDeclared(name) {
776
+ const re = new RegExp("(?:^|\\\\n|;)\\\\s*(?:const|let|var|function|class)\\\\s+" + name + "\\\\b", "m");
777
+ return re.test(code) ? name + "__nimbus_unused" : name;
778
+ }
779
+ const baseParams = [
780
+ "exports", "require", "module",
781
+ renameIfDeclared("__filename"),
782
+ renameIfDeclared("__dirname"),
783
+ ];
784
+ const renamedExtras = (extraParams || []).map(renameIfDeclared);
785
+ return new Function(...baseParams, ...renamedExtras, code);
786
+ }
787
+
788
+ const __compiledFn = __mkCompiledFn(USER_CODE, [
789
+ "console", "process", "Buffer",
790
+ "setTimeout", "setInterval", "clearTimeout", "clearInterval",
791
+ "global", "__args",
792
+ ]);
793
+
794
+ const __MODULE_VFS_BUNDLE = ${safeBundle};
795
+ const __MODULE_VFS_MANIFEST = ${safeManifest};
796
+ const __compiledModules = new Map();
797
+ const __compileFailures = new Map();
798
+ for (const [__p, __c] of Object.entries(__MODULE_VFS_BUNDLE)) {
799
+ if (__p.endsWith(".js") || __p.endsWith(".mjs") || __p.endsWith(".cjs")) {
800
+ try {
801
+ __compiledModules.set(__p, __mkCompiledFn(__c, [
802
+ "console", "process", "Buffer",
803
+ "setTimeout", "setInterval", "clearTimeout", "clearInterval",
804
+ ]));
805
+ } catch (__e) {
806
+ __compileFailures.set(__p, __e && __e.message ? __e.message : String(__e));
807
+ }
808
+ }
809
+ }
810
+
811
+ class __ProcessExit extends Error {
812
+ constructor(code) { super("process.exit(" + code + ")"); this.code = code; }
813
+ }
814
+
815
+ let __nimbusStarted = false;
816
+ let __nimbusStarting = null;
817
+ let __nimbusRuntime = null;
818
+
819
+ async function __nimbusFlushRuntime() {
820
+ const rt = __nimbusRuntime;
821
+ if (!rt) return;
822
+ if (rt.supervisor && Object.keys(rt.vfsWrites).length > 0) {
823
+ for (const [path, content] of Object.entries(rt.vfsWrites)) {
824
+ rt.pendingIO.push(rt.supervisor.writeFile(path, content).catch(() => {}));
825
+ }
826
+ }
827
+ if (rt.pendingIO.length > rt.settledIO) {
828
+ const slice = rt.pendingIO.slice(rt.settledIO);
829
+ rt.settledIO = rt.pendingIO.length;
830
+ await Promise.allSettled(slice);
831
+ }
832
+ }
833
+
834
+ async function __nimbusEnsureStarted(workerEnv) {
835
+ if (__nimbusStarted) return;
836
+ if (__nimbusStarting) return __nimbusStarting;
837
+ __nimbusStarting = (async () => {
838
+ const args = __NIMBUS_ARGS;
839
+ const { argv, env, cwd: _cwd, filename, dirname } = args;
840
+ const __vfsBundle = __MODULE_VFS_BUNDLE;
841
+ const __vfsManifest = __MODULE_VFS_MANIFEST;
842
+ const __supervisor = workerEnv?.SUPERVISOR || null;
843
+ const __pendingIO = [];
844
+ let __rpcDrops = 0;
845
+ let __rpcDropBytes = 0;
846
+ let __rpcLastError = "";
847
+ const __onRpcDrop = (bytes, e) => {
848
+ __rpcDrops++;
849
+ __rpcDropBytes += bytes | 0;
850
+ if (e) __rpcLastError = (e && e.message) || String(e);
851
+ };
852
+ let cwd = _cwd || "/home/user";
853
+ let stdout = "", stderr = "";
854
+ let exitCode = 0;
855
+ const __vfsWrites = {};
856
+ const __vfsDirs = {};
857
+
858
+ ${SHIMS}
859
+
860
+ if (__supervisor) {
861
+ __consoleMod.log = (...a) => { const s = __utilMod.format(...a) + "\\n"; stdout += s; __pendingIO.push(__supervisor.stdout(s).catch((e) => __onRpcDrop(s.length, e))); };
862
+ __consoleMod.error = (...a) => { const s = __utilMod.format(...a) + "\\n"; stderr += s; __pendingIO.push(__supervisor.stderr(s).catch((e) => __onRpcDrop(s.length, e))); };
863
+ __consoleMod.warn = __consoleMod.error;
864
+ __consoleMod.info = __consoleMod.log;
865
+ __consoleMod.debug = __consoleMod.log;
866
+ __processMod.stdout.write = (d) => { const s = String(d); stdout += s; __pendingIO.push(__supervisor.stdout(s).catch((e) => __onRpcDrop(s.length, e))); return true; };
867
+ __processMod.stderr.write = (d) => { const s = String(d); stderr += s; __pendingIO.push(__supervisor.stderr(s).catch((e) => __onRpcDrop(s.length, e))); return true; };
868
+ }
869
+
870
+ try { globalThis.console = __consoleMod; } catch {}
871
+
872
+ const mod = { exports: {} };
873
+ __require.main = mod;
874
+ try {
875
+ __compiledFn(
876
+ mod.exports, __require, mod, filename || "/home/user/script.js", dirname || "/home/user",
877
+ __consoleMod, __processMod, __BufferMod,
878
+ globalThis.setTimeout, globalThis.setInterval, globalThis.clearTimeout, globalThis.clearInterval,
879
+ globalThis, argv || []
880
+ );
881
+ } catch (e) {
882
+ if (e instanceof __ProcessExit) {
883
+ exitCode = e.code;
884
+ } else {
885
+ const trace = (e && e.stack) || (e && e.message) || String(e);
886
+ stderr += trace + "\\n";
887
+ exitCode = 1;
888
+ if (__supervisor) {
889
+ try { __pendingIO.push(__supervisor.stderr(trace + "\\n").catch((e2) => __onRpcDrop((trace || "").length + 1, e2))); } catch {}
890
+ }
891
+ }
892
+ }
893
+
894
+ await new Promise((resolve) => setTimeout(resolve, 0));
895
+ __nimbusRuntime = {
896
+ supervisor: __supervisor,
897
+ pendingIO: __pendingIO,
898
+ settledIO: 0,
899
+ vfsWrites: __vfsWrites,
900
+ };
901
+ await __nimbusFlushRuntime();
902
+
903
+ if (__rpcDrops > 0 && __supervisor) {
904
+ const tail = "[orphan output: " + __rpcDrops + " dropped RPC write(s), ~" +
905
+ __rpcDropBytes + " bytes lost" +
906
+ (__rpcLastError ? "; last error: " + __rpcLastError : "") + "]\\n";
907
+ try { await __supervisor.stderr(tail); } catch {}
908
+ }
909
+ if (exitCode !== 0) {
910
+ if (__supervisor) {
911
+ try { await __supervisor.reportExit(exitCode, stderr || ("exit " + exitCode + "\\n")); } catch {}
912
+ }
913
+ throw new Error(stderr || ("long-running node startup exited " + exitCode));
914
+ }
915
+ __nimbusStarted = true;
916
+ })();
917
+ return __nimbusStarting;
918
+ }
919
+
920
+ async function __nimbusDispatchHttp(req, workerEnv) {
921
+ await __nimbusEnsureStarted(workerEnv);
922
+ if (req.headers.get("X-Nimbus-Internal-Boot") === "1") {
923
+ return new Response(null, { status: 204 });
924
+ }
925
+ const ports = globalThis.__portRegistry;
926
+ const hinted = Number(req.headers.get("X-Nimbus-Port") || 0);
927
+ const server = ports && (ports.get(hinted) || ports.values().next().value);
928
+ if (!server || typeof server._handleRequest !== "function") {
929
+ return new Response("Nimbus: no HTTP server is listening in this process", { status: 502 });
930
+ }
931
+ const url = new URL(req.url);
932
+ const headers = {};
933
+ req.headers.forEach((v, k) => { headers[k] = v; });
934
+ let body = "";
935
+ if (req.method !== "GET" && req.method !== "HEAD") body = await req.text();
936
+ const res = server._handleRequest(url.pathname + url.search, req.method, headers, body);
937
+ if (!res._ended) {
938
+ await new Promise((resolve) => {
939
+ try { res.on("finish", resolve); } catch { resolve(); }
940
+ setTimeout(resolve, 5000);
941
+ });
942
+ }
943
+ await __nimbusFlushRuntime();
944
+ return new Response((res._body || []).join(""), { status: res.statusCode || 200, headers: res.headers || {} });
945
+ }
946
+
947
+ export default {
948
+ async fetch(req, env) { return __nimbusDispatchHttp(req, env); },
949
+ async handleHttpRequest(req) { return __nimbusDispatchHttp(req, undefined); },
950
+ };
951
+ `;
952
+ }
953
+ /**
954
+ * hardening-r5: read a file from the VFS and decide whether to keep it
955
+ * as a string (valid UTF-8 text — the hot path for source code,
956
+ * package.json, configs) or as Uint8Array bytes (binary — wasm
957
+ * modules, images, sqlite blobs, etc.).
958
+ *
959
+ * Strategy: read bytes, attempt a fatal UTF-8 decode. If decode
960
+ * succeeds the file IS valid UTF-8 and the string round-trips
961
+ * losslessly through JSON; return string. If decode throws (any
962
+ * invalid byte sequence) return Uint8Array.
963
+ *
964
+ * Throws on read errors (caller wraps in try/catch — matches the
965
+ * pre-fix readFileString contract).
966
+ */
967
+ function _readBundleCell(vfs, path) {
968
+ const bytes = vfs.readFile(path);
969
+ // Bun/workerd both support fatal:true on TextDecoder. Cast to any
970
+ // because TypeScript's lib.dom.d.ts marks ignoreBOM as required when
971
+ // an options object is provided; at runtime both fields are optional.
972
+ try {
973
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
974
+ }
975
+ catch {
976
+ return bytes;
977
+ }
978
+ }
979
+ /**
980
+ * hardening-r5: byte-length of a bundle cell for budget accounting.
981
+ * Strings counted as char-length (a slight under-count for non-ASCII
982
+ * but matches the pre-fix behaviour); Uint8Array counted as byteLength.
983
+ */
984
+ function _bundleCellLength(cell) {
985
+ return typeof cell === 'string' ? cell.length : cell.byteLength;
986
+ }
987
+ /**
988
+ * hardening-r5: emit a JS expression that, when evaluated inside the
989
+ * facet's module-init context, yields a `Record<string, string |
990
+ * Uint8Array>` with binary cells revived from base64. Strings stay as
991
+ * JSON strings (the hot path). Binary cells become `{ __b64: "..." }`
992
+ * markers and are revived by a tiny inline loop.
993
+ *
994
+ * The output is a SELF-EXECUTING IIFE expression so it can be substituted
995
+ * directly into `const __MODULE_VFS_BUNDLE = ${expr};` template slots.
996
+ */
997
+ function _serializeBundleForFacet(bundle) {
998
+ const strCells = {};
999
+ const binCells = {};
1000
+ for (const [k, v] of Object.entries(bundle)) {
1001
+ if (typeof v === 'string') {
1002
+ strCells[k] = v;
1003
+ }
1004
+ else {
1005
+ // Uint8Array → base64. btoa requires a binary string; we build it
1006
+ // 8K chars at a time to avoid String.fromCharCode argument-count
1007
+ // limits on large files (~1MB+).
1008
+ let bin = '';
1009
+ const CHUNK = 8192;
1010
+ for (let i = 0; i < v.byteLength; i += CHUNK) {
1011
+ bin += String.fromCharCode.apply(null, Array.from(v.subarray(i, Math.min(i + CHUNK, v.byteLength))));
1012
+ }
1013
+ binCells[k] = btoa(bin);
1014
+ }
1015
+ }
1016
+ // The IIFE revives binary cells in-place. atob → binary string →
1017
+ // Uint8Array (Uint8Array.from(str, c=>c.charCodeAt(0))).
1018
+ // Note: when binCells is empty (the overwhelming common case —
1019
+ // source code is all text) the IIFE collapses to a JSON literal,
1020
+ // costing only the IIFE wrapper bytes (~30) per facet boot.
1021
+ return `(function(){const __b=${JSON.stringify(strCells)};const __x=${JSON.stringify(binCells)};for(const __k in __x){__b[__k]=Uint8Array.from(atob(__x[__k]),__c=>__c.charCodeAt(0));}return __b;})()`;
1022
+ }
1023
+ const MANIFEST_MAX_DEPTH = 12;
1024
+ /**
1025
+ * Build the manifest pass — uncapped path→child-names map. UNCHANGED
1026
+ * from W2.5b; this is the W2.5b root-cause fix and continues to keep
1027
+ * fs.readdirSync / fs.statSync honest regardless of which subset of
1028
+ * file CONTENT we ship.
1029
+ */
1030
+ function buildManifest(vfs, cwd, scriptPath) {
1031
+ const manifest = {};
1032
+ function walk(dirPath, depth = 0) {
1033
+ if (depth > MANIFEST_MAX_DEPTH)
1034
+ return;
1035
+ const stripped = dirPath.replace(/^\/+/, '');
1036
+ if (stripped in manifest)
1037
+ return;
1038
+ let entries;
1039
+ try {
1040
+ entries = vfs.readdir(stripped);
1041
+ }
1042
+ catch {
1043
+ return;
1044
+ }
1045
+ manifest[stripped] = entries.map((e) => e.name);
1046
+ for (const entry of entries) {
1047
+ if (entry.type === 'directory') {
1048
+ const childPath = stripped ? stripped + '/' + entry.name : entry.name;
1049
+ walk(childPath, depth + 1);
1050
+ }
1051
+ }
1052
+ }
1053
+ const cwdStripped = cwd.replace(/^\/+/, '');
1054
+ walk(cwdStripped, 0);
1055
+ const nmDir = cwdStripped + '/node_modules';
1056
+ if (vfs.exists(nmDir) && vfs.isDirectory(nmDir)) {
1057
+ walk(nmDir, 0);
1058
+ }
1059
+ // ── Bin-target package root (e.g. /tmp/.npx-cache/node_modules/<pkg>/) ──
1060
+ //
1061
+ // When the entry script lives in a node_modules outside cwd (npx-cache
1062
+ // packages, globally-installed bins, etc.), buildManifest's cwd walk
1063
+ // misses the package's sibling files. The bin's index.js gets
1064
+ // require-walked + greedy-added to the BUNDLE, but the MANIFEST
1065
+ // (which is the source of truth for `fs.readdirSync`) was empty for
1066
+ // those paths — so `readdirSync('/tmp/.npx-cache/.../template-X')`
1067
+ // returned [] and `create-vite` scaffolded zero files.
1068
+ //
1069
+ // Walk the innermost `node_modules/<pkg>/` of `scriptPath` so its
1070
+ // entire package tree is enumerable via readdir. Bounded by
1071
+ // MANIFEST_MAX_DEPTH; same depth budget as the cwd walk.
1072
+ if (scriptPath) {
1073
+ const sp = scriptPath.replace(/^\/+/, '');
1074
+ const segs = sp.split('/');
1075
+ let nmIdx = -1;
1076
+ for (let i = segs.length - 1; i >= 0; i--) {
1077
+ if (segs[i] === 'node_modules') {
1078
+ nmIdx = i;
1079
+ break;
1080
+ }
1081
+ }
1082
+ if (nmIdx >= 0) {
1083
+ const isScoped = segs[nmIdx + 1]?.startsWith('@');
1084
+ const pkgEnd = isScoped ? nmIdx + 3 : nmIdx + 2;
1085
+ if (pkgEnd <= segs.length) {
1086
+ const pkgRoot = segs.slice(0, pkgEnd).join('/');
1087
+ if (vfs.exists(pkgRoot) && vfs.isDirectory(pkgRoot)) {
1088
+ walk(pkgRoot, 0);
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ return manifest;
1094
+ }
1095
+ /**
1096
+ * Greedy-oversample every installed package's main entry. The static
1097
+ * prefetch via require-resolver covers the require() chain literally
1098
+ * present in source; greedy oversampling adds a safety net for dynamic
1099
+ * patterns the regex misses (jest/`bindings`/`import-local` style
1100
+ * computed-path requires). Bounded to package.json + 1 main-entry file
1101
+ * per package — sub-agent §Q3 quantified the worst-case cumulative
1102
+ * budget impact (~322 KiB for fastify, ~1.7 MiB for ts-jest).
1103
+ */
1104
+ // X.5-C: exported so audit/probes/x5c/functional/f2-hash-chunk-greedy.mjs
1105
+ // can verify the hash-chunk + shared/ oversample directly. Pre-X.5-C this
1106
+ // was a file-local helper. Adding the named export is a pure surface
1107
+ // addition — no callers other than buildPrefetchBundle (same file) and
1108
+ // the new probe.
1109
+ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState) {
1110
+ let added = 0;
1111
+ const cwdStripped = cwd.replace(/^\/+/, '');
1112
+ const nmDir = cwdStripped + '/node_modules';
1113
+ if (!(vfs.exists(nmDir) && vfs.isDirectory(nmDir)))
1114
+ return { added };
1115
+ const exts = ['', '.js', '.cjs', '.mjs', '/index.js', '/index.cjs'];
1116
+ function addOne(path) {
1117
+ const stripped = path.replace(/^\/+/, '');
1118
+ if (stripped in bundle)
1119
+ return false;
1120
+ if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
1121
+ return false;
1122
+ if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
1123
+ return false;
1124
+ try {
1125
+ if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
1126
+ return false;
1127
+ // hardening-r5: preserve binary content as Uint8Array.
1128
+ const content = _readBundleCell(vfs, stripped);
1129
+ const cellLen = _bundleCellLength(content);
1130
+ if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
1131
+ return false;
1132
+ bundle[stripped] = content;
1133
+ budgetState.totalBytes += cellLen;
1134
+ budgetState.fileCount++;
1135
+ added++;
1136
+ return true;
1137
+ }
1138
+ catch {
1139
+ return false;
1140
+ }
1141
+ }
1142
+ // X.5-C Fix #2 helper: walk a (possibly nested) exports value and
1143
+ // collect every string-leaf path. unbuild-shaped packages like pathe
1144
+ // nest two deep — `exports."."`.{require,import}.{types,default} —
1145
+ // and the previous one-level loop only caught the inner string leaves
1146
+ // when default was at the top, missing the unbuild shape entirely.
1147
+ function collectExportLeaves(node, out) {
1148
+ if (typeof node === 'string') {
1149
+ out.add(node);
1150
+ return;
1151
+ }
1152
+ if (!node || typeof node !== 'object')
1153
+ return;
1154
+ // Order matters for the "most likely usable" leaf: prefer require
1155
+ // (most CJS-friendly), then default, then node, then import. We add
1156
+ // ALL of them to the candidate set — addPkgEntry will probe each.
1157
+ for (const k of ['require', 'node', 'default', 'import']) {
1158
+ if (k in node)
1159
+ collectExportLeaves(node[k], out);
1160
+ }
1161
+ }
1162
+ function addPkgEntry(pkgDir) {
1163
+ addOne(pkgDir + '/package.json');
1164
+ let meta;
1165
+ try {
1166
+ meta = JSON.parse(vfs.readFileString(pkgDir + '/package.json'));
1167
+ }
1168
+ catch {
1169
+ meta = null;
1170
+ }
1171
+ const candidates = new Set();
1172
+ if (meta) {
1173
+ if (typeof meta.main === 'string')
1174
+ candidates.add(meta.main);
1175
+ if (typeof meta.module === 'string')
1176
+ candidates.add(meta.module);
1177
+ const exp = meta.exports;
1178
+ if (typeof exp === 'string')
1179
+ candidates.add(exp);
1180
+ else if (exp && typeof exp === 'object') {
1181
+ const dot = exp['.'];
1182
+ // X.5-C Fix #2: walk nested condition trees recursively. Without
1183
+ // this, packages with two-level exports (pathe, magic-string,
1184
+ // most unbuild-emitted libs) miss their actual entry leaf and
1185
+ // greedyAddMainEntries falls back to /index.js probing — which
1186
+ // doesn't exist for those packages.
1187
+ collectExportLeaves(dot, candidates);
1188
+ }
1189
+ }
1190
+ if (candidates.size === 0)
1191
+ candidates.add('index.js');
1192
+ for (const rel of candidates) {
1193
+ const norm = rel.replace(/^\.\//, '');
1194
+ const base = pkgDir + '/' + norm;
1195
+ let landed = false;
1196
+ const tries = /\.[a-z]+$/.test(norm) ? [base] : exts.map((e) => base + e);
1197
+ for (const candidate of tries) {
1198
+ if (vfs.exists(candidate.replace(/^\/+/, '')) &&
1199
+ !vfs.isDirectory(candidate.replace(/^\/+/, ''))) {
1200
+ if (addOne(candidate)) {
1201
+ landed = true;
1202
+ break;
1203
+ }
1204
+ }
1205
+ }
1206
+ if (landed) {
1207
+ // X.5-C Fix #2: when an entry lands, also pull in sibling files
1208
+ // that match unbuild's hash-chunk pattern (`<base>.<hash>.cjs|mjs|js`)
1209
+ // AND walk one level into a `shared/` subdir if the package has
1210
+ // one. The unbuild bundler emits chunked CJS like:
1211
+ // dist/index.cjs (entry)
1212
+ // dist/shared/<base>.<hash>.cjs (chunk required by entry)
1213
+ //
1214
+ // Without this, even when Fix #1 lets the prefetch walker reach
1215
+ // the entry, the package's required chunks land OUTSIDE the
1216
+ // walker's MAX_FILES/MAX_BYTES budget on big trees (nuxt 516
1217
+ // pkgs / 10k+ files). The greedy oversample is the defensive
1218
+ // safety net for hash-chunk reachability.
1219
+ const entryDir = base.replace(/\/[^/]+$/, '');
1220
+ try {
1221
+ const sibs = vfs.readdir(entryDir);
1222
+ for (const sib of sibs) {
1223
+ if (sib.type !== 'file')
1224
+ continue;
1225
+ // Hash-chunk pattern: <name>.<hash>.<cjs|mjs|js>. Hash must
1226
+ // be 6+ chars AND look like a hash, not an English word —
1227
+ // either contain digits/underscore/dash, or contain BOTH
1228
+ // uppercase AND lowercase letters (real bundler hashes are
1229
+ // mixed-case base64-shaped: `BSlhyZSM`, `M-eThtNZ`, ...). This
1230
+ // discriminator keeps us from false-positiving on common
1231
+ // suffixes that happen to be 6+ chars all-lowercase like
1232
+ // `minified`, `modern`, `production`, `compiled`.
1233
+ const hashMatch = sib.name.match(/\.([A-Za-z0-9_-]{6,})\.(cjs|mjs|js)$/);
1234
+ if (!hashMatch)
1235
+ continue;
1236
+ const seg = hashMatch[1];
1237
+ const hasDigitOrDash = /[0-9_-]/.test(seg);
1238
+ const hasMixedCase = /[A-Z]/.test(seg) && /[a-z]/.test(seg);
1239
+ if (!hasDigitOrDash && !hasMixedCase)
1240
+ continue;
1241
+ addOne(entryDir + '/' + sib.name);
1242
+ }
1243
+ // Walk one level into `shared/` — unconditionally, since the
1244
+ // pattern is well-known across unbuild/rolldown/rollup chunked
1245
+ // outputs. Bounded by addOne's budget checks; readdir of a
1246
+ // typical shared/ dir returns 1-5 files.
1247
+ const sharedDir = entryDir + '/shared';
1248
+ const sharedStripped = sharedDir.replace(/^\/+/, '');
1249
+ if (vfs.exists(sharedStripped) && vfs.isDirectory(sharedStripped)) {
1250
+ for (const sh of vfs.readdir(sharedDir)) {
1251
+ if (sh.type !== 'file')
1252
+ continue;
1253
+ if (!/\.(cjs|mjs|js)$/.test(sh.name))
1254
+ continue;
1255
+ addOne(sharedDir + '/' + sh.name);
1256
+ }
1257
+ }
1258
+ }
1259
+ catch { /* unreadable dir — drop sibling oversample, entry
1260
+ file is enough */
1261
+ }
1262
+ break;
1263
+ }
1264
+ }
1265
+ }
1266
+ try {
1267
+ for (const pkg of vfs.readdir(nmDir)) {
1268
+ if (pkg.type !== 'directory')
1269
+ continue;
1270
+ const pkgDir = nmDir + '/' + pkg.name;
1271
+ if (pkg.name.startsWith('@')) {
1272
+ try {
1273
+ for (const sub of vfs.readdir(pkgDir)) {
1274
+ if (sub.type === 'directory')
1275
+ addPkgEntry(pkgDir + '/' + sub.name);
1276
+ }
1277
+ }
1278
+ catch { /* ignore */ }
1279
+ }
1280
+ else {
1281
+ addPkgEntry(pkgDir);
1282
+ }
1283
+ }
1284
+ }
1285
+ catch { /* ignore */ }
1286
+ return { added };
1287
+ }
1288
+ /**
1289
+ * X.5-Z3: scan every JS source already in `bundle` for static
1290
+ * `fs.readFileSync(path.resolve(__dirname, "<rel>"))` shapes and pull
1291
+ * the matched asset files (.css / .html / .htm / .svg / .txt / .json)
1292
+ * into the bundle. The motivating case is jsdom's
1293
+ * `lib/jsdom/living/css/helpers/computed-style.js:16-19`, which loads
1294
+ * `default-stylesheet.css` at module-eval time:
1295
+ *
1296
+ * const defaultStyleSheet = fs.readFileSync(
1297
+ * path.resolve(__dirname, "../../../browser/default-stylesheet.css"),
1298
+ * { encoding: "utf-8" },
1299
+ * );
1300
+ *
1301
+ * The fs shim's `readFileSync` (`src/node-shims.ts:202-215`) consults
1302
+ * only `__vfsBundle` + `__vfsWrites`; runtime asset files that the
1303
+ * require-graph walker doesn't reach (it's bounded to .js/.mjs/.cjs)
1304
+ * are absent from the bundle and ENOENT at runtime. This helper closes
1305
+ * that gap as a sibling of `greedyAddMainEntries` (W2.6a) +
1306
+ * `transformEsmInBundle` (W3.5 Fix B).
1307
+ *
1308
+ * Pattern matched: literal-only, conservative.
1309
+ *
1310
+ * fs.readFileSync(path.resolve(__dirname, "<rel>"), …)
1311
+ * readFileSync(path.resolve(__dirname, "<rel>"), …)
1312
+ *
1313
+ * `<rel>` is a string literal (single, double, OR backtick — provided
1314
+ * the backtick form has no `${}` interpolation). Template-literal,
1315
+ * variable, and concatenation forms are **deliberately skipped** —
1316
+ * they're an unbounded class. Comment-stripped first to avoid
1317
+ * matching the pattern inside `//` / `/* *​/`.
1318
+ *
1319
+ * Returns the count of asset files added (for diagnostics). Errors
1320
+ * are swallowed: missing assets, unreadable VFS, and non-string
1321
+ * readFile inputs are silent skips.
1322
+ *
1323
+ * Same budget shape as `greedyAddMainEntries` — shares the same
1324
+ * VFS_BUNDLE_MAX_FILES / VFS_BUNDLE_MAX_BYTES caps via the
1325
+ * `budgetState` counter.
1326
+ */
1327
+ export function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
1328
+ let added = 0;
1329
+ // Asset extensions covered. Conservative whitelist — txt/json are
1330
+ // also legit runtime-loaded assets (e.g. mime-db json, license.txt).
1331
+ // .json is already typically reachable via `require('./x.json')` so
1332
+ // it's mostly defensive here.
1333
+ const ASSET_EXT = /\.(css|html|htm|svg|txt|json)$/i;
1334
+ // Match the static-literal shape. The capture groups are:
1335
+ // 1 = the relative path string literal contents (no quote chars).
1336
+ // Shape:
1337
+ // readFileSync( path.resolve( __dirname , "rel" )
1338
+ // fs.readFileSync(path.resolve(__dirname, "rel"), …)
1339
+ // node:path / "node:path" forms also covered by allowing optional
1340
+ // leading `\w+\.` prefix on the resolve target.
1341
+ // Quote chars supported: ' " `. For backtick we additionally check
1342
+ // there's no `${` in the captured body (template-literal interpolation
1343
+ // is rejected).
1344
+ const RX = /(?:\bfs\s*\.)?readFileSync\s*\(\s*(?:[\w$.]+\s*\.\s*)?resolve\s*\(\s*__dirname\s*,\s*(['"`])([^'"`]+)\1\s*[\),]/g;
1345
+ function addOneAsset(absPath) {
1346
+ const stripped = absPath.replace(/^\/+/, '');
1347
+ if (stripped in bundle)
1348
+ return false;
1349
+ if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
1350
+ return false;
1351
+ if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
1352
+ return false;
1353
+ try {
1354
+ if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
1355
+ return false;
1356
+ // hardening-r5: preserve binary content as Uint8Array.
1357
+ const content = _readBundleCell(vfs, stripped);
1358
+ const cellLen = _bundleCellLength(content);
1359
+ if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
1360
+ return false;
1361
+ bundle[stripped] = content;
1362
+ budgetState.totalBytes += cellLen;
1363
+ budgetState.fileCount++;
1364
+ added++;
1365
+ return true;
1366
+ }
1367
+ catch {
1368
+ return false;
1369
+ }
1370
+ }
1371
+ // Snapshot the keys first — we mutate `bundle` during the loop.
1372
+ const sourceKeys = Object.keys(bundle).filter((k) => k.endsWith('.js') || k.endsWith('.mjs') || k.endsWith('.cjs'));
1373
+ for (const sourcePath of sourceKeys) {
1374
+ const src = bundle[sourcePath];
1375
+ if (!src || src.length === 0)
1376
+ continue;
1377
+ // hardening-r5: skip binary cells (a .js extension on a binary file
1378
+ // is rare but possible — defensive guard prevents .replace() throwing
1379
+ // on a Uint8Array).
1380
+ if (typeof src !== 'string')
1381
+ continue;
1382
+ // Strip line + block comments before regex-matching so the pattern
1383
+ // doesn't fire inside `// fs.readFileSync(...)` etc.
1384
+ const stripped = src.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
1385
+ // Quick reject: skip files that don't even contain readFileSync.
1386
+ if (stripped.indexOf('readFileSync') < 0)
1387
+ continue;
1388
+ const sourceDir = sourcePath.includes('/')
1389
+ ? sourcePath.substring(0, sourcePath.lastIndexOf('/'))
1390
+ : '';
1391
+ RX.lastIndex = 0;
1392
+ let match;
1393
+ while ((match = RX.exec(stripped)) !== null) {
1394
+ const quote = match[1];
1395
+ const rel = match[2];
1396
+ // Reject template-literal interpolation inside backticks.
1397
+ if (quote === '`' && rel.indexOf('${') >= 0)
1398
+ continue;
1399
+ // Reject any form that looks dynamic (defensive — RX already
1400
+ // requires literal but absolute paths starting with `/` would
1401
+ // bypass the __dirname-relative semantics; allow them since
1402
+ // they're literal and unambiguous).
1403
+ if (!ASSET_EXT.test(rel))
1404
+ continue;
1405
+ // Resolve relative to the source file's directory (the runtime's
1406
+ // __dirname for that source). Match runtime resolution: leading
1407
+ // `./` strips, `..` walks up.
1408
+ let resolved;
1409
+ if (rel.startsWith('/')) {
1410
+ resolved = rel.replace(/^\/+/, '');
1411
+ }
1412
+ else {
1413
+ const parts = (sourceDir + '/' + rel).split('/');
1414
+ const out = [];
1415
+ for (const seg of parts) {
1416
+ if (seg === '' || seg === '.')
1417
+ continue;
1418
+ if (seg === '..') {
1419
+ if (out.length > 0)
1420
+ out.pop();
1421
+ continue;
1422
+ }
1423
+ out.push(seg);
1424
+ }
1425
+ resolved = out.join('/');
1426
+ }
1427
+ addOneAsset(resolved);
1428
+ }
1429
+ }
1430
+ return { added };
1431
+ }
1432
+ /**
1433
+ * X.5-U: scan every JS source already in `bundle` for static
1434
+ * readFileSync of a `__dirname`-relative dotfile or "digest/hash/version/
1435
+ * sha/md5"-shaped sentinel, AND match the SWC/TypeScript-compiled
1436
+ * `(0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, "<rel>"))`
1437
+ * call shape that X.5-Z3's `addStaticReadFileAssets` regex misses.
1438
+ *
1439
+ * Motivating case: ts-jest@29.x's
1440
+ * `package/dist/legacy/config/config-set.js:105`:
1441
+ *
1442
+ * var fs_1 = require("fs");
1443
+ * var path_1 = require("path");
1444
+ * exports.MY_DIGEST = (0, fs_1.readFileSync)(
1445
+ * (0, path_1.resolve)(__dirname, '../../../.ts-jest-digest'), 'utf8');
1446
+ *
1447
+ * The install pipeline writes `.ts-jest-digest` to VFS correctly
1448
+ * (manifest pass at buildManifest enumerates it). But the runtime
1449
+ * fs shim's readFileSync (`src/node-shims.ts:202-215`) consults
1450
+ * `__vfsBundle` only, and none of the existing bundle-population
1451
+ * passes — `prefetchForRequire` (require-graph), `greedyAddMainEntries`
1452
+ * (pkg main entries), `addStaticReadFileAssets` (X.5-Z3, restricted to
1453
+ * `.css|html|svg|txt|json` and to direct `path.resolve(__dirname,…)`)
1454
+ * — picks the dotfile up. Result: ENOENT at facet runtime even though
1455
+ * `fs.readdirSync` and `fs.statSync` both see the file via the manifest.
1456
+ *
1457
+ * Bounded heuristic: filename must EITHER start with `.` (dotfile) OR
1458
+ * match `/digest|hash|version|sha|md5/i` (small-metadata-sentinel
1459
+ * pattern). Without this gate, an unconstrained "match any
1460
+ * __dirname-relative readFileSync filename" would pull arbitrary large
1461
+ * runtime-loaded files (compiled WASM, JSON dictionaries, …) on
1462
+ * packages that read them via this exact shape — bundle bloat with no
1463
+ * payoff. The heuristic narrows to the ts-jest class. Trade-off
1464
+ * documented; future packages outside this shape can extend the
1465
+ * predicate.
1466
+ *
1467
+ * Quote chars supported: `'`, `"`, and backticks WITHOUT `${}`
1468
+ * interpolation. Dynamic specifiers (variable, concatenation,
1469
+ * interpolation) are deliberately skipped.
1470
+ *
1471
+ * Same budget shape as `greedyAddMainEntries` /
1472
+ * `addStaticReadFileAssets` — shares the same VFS_BUNDLE_MAX_FILES /
1473
+ * VFS_BUNDLE_MAX_BYTES caps via `budgetState`. Returns the count of
1474
+ * files added (for diagnostics).
1475
+ *
1476
+ * Errors are swallowed: missing assets, unreadable VFS, and
1477
+ * non-string readFile inputs are silent skips — matches Z3 posture.
1478
+ */
1479
+ export function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetState) {
1480
+ let added = 0;
1481
+ // The heuristic gate. Filenames matching either branch are eligible.
1482
+ // - Leading `.` covers `.ts-jest-digest`, `.cache-marker`, `.lintstagedrc`-ish
1483
+ // sentinel files. Note: `package.json` etc are NOT dotfiles.
1484
+ // - `digest|hash|version|sha|md5` covers compiled-loose sentinel
1485
+ // filenames like `version.txt`, `git-sha`, `build-hash`, …
1486
+ // (Phase B regression matrix §5: bounded to the actual class.)
1487
+ const FILENAME_GATE = /(^\.[^/]+$|digest|hash|version|sha|md5)/i;
1488
+ // Match shapes:
1489
+ // readFileSync(path.resolve(__dirname, "<rel>")) (X.5-Z3)
1490
+ // fs.readFileSync(path.resolve(__dirname, "<rel>")) (X.5-Z3)
1491
+ // (0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, "<rel>")) (X.5-U new — SWC)
1492
+ // readFileSync(path.join(__dirname, "<rel>")) (X.5-U new — join also)
1493
+ // readFileSync((0, path_1.resolve)(__dirname, "<rel>")) (mixed)
1494
+ //
1495
+ // Strategy: anchor the ENTIRE call on `readFileSync` (with optional
1496
+ // `(0, x.y)` wrap or `x.` prefix), then look for either `resolve` OR
1497
+ // `join` (with optional `(0, x.y)` wrap or `x.` prefix), then
1498
+ // `__dirname` and the literal. Capture group 1 = quote, group 2 =
1499
+ // body.
1500
+ //
1501
+ // The regex is permissive about whitespace + parens because
1502
+ // SWC/TypeScript emit varies (extra parens in some output flags,
1503
+ // tighter spacing in production). Tested against:
1504
+ // ts-jest@29.1.4/dist/legacy/config/config-set.js:105
1505
+ // synth `(0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, "X"))`
1506
+ // plain `fs.readFileSync(path.resolve(__dirname, "X"))`
1507
+ // plain `readFileSync(path.join(__dirname, "X"))`
1508
+ const RX = new RegExp(
1509
+ // optional `(0, ` wrap then `<x.>?readFileSync` or bare `readFileSync`
1510
+ '(?:\\(\\s*0\\s*,\\s*)?(?:[\\w$]+\\s*\\.\\s*)?readFileSync\\s*\\)?\\s*\\(' +
1511
+ // call args: optional outer paren, optional `(0, ` wrap then
1512
+ // `<x.>?(resolve|join)` then required `(`
1513
+ '\\s*(?:\\(\\s*0\\s*,\\s*)?(?:[\\w$]+\\s*\\.\\s*)?(?:resolve|join)\\s*\\)?\\s*\\(' +
1514
+ // required __dirname
1515
+ '\\s*__dirname\\s*,\\s*' +
1516
+ // literal: ' " ` (no ${ for backtick)
1517
+ '([\'"`])([^\'"`]+)\\1', 'g');
1518
+ function addOneAsset(absPath) {
1519
+ const stripped = absPath.replace(/^\/+/, '');
1520
+ if (stripped in bundle)
1521
+ return false;
1522
+ if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
1523
+ return false;
1524
+ if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
1525
+ return false;
1526
+ try {
1527
+ if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
1528
+ return false;
1529
+ // hardening-r5: preserve binary content as Uint8Array.
1530
+ const content = _readBundleCell(vfs, stripped);
1531
+ const cellLen = _bundleCellLength(content);
1532
+ if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
1533
+ return false;
1534
+ bundle[stripped] = content;
1535
+ budgetState.totalBytes += cellLen;
1536
+ budgetState.fileCount++;
1537
+ added++;
1538
+ return true;
1539
+ }
1540
+ catch {
1541
+ return false;
1542
+ }
1543
+ }
1544
+ // Snapshot keys; we mutate `bundle` during the loop.
1545
+ const sourceKeys = Object.keys(bundle).filter((k) => k.endsWith('.js') || k.endsWith('.mjs') || k.endsWith('.cjs'));
1546
+ for (const sourcePath of sourceKeys) {
1547
+ const src = bundle[sourcePath];
1548
+ if (!src || src.length === 0)
1549
+ continue;
1550
+ // hardening-r5: skip binary cells (a .js extension on a binary file
1551
+ // is rare but possible — defensive guard prevents .replace() throwing
1552
+ // on a Uint8Array).
1553
+ if (typeof src !== 'string')
1554
+ continue;
1555
+ // Strip line + block comments before regex-matching so the pattern
1556
+ // doesn't fire inside `// fs.readFileSync(...)` etc.
1557
+ const stripped = src.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
1558
+ // Quick reject: skip files that don't even contain readFileSync.
1559
+ if (stripped.indexOf('readFileSync') < 0)
1560
+ continue;
1561
+ const sourceDir = sourcePath.includes('/')
1562
+ ? sourcePath.substring(0, sourcePath.lastIndexOf('/'))
1563
+ : '';
1564
+ RX.lastIndex = 0;
1565
+ let match;
1566
+ while ((match = RX.exec(stripped)) !== null) {
1567
+ const quote = match[1];
1568
+ const rel = match[2];
1569
+ // Reject template-literal interpolation inside backticks.
1570
+ if (quote === '`' && rel.indexOf('${') >= 0)
1571
+ continue;
1572
+ // Resolve relative to the source file's __dirname (matches runtime).
1573
+ let resolved;
1574
+ if (rel.startsWith('/')) {
1575
+ resolved = rel.replace(/^\/+/, '');
1576
+ }
1577
+ else {
1578
+ const parts = (sourceDir + '/' + rel).split('/');
1579
+ const out = [];
1580
+ for (const seg of parts) {
1581
+ if (seg === '' || seg === '.')
1582
+ continue;
1583
+ if (seg === '..') {
1584
+ if (out.length > 0)
1585
+ out.pop();
1586
+ continue;
1587
+ }
1588
+ out.push(seg);
1589
+ }
1590
+ resolved = out.join('/');
1591
+ }
1592
+ // Apply the bounded-heuristic gate on the BASENAME so we don't
1593
+ // overshoot. Z3's `ASSET_EXT` filter overlaps but doesn't cover
1594
+ // dotfiles or no-extension sentinels, which is X.5-U's class.
1595
+ const slash = resolved.lastIndexOf('/');
1596
+ const basename = slash >= 0 ? resolved.slice(slash + 1) : resolved;
1597
+ if (!FILENAME_GATE.test(basename))
1598
+ continue;
1599
+ addOneAsset(resolved);
1600
+ }
1601
+ }
1602
+ return { added };
1603
+ }
1604
+ /**
1605
+ * G3 (runtime-pkg wave) — bin-target sibling oversample.
1606
+ *
1607
+ * When the entry script lives at `node_modules/<pkg>/...` (typical
1608
+ * shape: cli.js, bin/foo, dist/index.js), bins commonly do
1609
+ * `readFileSync(path.join(__dirname, '<rel>'))` to load assets that
1610
+ * the static walker can't see (computed paths, package-internal
1611
+ * data files, .cow / .pem / .wasm / .ttf / etc.).
1612
+ *
1613
+ * Pre-fix: addStaticReadFileAssets only covers a hardcoded ASSET_EXT
1614
+ * whitelist (.css/.html/.htm/.svg/.txt/.json). Cowsay's `.cow` files
1615
+ * ENOENT at runtime.
1616
+ *
1617
+ * Fix shape: when entry is inside a `node_modules/<pkg>` directory,
1618
+ * walk that pkg dir's contents and pull every regular file under
1619
+ * VFS_BUNDLE_MAX_BYTES, capped at `MAX_PKG_FILES` per-pkg so a
1620
+ * 1000-file barrel package can't blow the bundle budget.
1621
+ *
1622
+ * The cap is intentionally generous (200 files): typical CLI
1623
+ * packages have <50 files; cowsay has ~85 (many .cow files);
1624
+ * commander has ~30. A 200-cap accommodates the long tail without
1625
+ * regressing big packages where the standard prefetch is already
1626
+ * doing the right thing.
1627
+ *
1628
+ * Caller already passed the `cwd` and the `scriptPath`; we only act
1629
+ * if scriptPath is /<...>/node_modules/<pkg>/... — anything else
1630
+ * (user scripts, npx-cache files outside node_modules, eval) is
1631
+ * a no-op.
1632
+ */
1633
+ function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState) {
1634
+ if (!scriptPath)
1635
+ return { added: 0 };
1636
+ const stripped = scriptPath.replace(/^\/+/, '');
1637
+ // Find the *innermost* node_modules/<pkg> root. Handles scoped
1638
+ // packages (`@org/name`) too.
1639
+ const segs = stripped.split('/');
1640
+ let nmIdx = -1;
1641
+ for (let i = segs.length - 1; i >= 0; i--) {
1642
+ if (segs[i] === 'node_modules') {
1643
+ nmIdx = i;
1644
+ break;
1645
+ }
1646
+ }
1647
+ if (nmIdx < 0)
1648
+ return { added: 0 };
1649
+ const isScoped = segs[nmIdx + 1]?.startsWith('@');
1650
+ const pkgEnd = isScoped ? nmIdx + 3 : nmIdx + 2;
1651
+ if (pkgEnd > segs.length)
1652
+ return { added: 0 };
1653
+ const pkgRoot = segs.slice(0, pkgEnd).join('/');
1654
+ // npm-create-fix wave (2026-05-12): bumped from 200 to 1000 to cover
1655
+ // multi-template scaffolds. create-vite ships 242 files + 74 dirs (316
1656
+ // visit entries) across 21 template-* subdirs; the 200-cap exhausted
1657
+ // BFS budget before late-alphabetical template files (vanilla, vue, etc.)
1658
+ // were bundled, causing readFileSync ENOENT in the facet and silent
1659
+ // partial scaffolding (only .gitignore + index.html materialized for
1660
+ // `npm create vite@latest test-vite -- --template vanilla`).
1661
+ //
1662
+ // 1000 covers the documented create-* family (create-vite ~316, create-
1663
+ // nuxt ~700, create-react-router ~400). Still well below the global
1664
+ // VFS_BUNDLE_MAX_FILES = 4000 (constants.ts:74) and the
1665
+ // VFS_BUNDLE_MAX_BYTES = 24 MiB content cap, both of which retain the
1666
+ // defense against pathological 4000+ file barrel packages.
1667
+ //
1668
+ // See /workspace/.seal-internal/2026-05-12-npm-create-gap/findings.md
1669
+ // for the prior wave's empirical investigation (243 manifest entries,
1670
+ // only 140/243 readable pre-bump on prod 11df6ca).
1671
+ const MAX_PKG_FILES = 1000;
1672
+ // BFS walk pkgRoot. Skip nested `node_modules` (those are
1673
+ // separate packages with their own walk if/when they become
1674
+ // entry points).
1675
+ let added = 0;
1676
+ let visited = 0;
1677
+ const queue = [pkgRoot];
1678
+ while (queue.length > 0 && visited < MAX_PKG_FILES) {
1679
+ const dir = queue.shift();
1680
+ let entries;
1681
+ try {
1682
+ entries = vfs.readdir(dir);
1683
+ }
1684
+ catch {
1685
+ continue;
1686
+ }
1687
+ for (const e of entries) {
1688
+ if (visited >= MAX_PKG_FILES)
1689
+ break;
1690
+ visited++;
1691
+ if (e.name === 'node_modules')
1692
+ continue;
1693
+ if (e.name === '.git')
1694
+ continue;
1695
+ const child = dir + '/' + e.name;
1696
+ if (e.type === 'directory') {
1697
+ queue.push(child);
1698
+ continue;
1699
+ }
1700
+ // File. Skip if already in bundle (the static walker beat us
1701
+ // to it). Otherwise pull, respecting global budget caps.
1702
+ if (bundle[child] !== undefined)
1703
+ continue;
1704
+ if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
1705
+ return { added };
1706
+ if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
1707
+ return { added };
1708
+ // hardening-r5: preserve binary content as Uint8Array.
1709
+ let content;
1710
+ try {
1711
+ content = _readBundleCell(vfs, child);
1712
+ }
1713
+ catch {
1714
+ continue;
1715
+ }
1716
+ const cellLen = _bundleCellLength(content);
1717
+ if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
1718
+ return { added };
1719
+ bundle[child] = content;
1720
+ budgetState.totalBytes += cellLen;
1721
+ budgetState.fileCount++;
1722
+ added++;
1723
+ }
1724
+ }
1725
+ return { added };
1726
+ }
1727
+ /**
1728
+ * BUG-SWEEP-R2-2 (2026-05-11): scan entry code (and any already-bundled
1729
+ * .js/.mjs/.cjs sources) for ABSOLUTE-PATH string literals that look
1730
+ * like file reads. For every candidate that exists on the SqliteVFS,
1731
+ * pull it into the bundle so the facet's __vfsBundle can serve it.
1732
+ *
1733
+ * Pre-fix: `node -e 'fs.readFileSync("/home/user/x.txt")'` returned
1734
+ * ENOENT for files the shell could `cat`. The facet's bundle never
1735
+ * included `/home/user/x.txt` because no static scanner matched the
1736
+ * shape (greedy/dotfile/asset all required `path.resolve(__dirname,
1737
+ * "rel")` or `node_modules` pkg-root containment).
1738
+ *
1739
+ * Match policy:
1740
+ * - String literals matching `/[^"`']+/` (slash-prefixed, no quotes
1741
+ * inside), length 2-512 chars.
1742
+ * - Reject paths under prefixes we don't mount (`/proc`, `/sys`,
1743
+ * `/dev`, `/lib`, `/lib64`) — wouldn't resolve.
1744
+ * - Reject paths containing `*` `?` `[` `]` `${` (glob/template).
1745
+ * - File must exist on VFS, be a file (not a dir).
1746
+ * - Defer to budgetState caps so we don't blow VFS_BUNDLE_MAX_BYTES.
1747
+ *
1748
+ * Quick-reject: only files containing readFileSync / createReadStream
1749
+ * / openSync / readFile in source are scanned. Entry code is always
1750
+ * scanned (it's the user's intent).
1751
+ */
1752
+ function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
1753
+ let added = 0;
1754
+ // Capture absolute-path string literals. The path may not contain
1755
+ // the quote char; the surrounding regex strips line/block comments
1756
+ // first to avoid commented-out matches.
1757
+ // Path char set: alnum + dot + dash + slash + underscore. This
1758
+ // excludes spaces, glob chars, template syntax — all dynamic.
1759
+ const RX = /(['"`])(\/[A-Za-z0-9._\-\/]{1,510})\1/g;
1760
+ const REJECT_PREFIX = /^\/(proc|sys|dev|lib|lib64|boot|root)(\/|$)/;
1761
+ function tryAdd(absPath) {
1762
+ if (!absPath || absPath.length < 2 || absPath.length > 512)
1763
+ return;
1764
+ if (REJECT_PREFIX.test(absPath))
1765
+ return;
1766
+ const stripped = absPath.replace(/^\/+/, '');
1767
+ if (stripped in bundle)
1768
+ return;
1769
+ if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
1770
+ return;
1771
+ if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
1772
+ return;
1773
+ try {
1774
+ if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
1775
+ return;
1776
+ // hardening-r5: preserve binary content as Uint8Array.
1777
+ const content = _readBundleCell(vfs, stripped);
1778
+ const cellLen = _bundleCellLength(content);
1779
+ if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
1780
+ return;
1781
+ bundle[stripped] = content;
1782
+ budgetState.totalBytes += cellLen;
1783
+ budgetState.fileCount++;
1784
+ added++;
1785
+ }
1786
+ catch { /* swallow — file may be binary, race-deleted, etc. */ }
1787
+ }
1788
+ function scanOne(src) {
1789
+ if (!src)
1790
+ return;
1791
+ // Strip line + block comments so we don't match commented-out reads.
1792
+ const stripped = src.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
1793
+ RX.lastIndex = 0;
1794
+ let m;
1795
+ while ((m = RX.exec(stripped)) !== null) {
1796
+ const literal = m[2];
1797
+ // Skip if there's any unsafe char (defensive — RX already
1798
+ // forbids most). The check on the captured group is cheap.
1799
+ if (/[\?\*\[\]\{\}]/.test(literal))
1800
+ continue;
1801
+ tryAdd(literal);
1802
+ }
1803
+ }
1804
+ // Always scan entry code.
1805
+ scanOne(entryCode);
1806
+ // Optionally scan bundled JS sources too — useful for transitive cases
1807
+ // where a require'd module hardcodes an absolute path. Use the same
1808
+ // budget-state so we don't blow caps.
1809
+ const sourceKeys = Object.keys(bundle).filter((k) => k.endsWith('.js') || k.endsWith('.mjs') || k.endsWith('.cjs'));
1810
+ for (const k of sourceKeys) {
1811
+ if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
1812
+ break;
1813
+ if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
1814
+ break;
1815
+ // hardening-r5: scanOne expects string. Binary cells (rare with a
1816
+ // .js extension but possible) are skipped — scanOne would throw on
1817
+ // a Uint8Array .replace() call.
1818
+ const cell = bundle[k];
1819
+ if (typeof cell !== 'string')
1820
+ continue;
1821
+ scanOne(cell);
1822
+ }
1823
+ return { added };
1824
+ }
1825
+ /**
1826
+ * W3.5 Fix B helper — detect ESM source by sniffing for top-level
1827
+ * `import` / `export` STATEMENTS. Identifier/property uses (`obj.import`,
1828
+ * `pkg.export`) won't match because we anchor at start-of-line / `^\s*`.
1829
+ *
1830
+ * Comment stripping is intentionally cheap: regex over `//…` and
1831
+ * `/* … *​/` blocks. Strings containing the patterns won't be touched,
1832
+ * but esbuild produces valid CJS for any input it parses, so a false
1833
+ * positive is harmless (just a wasted transform).
1834
+ *
1835
+ * Misses to be aware of:
1836
+ * - JSX-only files with no import/export — but those wouldn't load via
1837
+ * plain new Function anyway (loader: 'js' rejects JSX).
1838
+ * - Files inside a "type":"module" package that don't use import/export
1839
+ * keywords (rare; nominally CJS-shaped). Accepted limitation, called
1840
+ * out in audit/sections/W3.5-plan.md §6.
1841
+ */
1842
+ function looksLikeEsm(src) {
1843
+ // Cheap comment strip. Not a full parser — enough to avoid the
1844
+ // common false positives ("// import X" or `/* export */`).
1845
+ const stripped = src.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
1846
+ // import STATEMENT: catches `^...import...`, `\n...import...`,
1847
+ // `;import...` (post-statement on same line), `}import...` (post-block).
1848
+ // Trailing `[\s{]` catches the no-whitespace minified form `import{...}from"..."`
1849
+ // shipped by @tailwindcss/vite/dist/index.mjs and other minified ESM bundles.
1850
+ // Skips dynamic `import(...)` (which is fine in CJS) because `(` ∉ `[\s{]`.
1851
+ // See audit/sections/X5Z5-plan.md §3 (both relaxations needed: leading
1852
+ // anchor [\n;}] AND trailing [\s{]).
1853
+ const importStmt = /(^|[\n;}])\s*import[\s{]/;
1854
+ // export STATEMENT: same dual relaxation. Trailing `[\s{*]` covers
1855
+ // `export ` (whitespace), `export{` (no-ws minified), and `export*`.
1856
+ const exportStmt = /(^|[\n;}])\s*export[\s{*]/;
1857
+ return importStmt.test(stripped) || exportStmt.test(stripped);
1858
+ }
1859
+ /**
1860
+ * W3.5 Fix B — module-level cache for ESM→CJS transform results, keyed
1861
+ * by content hash. A cheap FNV-1a 32-bit hash is enough (collisions are
1862
+ * astronomically rare for the size of bundles we ship; on collision the
1863
+ * pre-compile would still succeed because the cached result is a valid
1864
+ * CJS rebuild of an equally-valid ESM input).
1865
+ *
1866
+ * Lives at module scope so warm exec invocations hit the cache without
1867
+ * paying the wasm cold-start cost again.
1868
+ */
1869
+ const __esmTransformCache = new Map();
1870
+ function __cacheKey(src) {
1871
+ // FNV-1a 32-bit. Only used for cache keys, NEVER for content
1872
+ // integrity. The ~30-byte string we return is a hex hash + length —
1873
+ // length disambiguates collisions across the rare 32-bit overlap.
1874
+ let h = 0x811c9dc5;
1875
+ for (let i = 0; i < src.length; i++) {
1876
+ h ^= src.charCodeAt(i);
1877
+ h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
1878
+ }
1879
+ return h.toString(16).padStart(8, '0') + ':' + src.length.toString(16);
1880
+ }
1881
+ /**
1882
+ * framework-fixes-F4 (2026-05-12): helper for the "esbuild unavailable
1883
+ * or fatally errored" paths. Walks the bundle for ESM-shaped files and
1884
+ * replaces each with a JS-valid diagnostic shim that throws an
1885
+ * informative Error at require-time. Mirrors the per-file catch in
1886
+ * transformEsmInBundle so the user gets the same actionable error
1887
+ * surface regardless of whether transform failed for the whole batch
1888
+ * or for one file.
1889
+ *
1890
+ * Does NOT touch non-ESM files (which the pre-compile loop handles
1891
+ * fine as-is).
1892
+ */
1893
+ function _markBundleEsmAsFailed(bundle, reason) {
1894
+ for (const path of Object.keys(bundle)) {
1895
+ if (!path.endsWith('.js') && !path.endsWith('.mjs'))
1896
+ continue;
1897
+ const src = bundle[path];
1898
+ if (typeof src !== 'string')
1899
+ continue;
1900
+ if (!looksLikeEsm(src))
1901
+ continue;
1902
+ const escapedReason = JSON.stringify(`esbuild transform failed for ${path}: ${reason}`);
1903
+ bundle[path] =
1904
+ '// framework-fixes-F4 diagnostic shim — esbuild rejected the ESM transform\n' +
1905
+ '(function () { throw new Error(' + escapedReason + '); })();\n';
1906
+ }
1907
+ }
1908
+ /**
1909
+ * Transform every ESM-shaped file in the bundle to CJS via esbuild.
1910
+ * Mutates `bundle` in place. Errors are swallowed (the file is left as
1911
+ * ESM source); the facet's pre-compile loop will record the SyntaxError
1912
+ * into __compileFailures and __loadModule will surface it (Fix C).
1913
+ *
1914
+ * Skips:
1915
+ * - .json (esbuild can transform but there's no payoff and it's a
1916
+ * no-op on our pre-compile loop too).
1917
+ * - .cjs (already CJS; transform is a wash).
1918
+ * - files that pass the regex sniff cleanly (heuristic: no top-level
1919
+ * import/export → CJS-shaped already).
1920
+ *
1921
+ * Returns the count of files transformed (for diagnostics).
1922
+ */
1923
+ async function transformEsmInBundle(bundle, esbuild) {
1924
+ let transformed = 0;
1925
+ let failed = 0;
1926
+ // Snapshot the keys first — esbuild calls await; never iterate-and-mutate.
1927
+ const candidates = [];
1928
+ for (const path of Object.keys(bundle)) {
1929
+ if (!path.endsWith('.js') && !path.endsWith('.mjs'))
1930
+ continue;
1931
+ const src = bundle[path];
1932
+ // hardening-r5: binary cells (rare for .js/.mjs but defensive) are
1933
+ // not ESM. Skip — looksLikeEsm + esbuild.transform expect strings.
1934
+ if (typeof src !== 'string')
1935
+ continue;
1936
+ if (!looksLikeEsm(src))
1937
+ continue;
1938
+ candidates.push(path);
1939
+ }
1940
+ for (const path of candidates) {
1941
+ const src = bundle[path];
1942
+ if (typeof src !== 'string')
1943
+ continue;
1944
+ // `import.meta.url` substitution mirrors the sibling fix at
1945
+ // src/runtime/runtime-registry.ts:383-389 (framework-gaps-fix P5).
1946
+ // Without `define`, esbuild's CJS transform reduces `import.meta.url`
1947
+ // to undefined (single-pass) or preserves it literally — only to
1948
+ // SyntaxError at `new Function(...)` parse time (two-pass via
1949
+ // EsbuildService.transform's async-IIFE wrap → "Cannot use
1950
+ // 'import.meta' outside a module"). The substitution value is the
1951
+ // real `file:///<absolute-path>` URL — exactly what real Node returns
1952
+ // for an ESM module at that path.
1953
+ //
1954
+ // Note: cache key now incorporates the path because the transformed
1955
+ // output is path-specific (the URL literal is baked in). Two files
1956
+ // with identical source but different paths would otherwise share a
1957
+ // cache entry and the second file would get the first file's URL.
1958
+ const absUrl = 'file:///' + path.replace(/^\/+/, '');
1959
+ const key = __cacheKey(src + '\0' + absUrl);
1960
+ const cached = __esmTransformCache.get(key);
1961
+ if (cached) {
1962
+ bundle[path] = cached;
1963
+ transformed++;
1964
+ continue;
1965
+ }
1966
+ try {
1967
+ const t = await esbuild.transform(src, {
1968
+ loader: 'js',
1969
+ format: 'cjs',
1970
+ target: 'esnext',
1971
+ define: {
1972
+ 'import.meta.url': JSON.stringify(absUrl),
1973
+ },
1974
+ });
1975
+ bundle[path] = t.code;
1976
+ __esmTransformCache.set(key, t.code);
1977
+ transformed++;
1978
+ }
1979
+ catch (e) {
1980
+ // framework-fixes-F4 (2026-05-12): replace the source with a
1981
+ // JS-valid module that throws an INFORMATIVE Error when require'd.
1982
+ //
1983
+ // Pre-fix this catch swallowed the failure silently. The pre-
1984
+ // compile loop then re-saw the ESM source and emitted the
1985
+ // SyntaxError "Cannot use import statement outside a module" into
1986
+ // __compileFailures. __loadModule surfaced that as the user error
1987
+ // — but it told the user nothing about WHY esbuild rejected the
1988
+ // file (was it TLA at module scope? a TypeScript file mistyped
1989
+ // as .js? an unsupported syntax? our esbuild service not init?).
1990
+ //
1991
+ // Post-fix the replacement source is parseable CJS that, when
1992
+ // require()'d, throws a real Error carrying the esbuild reason.
1993
+ // The pre-compile loop succeeds, the file lands in
1994
+ // __compiledModules, and the failure surfaces at require time
1995
+ // with FULL diagnostic context. Net effect for users: the same
1996
+ // upstream tool still fails, but they now see WHY.
1997
+ //
1998
+ // Probe: tests/behavioral/npm-create/new/create-astro-diagnostic.mjs
1999
+ // asserts the user-visible error contains "esbuild transform" so
2000
+ // future diagnostic regressions are caught.
2001
+ const reason = (e && e.message) ? String(e.message).replace(/\n/g, ' ') : String(e);
2002
+ const escapedReason = JSON.stringify(`esbuild transform failed for ${path}: ${reason}`);
2003
+ // Build a valid CJS module that throws on load. Use IIFE to keep
2004
+ // module-scope flat (no TLA, no top-level await). Esbuild-cache
2005
+ // this so repeated submits don't re-run the same transform.
2006
+ const diagnosticSrc = '// framework-fixes-F4 diagnostic shim — esbuild rejected the ESM transform\n' +
2007
+ '(function () { throw new Error(' + escapedReason + '); })();\n';
2008
+ bundle[path] = diagnosticSrc;
2009
+ __esmTransformCache.set(key, diagnosticSrc);
2010
+ failed++;
2011
+ }
2012
+ }
2013
+ return { transformed, failed };
2014
+ }
2015
+ /**
2016
+ * W2.6a: build the prefetch bundle for FacetManager.exec.
2017
+ *
2018
+ * Replaces the legacy whole-tree-with-cap walk that pre-W2.5b shipped
2019
+ * up to 500 files / 4 MiB of node_modules content (whichever ran out
2020
+ * first) with a static reachable-set walk via require-resolver.ts. The
2021
+ * shipped bundle is now bounded by what the user's require() chain
2022
+ * actually reaches PLUS a greedy oversample of every installed pkg's
2023
+ * package.json + main entry (dynamic-require survival).
2024
+ *
2025
+ * Cap is on the JSON-encoded size of the final payload, not on raw
2026
+ * content byte sum. The dynamic worker module embeds the bundle as
2027
+ * `const __MODULE_VFS_BUNDLE = ${JSON.stringify(bundle)}`, so workerd's
2028
+ * per-module text-size limit applies to the encoded form.
2029
+ *
2030
+ * W3.5: now async to allow the optional ESM→CJS pre-pass via esbuild.
2031
+ * If `esbuild` is not provided, the pass is skipped (preserves prior
2032
+ * behaviour for code paths that don't have esbuild handy).
2033
+ *
2034
+ * See audit/sections/W2.6-plan.md §3 (B1+ verdict + sub-agent review).
2035
+ */
2036
+ async function buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild) {
2037
+ // 1. Static reachable-set walk from entry.
2038
+ const prefetch = prefetchForRequire(vfs, entryCode || '', cwd, scriptPath);
2039
+ const bundle = { ...prefetch.bundle };
2040
+ let totalBytes = 0;
2041
+ let fileCount = 0;
2042
+ for (const k of Object.keys(bundle)) {
2043
+ totalBytes += bundle[k].length;
2044
+ fileCount++;
2045
+ }
2046
+ let truncated = prefetch.truncated;
2047
+ // 2. Greedy oversample — every installed pkg's pkg.json + main.
2048
+ // Catches dynamic-require / `bindings()` / plugin-loader cases the
2049
+ // regex prefetch misses. Bounded by VFS_BUNDLE_MAX_BYTES.
2050
+ const budgetState = { totalBytes, fileCount };
2051
+ const greedy = greedyAddMainEntries(vfs, cwd, bundle, budgetState);
2052
+ totalBytes = budgetState.totalBytes;
2053
+ fileCount = budgetState.fileCount;
2054
+ // 2.25 X.5-Z3: static-readFileSync asset prefetch. Scans every
2055
+ // bundle .js/.mjs/.cjs source for the canonical jsdom shape:
2056
+ //
2057
+ // fs.readFileSync(path.resolve(__dirname, "<rel>.css"), …)
2058
+ //
2059
+ // and pulls the matched asset into the bundle. Without this,
2060
+ // `default-stylesheet.css` (and similar runtime asset reads in
2061
+ // tldts, parse5, lookup-table packages, mime-db, etc.) ENOENT
2062
+ // at facet runtime even though the file is on VFS-disk + in
2063
+ // the manifest. See audit/sections/X5Z3-plan.md §3.
2064
+ const assetAdd = addStaticReadFileAssets(vfs, cwd, bundle, budgetState);
2065
+ void assetAdd;
2066
+ totalBytes = budgetState.totalBytes;
2067
+ fileCount = budgetState.fileCount;
2068
+ // 2.27 X.5-U: dotfile + SWC-shape readFileSync sentinel prefetch.
2069
+ // Sibling of `addStaticReadFileAssets` (X.5-Z3) — same call shape,
2070
+ // different match space. Covers the SWC/TS-compiled
2071
+ // `(0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, "<rel>"))`
2072
+ // pattern AND filenames outside the Z3 ASSET_EXT whitelist
2073
+ // (dotfiles, no-extension sentinels, "digest/hash/version/sha/md5"
2074
+ // shapes). Motivating case: ts-jest's `.ts-jest-digest`. See
2075
+ // audit/sections/X5U-plan.md §4.
2076
+ const dotAdd = addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetState);
2077
+ void dotAdd;
2078
+ totalBytes = budgetState.totalBytes;
2079
+ fileCount = budgetState.fileCount;
2080
+ // 2.30 G3 (runtime-pkg wave): bin-target sibling oversample. Pulls
2081
+ // ALL files under the entry's package root (capped at 200) so
2082
+ // bins like cowsay that readFileSync('cows/X.cow') at runtime
2083
+ // find their data files. Existing greedy/asset/dotfile passes
2084
+ // cover JS sources + a hardcoded ASSET_EXT list; this one
2085
+ // catches custom extensions (.cow, .pem, .ttf, .wasm bundled
2086
+ // as data, etc.) without needing a per-pkg whitelist.
2087
+ // No-op when entry isn't inside node_modules.
2088
+ const binSiblingAdd = addBinTargetSiblings(vfs, scriptPath, bundle, budgetState);
2089
+ void binSiblingAdd;
2090
+ totalBytes = budgetState.totalBytes;
2091
+ fileCount = budgetState.fileCount;
2092
+ // 2.35 BUG-SWEEP-R2-2: absolute-path readFileSync scanner.
2093
+ //
2094
+ // Pre-fix `node -e 'fs.readFileSync("/home/user/er.txt")'` returned
2095
+ // ENOENT even when the file existed on the SqliteVFS (verified via
2096
+ // `cat`/`ls`). Cause: buildPrefetchBundle's existing scanners
2097
+ // (greedy main entries, addStaticReadFileAssets, dotfiles, bin
2098
+ // siblings) all look at INSTALLED packages or relative-resolve
2099
+ // patterns inside the bundle's source files. They never scan the
2100
+ // user's ENTRY CODE for absolute-path string literals, so user
2101
+ // files like /home/user/data.json or /tmp/cache.bin never reached
2102
+ // the facet's __vfsBundle.
2103
+ //
2104
+ // Fix: scan entryCode + every JS source already in the bundle for
2105
+ // any string literal that LOOKS like an absolute path (`/x/y/z`,
2106
+ // optionally inside a readFileSync/createReadStream/open call).
2107
+ // For each candidate that exists on the VFS (and isn't already in
2108
+ // the bundle), pull it in within the byte budget.
2109
+ //
2110
+ // Defenses:
2111
+ // - Only path-shaped strings (starts with `/`, length 1-512).
2112
+ // - VFS file existence + non-dir check before adding.
2113
+ // - Byte/file budget guards identical to other passes.
2114
+ // - Skip paths under known-untrusted prefixes (`/proc`, `/sys`,
2115
+ // `/dev` — we don't have these mounts; they'd never resolve).
2116
+ const absScanAdd = addEntryAbsPathReads(vfs, entryCode || '', bundle, budgetState);
2117
+ void absScanAdd;
2118
+ totalBytes = budgetState.totalBytes;
2119
+ fileCount = budgetState.fileCount;
2120
+ // 2.5 W3.5 Fix B: ESM→CJS transform pass. Walks `bundle`, sniffs each
2121
+ // .js/.mjs for top-level import/export, runs esbuild's CJS transform
2122
+ // on the matches, and replaces the value in-place. Without this,
2123
+ // ESM files (e.g. tldts/dist/es6/index.js, @remix-run/react/dist/esm,
2124
+ // @tailwindcss/vite, react-remove-scroll, astro) silently fail
2125
+ // `new Function` at facet startup and surface as the misleading
2126
+ // "file was not pre-bundled" at request time.
2127
+ if (esbuild) {
2128
+ try {
2129
+ await transformEsmInBundle(bundle, esbuild);
2130
+ // Recount bytes after the transform — CJS rebuilds can be larger
2131
+ // OR smaller than the ESM source. We don't try to thread totalBytes
2132
+ // through the transform because the eviction loop below recomputes
2133
+ // the encoded size from scratch anyway.
2134
+ }
2135
+ catch (e) {
2136
+ // framework-fixes-F4 (2026-05-12): the prior catch was silent.
2137
+ // Replace every detected-ESM source with the SAME diagnostic
2138
+ // shim transformEsmInBundle's per-file catch installs, so the
2139
+ // user sees "esbuild transform failed (service-level): <reason>"
2140
+ // instead of a bare "Cannot use import statement..." parse
2141
+ // error with no context.
2142
+ const reason = (e && e.message) ? String(e.message).replace(/\n/g, ' ') : String(e);
2143
+ _markBundleEsmAsFailed(bundle, `esbuild service unavailable: ${reason}`);
2144
+ }
2145
+ }
2146
+ else {
2147
+ // framework-fixes-F4 (2026-05-12): no esbuild service available at
2148
+ // all (lazy-init failed or never wired). Same diagnostic-shim
2149
+ // treatment so users see WHY the ESM file couldn't be transformed.
2150
+ _markBundleEsmAsFailed(bundle, 'esbuild service not initialized (likely lazy-init failure)');
2151
+ }
2152
+ // 3. Manifest pass — UNCHANGED from W2.5b. Decouples directory shape
2153
+ // from content cap so fs.readdirSync remains honest even if the
2154
+ // content for a given file was capped out.
2155
+ const manifest = buildManifest(vfs, cwd, scriptPath);
2156
+ // 4. JSON-encoded-size guard. Pre-check via TextEncoder.encode().length
2157
+ // so we measure UTF-8 bytes (not UTF-16 code units), matching what
2158
+ // workerd accounts against the per-module text-size budget. If the
2159
+ // bundle exceeds the encoded ceiling, evict largest non-manifest
2160
+ // files first (manifest stays — it's needed for readdirSync) and
2161
+ // RECOMPUTE the encoded size after every eviction (sub-agent S2:
2162
+ // naïve `encoded -= len(file) + len(key) + 6` accumulates 2-5% drift
2163
+ // on JS-source-heavy bundles; recomputing is O(n) per eviction but
2164
+ // bundles past the budget are rare and the count of evictions is
2165
+ // bounded by the size of a few large files).
2166
+ const encoder = new TextEncoder();
2167
+ let encoded = encoder.encode(JSON.stringify({ bundle, manifest })).length;
2168
+ if (encoded > BUNDLE_MAX_ENCODED_BYTES) {
2169
+ truncated = true;
2170
+ const keysBySize = Object.keys(bundle).sort((a, b) => bundle[b].length - bundle[a].length);
2171
+ for (const k of keysBySize) {
2172
+ if (encoded <= BUNDLE_MAX_ENCODED_BYTES)
2173
+ break;
2174
+ delete bundle[k];
2175
+ fileCount--;
2176
+ encoded = encoder.encode(JSON.stringify({ bundle, manifest })).length;
2177
+ }
2178
+ }
2179
+ // Suppress lint: `greedy.added` is observed only via diagnostics.
2180
+ void greedy;
2181
+ return { bundle, manifest, reachableCount: fileCount, truncated };
2182
+ }
2183
+ export class FacetManager {
2184
+ ctx;
2185
+ env;
2186
+ processTable;
2187
+ portRegistry;
2188
+ vfs = null;
2189
+ /** Track whether facets API is available (detected on first use). */
2190
+ _hasFacets = null;
2191
+ _facetLogOnce = false;
2192
+ hooks;
2193
+ /**
2194
+ * W3.5 Fix B: lazily-created EsbuildService for the ESM→CJS pre-pass
2195
+ * over the prefetch bundle. Created on first exec where vfs is set;
2196
+ * shared across subsequent execs (warm wasm). Optional setter
2197
+ * `setEsbuildService` lets NimbusSession share its existing instance
2198
+ * to avoid double-init.
2199
+ */
2200
+ esbuild = null;
2201
+ constructor(ctx, env, processTable, portRegistry, hooks = {}) {
2202
+ this.ctx = ctx;
2203
+ this.env = env;
2204
+ this.processTable = processTable;
2205
+ this.portRegistry = portRegistry;
2206
+ this.hooks = hooks;
2207
+ }
2208
+ setVfs(vfs) { this.vfs = vfs; }
2209
+ /**
2210
+ * W3.5 Fix B: hand the FacetManager a pre-warmed EsbuildService for
2211
+ * the ESM→CJS bundle pre-pass. NimbusSession already lazy-creates one
2212
+ * for the user-shell `node` runtime; sharing avoids paying init twice.
2213
+ */
2214
+ setEsbuildService(esbuild) { this.esbuild = esbuild; }
2215
+ /**
2216
+ * Execute JS code in a facet (or fallback dynamic worker).
2217
+ *
2218
+ * Strategy:
2219
+ * 1. Try LOADER.get() + ctx.facets.get() (production: warm reuse, own SQLite)
2220
+ * 2. Fallback: LOADER.load() + getEntrypoint().fetch() (local dev)
2221
+ */
2222
+ async exec(code, opts) {
2223
+ const command = opts.command
2224
+ || (opts.filename && opts.filename !== '<eval>'
2225
+ ? `node ${opts.filename}` : 'node -e ...');
2226
+ let entry;
2227
+ if (opts.skipSpawn && opts.callerPid != null) {
2228
+ // The caller already allocated the PID via processTable.spawn
2229
+ // (with their own user-facing label). Look up the full entry
2230
+ // from the table — _execWithTimeout etc. need the canonical
2231
+ // ProcessEntry shape. Do NOT reap() either: reaping would
2232
+ // clear the caller's just-spawned entry because its startTime
2233
+ // is recent (< 60s) but reap() ALSO drops 'running' entries
2234
+ // older than the threshold; in any case we don't want side
2235
+ // effects when the caller is delegating PID ownership.
2236
+ const found = this.processTable.get(opts.callerPid);
2237
+ if (!found) {
2238
+ throw new Error(`facetMgr.exec skipSpawn: callerPid=${opts.callerPid} not in processTable`);
2239
+ }
2240
+ entry = found;
2241
+ }
2242
+ else {
2243
+ this.processTable.reap();
2244
+ entry = this.processTable.spawn(command, opts.argv || [], opts.cwd || '/home/user');
2245
+ // Short foreground `node -e ...` helpers are quiet by design — only
2246
+ // notify for user-facing `node <file>` invocations, which covers the
2247
+ // real user intent (running scripts, wrangler, etc.).
2248
+ if (opts.filename && opts.filename !== '<eval>') {
2249
+ try {
2250
+ this.hooks.onSpawn?.(entry.pid, command, false);
2251
+ }
2252
+ catch { }
2253
+ }
2254
+ }
2255
+ // W3.5 Fix B: thread an EsbuildService into buildPrefetchBundle so
2256
+ // ESM source files (e.g. tldts/dist/es6/index.js, @remix-run/react,
2257
+ // @tailwindcss/vite, react-remove-scroll, astro) get transformed to
2258
+ // CJS before they hit the facet's `new Function` pre-compile loop.
2259
+ // Lazy-create one if NimbusSession didn't share its own.
2260
+ if (this.vfs && !this.esbuild) {
2261
+ try {
2262
+ this.esbuild = new EsbuildService(this.vfs);
2263
+ }
2264
+ catch {
2265
+ this.esbuild = null;
2266
+ }
2267
+ }
2268
+ const vfsState = this.vfs
2269
+ ? await buildPrefetchBundle(this.vfs, opts.filename, opts.cwd || '/home/user', code, this.esbuild || undefined)
2270
+ : { bundle: {}, manifest: {}, reachableCount: 0, truncated: false };
2271
+ try {
2272
+ let result;
2273
+ // Try facets API first (production path)
2274
+ if (this._hasFacets !== false) {
2275
+ try {
2276
+ result = await this._execWithTimeout(this._execViaFacets(code, opts, entry, vfsState), entry);
2277
+ this._hasFacets = true;
2278
+ this.processTable.exit(entry.pid, result.exitCode);
2279
+ // W5 Lever 5: zero-silent-OOM contract — every non-zero exit
2280
+ // must yield a ring entry.
2281
+ if (result.exitCode !== 0) {
2282
+ this._w5RecordTermination(entry.pid, result.exitCode, 'facet', result.stderr || `exit ${result.exitCode}`);
2283
+ }
2284
+ this._flushVfsWrites(result);
2285
+ return result;
2286
+ }
2287
+ catch (facetErr) {
2288
+ // Detect if it's a facets-unavailable error vs a user code error
2289
+ const isFacetApiError = facetErr?.message?.includes('is not a function') ||
2290
+ facetErr?.message?.includes('facets') ||
2291
+ facetErr?.message?.includes('getDurableObjectClass') ||
2292
+ facetErr?.message?.includes('not available');
2293
+ if (!isFacetApiError) {
2294
+ // User code error — don't fall back. Route through the
2295
+ // external-exit hook so the supervisor's log store records
2296
+ // the exit AND the structured {type:'exit'} notification
2297
+ // fires on the terminal WS (otherwise the tabs UI shows a
2298
+ // stuck "running" dot for facets that crash before they get
2299
+ // a chance to self-report).
2300
+ this.processTable.exit(entry.pid, 1);
2301
+ this._w5RecordTermination(entry.pid, 1, 'facet', 'facet error: ' + (facetErr?.message || String(facetErr)));
2302
+ try {
2303
+ this.hooks.onExternalExit?.(entry.pid, 1, 'facet error: ' + (facetErr?.message || String(facetErr)));
2304
+ }
2305
+ catch { }
2306
+ this._flushVfsWrites({ exitCode: 1, stdout: '', stderr: '', vfsWrites: {} });
2307
+ return { exitCode: 1, stdout: '', stderr: facetErr?.message || String(facetErr) };
2308
+ }
2309
+ // Mark facets as unavailable and fall through
2310
+ if (this._hasFacets === null) {
2311
+ this._hasFacets = false;
2312
+ if (!this._facetLogOnce) {
2313
+ this._facetLogOnce = true;
2314
+ console.log('[nimbus] Facets API unavailable, using LOADER.load() fallback');
2315
+ }
2316
+ }
2317
+ }
2318
+ }
2319
+ // Fallback: LOADER.load() (local dev)
2320
+ result = await this._execWithTimeout(this._execViaLoader(code, opts, entry, vfsState), entry);
2321
+ this.processTable.exit(entry.pid, result.exitCode);
2322
+ if (result.exitCode !== 0) {
2323
+ this._w5RecordTermination(entry.pid, result.exitCode, 'facet', result.stderr || `exit ${result.exitCode}`);
2324
+ }
2325
+ this._flushVfsWrites(result);
2326
+ return result;
2327
+ }
2328
+ catch (err) {
2329
+ // If the timeout already fired, it already called onExternalExit
2330
+ // with code 124 and reason "timeout…". Don't clobber that with a
2331
+ // generic exit code 1. (_reportExternalExit's guard separately
2332
+ // prevents double-dump; this stops ProcessTable from showing a
2333
+ // different exit code than the ring buffer's footer.)
2334
+ const timedOut = !!entry.__timedOut;
2335
+ const exitCode = timedOut ? 124 : 1;
2336
+ this.processTable.exit(entry.pid, exitCode);
2337
+ // W5 Lever 5: ring entry on every catch-path exit.
2338
+ this._w5RecordTermination(entry.pid, exitCode, timedOut ? 'rpc' : 'facet', timedOut
2339
+ ? 'timeout'
2340
+ : ('facet error: ' + (err?.message || String(err))));
2341
+ // Non-timeout failure: route through external-exit so the log
2342
+ // store marks exit AND the tabs-UI structured event fires. The
2343
+ // timeout path already called onExternalExit from the timeout
2344
+ // handler; _reportExternalExit's getExit() guard dedupes.
2345
+ if (!timedOut) {
2346
+ try {
2347
+ this.hooks.onExternalExit?.(entry.pid, exitCode, 'facet error: ' + (err?.message || String(err)));
2348
+ }
2349
+ catch { }
2350
+ }
2351
+ return { exitCode, stdout: '', stderr: err?.message || String(err) };
2352
+ }
2353
+ }
2354
+ /**
2355
+ * W5 Lever 5: push a DiagFailure into the OOM ring for every facet
2356
+ * termination with a non-zero exit code. This is the supervisor side
2357
+ * of the zero-silent-OOM contract — the audit/probes/w5/e2e/
2358
+ * oom-stress probe asserts that every termination has a matching
2359
+ * ring entry.
2360
+ *
2361
+ * Classification: parse the reason/stderr for SQLITE_NOMEM, OOM,
2362
+ * clone-refused, rpc_timeout signatures (oom-classify.ts). Code 124
2363
+ * always maps to rpc_timeout regardless of message.
2364
+ */
2365
+ _w5RecordTermination(pid, exitCode, phase, reason) {
2366
+ try {
2367
+ let cause = classifyError(reason);
2368
+ if (exitCode === 124 && cause === 'unknown')
2369
+ cause = 'rpc_timeout';
2370
+ recordFailure({
2371
+ at: Date.now(),
2372
+ phase,
2373
+ cause,
2374
+ rssEstimateBytes: 0,
2375
+ heapUsedBytes: 0,
2376
+ lruBytes: 0,
2377
+ inFlightBytes: 0,
2378
+ lastRpcFrame: getLastRpcFrame(),
2379
+ lastFacetId: getLastFacetId(),
2380
+ exitCode,
2381
+ pid,
2382
+ message: reason,
2383
+ });
2384
+ }
2385
+ catch (e) {
2386
+ // Fail-soft: telemetry must never break the exit path.
2387
+ console.warn('[facet-manager/W5] recordFailure threw:', e?.message);
2388
+ }
2389
+ }
2390
+ // ── Strategy 1: Real DO Facets (production) ───────────────────────────
2391
+ async _execViaFacets(code, opts, entry, vfsState) {
2392
+ const workerCode = generateFacetCode(code, vfsState);
2393
+ // Scope the LOADER cache key to this DO. Without the doId prefix,
2394
+ // two sessions that happen to execute identical code + vfs key sets
2395
+ // share a warm isolate — and the isolate's baked-in env.SUPERVISOR
2396
+ // stub was minted against whichever DO instantiated the slot FIRST.
2397
+ // Same cross-session-slot-sharing failure mode that b225db3 fixed
2398
+ // for install-time facets in facet-pool.ts; this path wasn't covered.
2399
+ // Cache key includes manifest keys so a re-install that adds packages
2400
+ // mints a fresh isolate (otherwise the warm slot's baked-in manifest
2401
+ // would still hide newly-installed packages from readdirSync).
2402
+ const codeId = `${this.ctx.id.toString()}:${hashCode(code + JSON.stringify(Object.keys(vfsState.bundle)) + JSON.stringify(Object.keys(vfsState.manifest)))}`;
2403
+ // LOADER.get(id, callback) — creates/reuses a warm dynamic worker
2404
+ // Pass SUPERVISOR binding for facet → supervisor RPC
2405
+ const ctxExports = getCtxExports();
2406
+ const supervisorBinding = ctxExports?.SupervisorRPC
2407
+ ? ctxExports.SupervisorRPC({ props: { doId: this.ctx.id.toString(), pid: entry.pid } })
2408
+ : undefined;
2409
+ const worker = this.env.LOADER.get(codeId, async () => ({
2410
+ compatibilityDate: CF_COMPAT_DATE,
2411
+ compatibilityFlags: ['nodejs_compat', 'nodejs_compat_v2'],
2412
+ mainModule: 'runner.js',
2413
+ modules: { 'runner.js': workerCode },
2414
+ ...(supervisorBinding ? { env: { SUPERVISOR: supervisorBinding } } : {}),
2415
+ }));
2416
+ // Get the DurableObject class from the dynamic worker
2417
+ const NodeProcessClass = worker.getDurableObjectClass('NodeProcess');
2418
+ // ctx.facets.get(name, callback) — creates a child DO with its own SQLite
2419
+ const facetName = `proc-${entry.pid}`;
2420
+ const facet = this.ctx.facets.get(facetName, async () => ({
2421
+ class: NodeProcessClass,
2422
+ }));
2423
+ // RPC: call the facet's run() method (vfsBundle is embedded in the module code)
2424
+ const argsJson = JSON.stringify({
2425
+ argv: opts.argv || [],
2426
+ env: opts.env || {},
2427
+ cwd: opts.cwd || '/home/user',
2428
+ filename: opts.filename || '<eval>',
2429
+ dirname: opts.dirname || '/home/user',
2430
+ });
2431
+ // Clean up the facet (free SQLite storage) regardless of outcome.
2432
+ // AUDIT.md M3 / STABILITY-AUDIT.md M-S2 / TOP-5-NEXT.md #3 (A3):
2433
+ // prior to this, facets.delete ran only on the success branch.
2434
+ // Timeouts (via _execWithTimeout) and user-code errors (throwing
2435
+ // from facet.run) both left the ctx.facets entry and its per-facet
2436
+ // SQLite storage slot orphaned until DO hibernation. Try/finally
2437
+ // ensures cleanup for every exit path.
2438
+ try {
2439
+ const resultJson = await facet.run(argsJson);
2440
+ const result = JSON.parse(resultJson);
2441
+ return result;
2442
+ }
2443
+ finally {
2444
+ try {
2445
+ this.ctx.facets.delete(facetName);
2446
+ }
2447
+ catch { }
2448
+ }
2449
+ }
2450
+ // ── Strategy 2: LOADER.load() fallback (local dev) ────────────────────
2451
+ async _execViaLoader(code, opts, entry, vfsState) {
2452
+ const workerCode = generateEntrypointCode(code, vfsState);
2453
+ // Pass SUPERVISOR binding for facet → supervisor RPC
2454
+ const ctxExports = getCtxExports();
2455
+ const supervisorBinding = ctxExports?.SupervisorRPC
2456
+ ? ctxExports.SupervisorRPC({ props: { doId: this.ctx.id.toString(), pid: entry.pid } })
2457
+ : undefined;
2458
+ const worker = this.env.LOADER.load({
2459
+ compatibilityDate: CF_COMPAT_DATE,
2460
+ compatibilityFlags: ['nodejs_compat', 'nodejs_compat_v2'],
2461
+ mainModule: 'runner.js',
2462
+ modules: { 'runner.js': workerCode },
2463
+ ...(supervisorBinding ? { env: { SUPERVISOR: supervisorBinding } } : {}),
2464
+ });
2465
+ const entrypoint = worker.getEntrypoint();
2466
+ const body = JSON.stringify({
2467
+ argv: opts.argv || [],
2468
+ env: opts.env || {},
2469
+ cwd: opts.cwd || '/home/user',
2470
+ filename: opts.filename || '<eval>',
2471
+ dirname: opts.dirname || '/home/user',
2472
+ });
2473
+ const response = await entrypoint.fetch(new Request('http://facet/run', {
2474
+ method: 'POST',
2475
+ body,
2476
+ }));
2477
+ return await response.json();
2478
+ }
2479
+ /** Flush files written by the script back to the supervisor's VFS. */
2480
+ _flushVfsWrites(result) {
2481
+ if (!this.vfs || !result.vfsWrites)
2482
+ return;
2483
+ for (const [path, content] of Object.entries(result.vfsWrites)) {
2484
+ try {
2485
+ const parts = path.split('/');
2486
+ for (let i = 1; i < parts.length; i++) {
2487
+ const dir = parts.slice(0, i).join('/');
2488
+ if (dir && !this.vfs.exists(dir))
2489
+ this.vfs.mkdir(dir, { recursive: true });
2490
+ }
2491
+ // binary-fs wave: __vfsWrites cells carry string | Uint8Array.
2492
+ // The hot path here is the LIVE SUPERVISOR.writeFile RPC inside
2493
+ // the facet — which preserves Uint8Array via structured-clone.
2494
+ // This `result.vfsWrites` carries only the FAILED-writes residue
2495
+ // (after JSON.parse), where Uint8Array gets serialized as a
2496
+ // {"0":...,"1":...} object. Detect that shape and reconstitute
2497
+ // bytes; otherwise pass through (string for source code, etc.).
2498
+ const restored = _reviveVfsWriteCell(content);
2499
+ this.vfs.writeFile(path, restored);
2500
+ }
2501
+ catch (e) {
2502
+ console.error('[nimbus] VFS write-back failed:', path, e?.message);
2503
+ }
2504
+ }
2505
+ }
2506
+ /** Execution timeout. */
2507
+ async _execWithTimeout(promise, entry) {
2508
+ let timer;
2509
+ let timedOut = false;
2510
+ const timeout = new Promise((_, reject) => {
2511
+ timer = setTimeout(() => {
2512
+ timedOut = true;
2513
+ // Try to abort the facet
2514
+ try {
2515
+ this.ctx.facets?.abort(`proc-${entry.pid}`, new Error('timeout'));
2516
+ }
2517
+ catch { }
2518
+ // The facet's `reportExit` never runs — notify the session so
2519
+ // the ring buffer gets marked and any dump fires.
2520
+ try {
2521
+ this.hooks.onExternalExit?.(entry.pid, 124, // conventional timeout exit code
2522
+ `timeout after ${FACET_TIMEOUT_MS / 1000}s`);
2523
+ }
2524
+ catch { }
2525
+ reject(new Error(`Process timed out after ${FACET_TIMEOUT_MS / 1000}s`));
2526
+ }, FACET_TIMEOUT_MS);
2527
+ });
2528
+ // Always clear the timer; otherwise a successful run would still
2529
+ // trigger the timeout callback at FACET_TIMEOUT_MS, spuriously
2530
+ // marking the exit code as 124.
2531
+ try {
2532
+ return await Promise.race([promise, timeout]);
2533
+ }
2534
+ finally {
2535
+ clearTimeout(timer);
2536
+ // Expose the flag so callers (exec's outer catch) can avoid
2537
+ // overriding the exit code that onExternalExit already stamped.
2538
+ entry.__timedOut = timedOut;
2539
+ }
2540
+ }
2541
+ /**
2542
+ * Run npm install in a dedicated facet.
2543
+ * All writes go through SUPERVISOR.writeFile (live VFS),
2544
+ * progress streams via SUPERVISOR.stdout.
2545
+ */
2546
+ /**
2547
+ * Spawn a vite dev server facet.
2548
+ * Returns immediately with the facet stub for HTTP routing.
2549
+ */
2550
+ spawnVite(root, basePath = '/preview') {
2551
+ const code = generateViteFacetCode(root, basePath);
2552
+ return this.spawn(code, 'vite (' + root + ')', root);
2553
+ }
2554
+ /**
2555
+ * Spawn a long-running Node process with the same shimmed require/fs/http
2556
+ * environment used by foreground `node <script>` execution.
2557
+ */
2558
+ async spawnNode(code, opts = {}) {
2559
+ this.processTable.reap();
2560
+ const command = opts.command || (opts.filename ? `node ${opts.filename}` : 'node <script>');
2561
+ const cwd = opts.cwd || '/home/user';
2562
+ const entry = this.processTable.spawn(command, opts.argv || [], cwd);
2563
+ this.processTable.setLongRunning(entry.pid);
2564
+ try {
2565
+ this.hooks.onSpawn?.(entry.pid, command, true);
2566
+ }
2567
+ catch { }
2568
+ if (this.vfs && !this.esbuild) {
2569
+ try {
2570
+ this.esbuild = new EsbuildService(this.vfs);
2571
+ }
2572
+ catch {
2573
+ this.esbuild = null;
2574
+ }
2575
+ }
2576
+ const vfsState = this.vfs
2577
+ ? await buildPrefetchBundle(this.vfs, opts.filename, cwd, code, this.esbuild || undefined)
2578
+ : { bundle: {}, manifest: {}, reachableCount: 0, truncated: false };
2579
+ const workerCode = generateLongRunningNodeCode(code, vfsState, opts);
2580
+ const ctxExports = getCtxExports();
2581
+ const supervisorBinding = ctxExports?.SupervisorRPC
2582
+ ? ctxExports.SupervisorRPC({ props: { doId: this.ctx.id.toString(), pid: entry.pid } })
2583
+ : undefined;
2584
+ try {
2585
+ const worker = this.env.LOADER.load({
2586
+ compatibilityDate: CF_COMPAT_DATE,
2587
+ compatibilityFlags: ['nodejs_compat', 'nodejs_compat_v2'],
2588
+ mainModule: 'worker.js',
2589
+ modules: { 'worker.js': workerCode },
2590
+ ...(supervisorBinding ? { env: { SUPERVISOR: supervisorBinding } } : {}),
2591
+ });
2592
+ const facetStub = worker.getEntrypoint();
2593
+ const bootResponse = await facetStub.fetch(new Request('http://nimbus.internal/__nimbus_start', {
2594
+ headers: { 'X-Nimbus-Internal-Boot': '1' },
2595
+ }));
2596
+ if (!bootResponse.ok) {
2597
+ const body = await bootResponse.text().catch(() => '');
2598
+ throw new Error(body || `long-running node boot failed with HTTP ${bootResponse.status}`);
2599
+ }
2600
+ if (opts.port && opts.port > 0 && opts.port < 65536) {
2601
+ this.portRegistry.register(opts.port, entry.pid, facetStub);
2602
+ }
2603
+ return { pid: entry.pid, facetStub };
2604
+ }
2605
+ catch (e) {
2606
+ this.processTable.exit(entry.pid, 1);
2607
+ this._w5RecordTermination(entry.pid, 1, 'facet', 'long-running node boot failed: ' + (e?.message || String(e)));
2608
+ try {
2609
+ this.hooks.onExternalExit?.(entry.pid, 1, 'long-running node boot failed: ' + (e?.message || String(e)));
2610
+ }
2611
+ catch { }
2612
+ throw e;
2613
+ }
2614
+ }
2615
+ /**
2616
+ * Spawn a long-running facet process.
2617
+ * Returns immediately with the process entry.
2618
+ * The facet stays alive and can handle HTTP requests via its fetch() method.
2619
+ * Used for: vite dev server, node HTTP servers, etc.
2620
+ *
2621
+ * @param workerCode The dynamic worker code (must export a default fetch handler)
2622
+ * @param command Display name for process listing
2623
+ * @returns Process entry with pid and facet stub
2624
+ */
2625
+ spawn(workerCode, command, cwd, opts = {}) {
2626
+ this.processTable.reap();
2627
+ const entry = this.processTable.spawn(command, [], cwd);
2628
+ // arch-gaps gap #2: stamp the explicit longRunning flag on the
2629
+ // process_table entry so /api/processes returns longRunning=true
2630
+ // independent of the LONG_RUNNING_CMD_RE heuristic. Vite, wrangler,
2631
+ // node servers, --watch, etc. all flow through this primitive.
2632
+ this.processTable.setLongRunning(entry.pid);
2633
+ // Long-running facets (vite, nimbus-wrangler, node servers) always
2634
+ // get a spawn notification — they're visible and users want to know
2635
+ // the PID for later `logs`/`kill`.
2636
+ try {
2637
+ this.hooks.onSpawn?.(entry.pid, command, true);
2638
+ }
2639
+ catch { }
2640
+ const ctxExports = getCtxExports();
2641
+ const supervisorBinding = ctxExports?.SupervisorRPC
2642
+ ? ctxExports.SupervisorRPC({ props: { doId: this.ctx.id.toString(), pid: entry.pid } })
2643
+ : undefined;
2644
+ const worker = this.env.LOADER.load({
2645
+ compatibilityDate: CF_COMPAT_DATE,
2646
+ compatibilityFlags: ['nodejs_compat'],
2647
+ mainModule: 'worker.js',
2648
+ modules: { 'worker.js': workerCode },
2649
+ ...(supervisorBinding ? { env: { SUPERVISOR: supervisorBinding } } : {}),
2650
+ });
2651
+ const facetStub = worker.getEntrypoint();
2652
+ if (opts.port && opts.port > 0 && opts.port < 65536) {
2653
+ this.portRegistry.register(opts.port, entry.pid, facetStub);
2654
+ }
2655
+ return { pid: entry.pid, facetStub };
2656
+ }
2657
+ /** Kill a running process by PID. */
2658
+ kill(pid) {
2659
+ const entry = this.processTable.get(pid);
2660
+ if (!entry || entry.state !== 'running')
2661
+ return false;
2662
+ try {
2663
+ this.ctx.facets?.abort(`proc-${entry.pid}`, new Error('SIGKILL'));
2664
+ }
2665
+ catch { }
2666
+ try {
2667
+ this.ctx.facets?.delete(`proc-${entry.pid}`);
2668
+ }
2669
+ catch { }
2670
+ this.portRegistry.unregisterByPid(pid);
2671
+ const result = this.processTable.kill(pid);
2672
+ if (result) {
2673
+ try {
2674
+ this.hooks.onExternalExit?.(pid, 137, 'killed');
2675
+ }
2676
+ catch { }
2677
+ }
2678
+ return result;
2679
+ }
2680
+ get stats() { return this.processTable.stats; }
2681
+ }