@seanhogg/builderforce-webcontainers-core 2026.10.2 → 2026.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/dist/build.d.ts +42 -0
  2. package/dist/build.d.ts.map +1 -0
  3. package/dist/build.js +65 -0
  4. package/dist/build.js.map +1 -0
  5. package/dist/buildHost.d.ts +31 -0
  6. package/dist/buildHost.d.ts.map +1 -0
  7. package/dist/buildHost.js +125 -0
  8. package/dist/buildHost.js.map +1 -0
  9. package/dist/buildHtml.d.ts +28 -0
  10. package/dist/buildHtml.d.ts.map +1 -0
  11. package/dist/buildHtml.js +74 -0
  12. package/dist/buildHtml.js.map +1 -0
  13. package/dist/bundler.d.ts +129 -0
  14. package/dist/bundler.d.ts.map +1 -0
  15. package/dist/bundler.js +79 -0
  16. package/dist/bundler.js.map +1 -0
  17. package/dist/check/acquire.d.ts +46 -0
  18. package/dist/check/acquire.d.ts.map +1 -0
  19. package/dist/check/acquire.js +179 -0
  20. package/dist/check/acquire.js.map +1 -0
  21. package/dist/check/compilerHost.d.ts +22 -0
  22. package/dist/check/compilerHost.d.ts.map +1 -0
  23. package/dist/check/compilerHost.js +128 -0
  24. package/dist/check/compilerHost.js.map +1 -0
  25. package/dist/check/diagnostics.d.ts +23 -0
  26. package/dist/check/diagnostics.d.ts.map +1 -0
  27. package/dist/check/diagnostics.js +28 -0
  28. package/dist/check/diagnostics.js.map +1 -0
  29. package/dist/check/index.d.ts +19 -0
  30. package/dist/check/index.d.ts.map +1 -0
  31. package/dist/check/index.js +13 -0
  32. package/dist/check/index.js.map +1 -0
  33. package/dist/check/projects.d.ts +26 -0
  34. package/dist/check/projects.d.ts.map +1 -0
  35. package/dist/check/projects.js +124 -0
  36. package/dist/check/projects.js.map +1 -0
  37. package/dist/check/shims.d.ts +29 -0
  38. package/dist/check/shims.d.ts.map +1 -0
  39. package/dist/check/shims.js +76 -0
  40. package/dist/check/shims.js.map +1 -0
  41. package/dist/check/typeStore.d.ts +57 -0
  42. package/dist/check/typeStore.d.ts.map +1 -0
  43. package/dist/check/typeStore.js +89 -0
  44. package/dist/check/typeStore.js.map +1 -0
  45. package/dist/check/typecheck.d.ts +29 -0
  46. package/dist/check/typecheck.d.ts.map +1 -0
  47. package/dist/check/typecheck.js +91 -0
  48. package/dist/check/typecheck.js.map +1 -0
  49. package/dist/compileComponent.d.ts +8 -0
  50. package/dist/compileComponent.d.ts.map +1 -0
  51. package/dist/compileComponent.js +22 -0
  52. package/dist/compileComponent.js.map +1 -0
  53. package/dist/compileScript.d.ts +19 -4
  54. package/dist/compileScript.d.ts.map +1 -1
  55. package/dist/compileScript.js +37 -12
  56. package/dist/compileScript.js.map +1 -1
  57. package/dist/components.d.ts +41 -0
  58. package/dist/components.d.ts.map +1 -0
  59. package/dist/components.js +14 -0
  60. package/dist/components.js.map +1 -0
  61. package/dist/detectProject.d.ts +3 -0
  62. package/dist/detectProject.d.ts.map +1 -1
  63. package/dist/detectProject.js +16 -6
  64. package/dist/detectProject.js.map +1 -1
  65. package/dist/devServer.d.ts +5 -2
  66. package/dist/devServer.d.ts.map +1 -1
  67. package/dist/devServer.js +10 -6
  68. package/dist/devServer.js.map +1 -1
  69. package/dist/hash.d.ts +2 -0
  70. package/dist/hash.d.ts.map +1 -0
  71. package/dist/hash.js +22 -0
  72. package/dist/hash.js.map +1 -0
  73. package/dist/index.d.ts +24 -6
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +14 -3
  76. package/dist/index.js.map +1 -1
  77. package/dist/installer/bins.d.ts +24 -0
  78. package/dist/installer/bins.d.ts.map +1 -0
  79. package/dist/installer/bins.js +56 -0
  80. package/dist/installer/bins.js.map +1 -0
  81. package/dist/installer/index.d.ts +14 -0
  82. package/dist/installer/index.d.ts.map +1 -0
  83. package/dist/installer/index.js +9 -0
  84. package/dist/installer/index.js.map +1 -0
  85. package/dist/installer/install.d.ts +46 -0
  86. package/dist/installer/install.d.ts.map +1 -0
  87. package/dist/installer/install.js +245 -0
  88. package/dist/installer/install.js.map +1 -0
  89. package/dist/installer/lockfile.d.ts +49 -0
  90. package/dist/installer/lockfile.d.ts.map +1 -0
  91. package/dist/installer/lockfile.js +110 -0
  92. package/dist/installer/lockfile.js.map +1 -0
  93. package/dist/installer/registry.d.ts +84 -0
  94. package/dist/installer/registry.d.ts.map +1 -0
  95. package/dist/installer/registry.js +130 -0
  96. package/dist/installer/registry.js.map +1 -0
  97. package/dist/installer/semver.d.ts +38 -0
  98. package/dist/installer/semver.d.ts.map +1 -0
  99. package/dist/installer/semver.js +187 -0
  100. package/dist/installer/semver.js.map +1 -0
  101. package/dist/installer/spec.d.ts +30 -0
  102. package/dist/installer/spec.d.ts.map +1 -0
  103. package/dist/installer/spec.js +30 -0
  104. package/dist/installer/spec.js.map +1 -0
  105. package/dist/installer/tar.d.ts +17 -0
  106. package/dist/installer/tar.d.ts.map +1 -0
  107. package/dist/installer/tar.js +150 -0
  108. package/dist/installer/tar.js.map +1 -0
  109. package/dist/installer/tree.d.ts +48 -0
  110. package/dist/installer/tree.d.ts.map +1 -0
  111. package/dist/installer/tree.js +179 -0
  112. package/dist/installer/tree.js.map +1 -0
  113. package/dist/node/assert.d.ts +15 -0
  114. package/dist/node/assert.d.ts.map +1 -0
  115. package/dist/node/assert.js +137 -0
  116. package/dist/node/assert.js.map +1 -0
  117. package/dist/node/buffer.d.ts +84 -0
  118. package/dist/node/buffer.d.ts.map +1 -0
  119. package/dist/node/buffer.js +304 -0
  120. package/dist/node/buffer.js.map +1 -0
  121. package/dist/node/builtins.d.ts +25 -0
  122. package/dist/node/builtins.d.ts.map +1 -0
  123. package/dist/node/builtins.js +101 -0
  124. package/dist/node/builtins.js.map +1 -0
  125. package/dist/node/callable.d.ts +13 -0
  126. package/dist/node/callable.d.ts.map +1 -0
  127. package/dist/node/callable.js +26 -0
  128. package/dist/node/callable.js.map +1 -0
  129. package/dist/node/childProcess.d.ts +13 -0
  130. package/dist/node/childProcess.d.ts.map +1 -0
  131. package/dist/node/childProcess.js +143 -0
  132. package/dist/node/childProcess.js.map +1 -0
  133. package/dist/node/crypto.d.ts +3 -0
  134. package/dist/node/crypto.d.ts.map +1 -0
  135. package/dist/node/crypto.js +180 -0
  136. package/dist/node/crypto.js.map +1 -0
  137. package/dist/node/esm.d.ts +9 -0
  138. package/dist/node/esm.d.ts.map +1 -0
  139. package/dist/node/esm.js +202 -0
  140. package/dist/node/esm.js.map +1 -0
  141. package/dist/node/events.d.ts +40 -0
  142. package/dist/node/events.d.ts.map +1 -0
  143. package/dist/node/events.js +160 -0
  144. package/dist/node/events.js.map +1 -0
  145. package/dist/node/fs/index.d.ts +7 -0
  146. package/dist/node/fs/index.d.ts.map +1 -0
  147. package/dist/node/fs/index.js +270 -0
  148. package/dist/node/fs/index.js.map +1 -0
  149. package/dist/node/fs/stats.d.ts +93 -0
  150. package/dist/node/fs/stats.d.ts.map +1 -0
  151. package/dist/node/fs/stats.js +104 -0
  152. package/dist/node/fs/stats.js.map +1 -0
  153. package/dist/node/fs/sync.d.ts +98 -0
  154. package/dist/node/fs/sync.d.ts.map +1 -0
  155. package/dist/node/fs/sync.js +348 -0
  156. package/dist/node/fs/sync.js.map +1 -0
  157. package/dist/node/hashes.d.ts +15 -0
  158. package/dist/node/hashes.d.ts.map +1 -0
  159. package/dist/node/hashes.js +195 -0
  160. package/dist/node/hashes.js.map +1 -0
  161. package/dist/node/http/client.d.ts +35 -0
  162. package/dist/node/http/client.d.ts.map +1 -0
  163. package/dist/node/http/client.js +161 -0
  164. package/dist/node/http/client.js.map +1 -0
  165. package/dist/node/http/index.d.ts +5 -0
  166. package/dist/node/http/index.d.ts.map +1 -0
  167. package/dist/node/http/index.js +125 -0
  168. package/dist/node/http/index.js.map +1 -0
  169. package/dist/node/http/server.d.ts +135 -0
  170. package/dist/node/http/server.d.ts.map +1 -0
  171. package/dist/node/http/server.js +308 -0
  172. package/dist/node/http/server.js.map +1 -0
  173. package/dist/node/http/status.d.ts +3 -0
  174. package/dist/node/http/status.d.ts.map +1 -0
  175. package/dist/node/http/status.js +13 -0
  176. package/dist/node/http/status.js.map +1 -0
  177. package/dist/node/inspect.d.ts +22 -0
  178. package/dist/node/inspect.d.ts.map +1 -0
  179. package/dist/node/inspect.js +172 -0
  180. package/dist/node/inspect.js.map +1 -0
  181. package/dist/node/loader.d.ts +91 -0
  182. package/dist/node/loader.d.ts.map +1 -0
  183. package/dist/node/loader.js +265 -0
  184. package/dist/node/loader.js.map +1 -0
  185. package/dist/node/loop.d.ts +71 -0
  186. package/dist/node/loop.d.ts.map +1 -0
  187. package/dist/node/loop.js +189 -0
  188. package/dist/node/loop.js.map +1 -0
  189. package/dist/node/misc.d.ts +10 -0
  190. package/dist/node/misc.d.ts.map +1 -0
  191. package/dist/node/misc.js +256 -0
  192. package/dist/node/misc.js.map +1 -0
  193. package/dist/node/path.d.ts +14 -0
  194. package/dist/node/path.d.ts.map +1 -0
  195. package/dist/node/path.js +128 -0
  196. package/dist/node/path.js.map +1 -0
  197. package/dist/node/process.d.ts +31 -0
  198. package/dist/node/process.d.ts.map +1 -0
  199. package/dist/node/process.js +309 -0
  200. package/dist/node/process.js.map +1 -0
  201. package/dist/node/program.d.ts +19 -0
  202. package/dist/node/program.d.ts.map +1 -0
  203. package/dist/node/program.js +165 -0
  204. package/dist/node/program.js.map +1 -0
  205. package/dist/node/readline.d.ts +2 -0
  206. package/dist/node/readline.d.ts.map +1 -0
  207. package/dist/node/readline.js +155 -0
  208. package/dist/node/readline.js.map +1 -0
  209. package/dist/node/resolve.d.ts +45 -0
  210. package/dist/node/resolve.d.ts.map +1 -0
  211. package/dist/node/resolve.js +206 -0
  212. package/dist/node/resolve.js.map +1 -0
  213. package/dist/node/runtime.d.ts +40 -0
  214. package/dist/node/runtime.d.ts.map +1 -0
  215. package/dist/node/runtime.js +149 -0
  216. package/dist/node/runtime.js.map +1 -0
  217. package/dist/node/stream/index.d.ts +52 -0
  218. package/dist/node/stream/index.d.ts.map +1 -0
  219. package/dist/node/stream/index.js +184 -0
  220. package/dist/node/stream/index.js.map +1 -0
  221. package/dist/node/stream/readable.d.ts +83 -0
  222. package/dist/node/stream/readable.d.ts.map +1 -0
  223. package/dist/node/stream/readable.js +325 -0
  224. package/dist/node/stream/readable.js.map +1 -0
  225. package/dist/node/stream/writable.d.ts +80 -0
  226. package/dist/node/stream/writable.d.ts.map +1 -0
  227. package/dist/node/stream/writable.js +224 -0
  228. package/dist/node/stream/writable.js.map +1 -0
  229. package/dist/node/urlqs.d.ts +22 -0
  230. package/dist/node/urlqs.d.ts.map +1 -0
  231. package/dist/node/urlqs.js +165 -0
  232. package/dist/node/urlqs.js.map +1 -0
  233. package/dist/node/util.d.ts +37 -0
  234. package/dist/node/util.d.ts.map +1 -0
  235. package/dist/node/util.js +160 -0
  236. package/dist/node/util.js.map +1 -0
  237. package/dist/node/zlib.d.ts +2 -0
  238. package/dist/node/zlib.d.ts.map +1 -0
  239. package/dist/node/zlib.js +133 -0
  240. package/dist/node/zlib.js.map +1 -0
  241. package/dist/packageCdn.d.ts +12 -0
  242. package/dist/packageCdn.d.ts.map +1 -1
  243. package/dist/packageCdn.js +3 -1
  244. package/dist/packageCdn.js.map +1 -1
  245. package/dist/projectConfig.d.ts +6 -2
  246. package/dist/projectConfig.d.ts.map +1 -1
  247. package/dist/projectConfig.js +32 -8
  248. package/dist/projectConfig.js.map +1 -1
  249. package/dist/shell/builtins.d.ts +23 -0
  250. package/dist/shell/builtins.d.ts.map +1 -0
  251. package/dist/shell/builtins.js +131 -0
  252. package/dist/shell/builtins.js.map +1 -0
  253. package/dist/shell/context.d.ts +41 -0
  254. package/dist/shell/context.d.ts.map +1 -0
  255. package/dist/shell/context.js +65 -0
  256. package/dist/shell/context.js.map +1 -0
  257. package/dist/shell/devHandoff.d.ts +18 -0
  258. package/dist/shell/devHandoff.d.ts.map +1 -0
  259. package/dist/shell/devHandoff.js +36 -0
  260. package/dist/shell/devHandoff.js.map +1 -0
  261. package/dist/shell/exec.d.ts +67 -0
  262. package/dist/shell/exec.d.ts.map +1 -0
  263. package/dist/shell/exec.js +247 -0
  264. package/dist/shell/exec.js.map +1 -0
  265. package/dist/shell/expand.d.ts +28 -0
  266. package/dist/shell/expand.d.ts.map +1 -0
  267. package/dist/shell/expand.js +149 -0
  268. package/dist/shell/expand.js.map +1 -0
  269. package/dist/shell/fileCommands.d.ts +10 -0
  270. package/dist/shell/fileCommands.d.ts.map +1 -0
  271. package/dist/shell/fileCommands.js +208 -0
  272. package/dist/shell/fileCommands.js.map +1 -0
  273. package/dist/shell/lineEditor.d.ts +43 -0
  274. package/dist/shell/lineEditor.d.ts.map +1 -0
  275. package/dist/shell/lineEditor.js +234 -0
  276. package/dist/shell/lineEditor.js.map +1 -0
  277. package/dist/shell/npm.d.ts +6 -0
  278. package/dist/shell/npm.d.ts.map +1 -0
  279. package/dist/shell/npm.js +234 -0
  280. package/dist/shell/npm.js.map +1 -0
  281. package/dist/shell/parse.d.ts +73 -0
  282. package/dist/shell/parse.d.ts.map +1 -0
  283. package/dist/shell/parse.js +300 -0
  284. package/dist/shell/parse.js.map +1 -0
  285. package/dist/shell/program.d.ts +12 -0
  286. package/dist/shell/program.d.ts.map +1 -0
  287. package/dist/shell/program.js +219 -0
  288. package/dist/shell/program.js.map +1 -0
  289. package/dist/shell/textCommands.d.ts +15 -0
  290. package/dist/shell/textCommands.d.ts.map +1 -0
  291. package/dist/shell/textCommands.js +185 -0
  292. package/dist/shell/textCommands.js.map +1 -0
  293. package/dist/styleModules.d.ts +6 -0
  294. package/dist/styleModules.d.ts.map +1 -1
  295. package/dist/styleModules.js +14 -4
  296. package/dist/styleModules.js.map +1 -1
  297. package/dist/svelteCompiler.d.ts +28 -0
  298. package/dist/svelteCompiler.d.ts.map +1 -0
  299. package/dist/svelteCompiler.js +13 -0
  300. package/dist/svelteCompiler.js.map +1 -0
  301. package/dist/system/commands.d.ts +26 -0
  302. package/dist/system/commands.d.ts.map +1 -0
  303. package/dist/system/commands.js +53 -0
  304. package/dist/system/commands.js.map +1 -0
  305. package/dist/system/index.d.ts +23 -0
  306. package/dist/system/index.d.ts.map +1 -0
  307. package/dist/system/index.js +16 -0
  308. package/dist/system/index.js.map +1 -0
  309. package/dist/system/kernel.d.ts +54 -0
  310. package/dist/system/kernel.d.ts.map +1 -0
  311. package/dist/system/kernel.js +81 -0
  312. package/dist/system/kernel.js.map +1 -0
  313. package/dist/system/ports.d.ts +32 -0
  314. package/dist/system/ports.d.ts.map +1 -0
  315. package/dist/system/ports.js +57 -0
  316. package/dist/system/ports.js.map +1 -0
  317. package/dist/system/process.d.ts +32 -0
  318. package/dist/system/process.d.ts.map +1 -0
  319. package/dist/system/process.js +130 -0
  320. package/dist/system/process.js.map +1 -0
  321. package/dist/system/programs.d.ts +10 -0
  322. package/dist/system/programs.d.ts.map +1 -0
  323. package/dist/system/programs.js +22 -0
  324. package/dist/system/programs.js.map +1 -0
  325. package/dist/system/terminal.d.ts +45 -0
  326. package/dist/system/terminal.d.ts.map +1 -0
  327. package/dist/system/terminal.js +127 -0
  328. package/dist/system/terminal.js.map +1 -0
  329. package/dist/system/types.d.ts +100 -0
  330. package/dist/system/types.d.ts.map +1 -0
  331. package/dist/system/types.js +2 -0
  332. package/dist/system/types.js.map +1 -0
  333. package/dist/vfs.d.ts +28 -4
  334. package/dist/vfs.d.ts.map +1 -1
  335. package/dist/vfs.js +95 -32
  336. package/dist/vfs.js.map +1 -1
  337. package/dist/vueCompiler.d.ts +75 -0
  338. package/dist/vueCompiler.d.ts.map +1 -0
  339. package/dist/vueCompiler.js +67 -0
  340. package/dist/vueCompiler.js.map +1 -0
  341. package/package.json +18 -2
  342. package/src/build.ts +105 -0
  343. package/src/buildHost.ts +154 -0
  344. package/src/buildHtml.ts +101 -0
  345. package/src/bundler.ts +190 -0
  346. package/src/check/acquire.ts +212 -0
  347. package/src/check/compilerHost.ts +138 -0
  348. package/src/check/diagnostics.ts +48 -0
  349. package/src/check/index.ts +28 -0
  350. package/src/check/projects.ts +149 -0
  351. package/src/check/shims.ts +88 -0
  352. package/src/check/typeStore.ts +103 -0
  353. package/src/check/typecheck.ts +134 -0
  354. package/src/compileComponent.ts +21 -0
  355. package/src/compileScript.ts +42 -13
  356. package/src/components.ts +59 -0
  357. package/src/detectProject.ts +18 -6
  358. package/src/devServer.ts +13 -8
  359. package/src/hash.ts +22 -0
  360. package/src/index.ts +35 -6
  361. package/src/installer/bins.ts +64 -0
  362. package/src/installer/index.ts +13 -0
  363. package/src/installer/install.ts +275 -0
  364. package/src/installer/lockfile.ts +146 -0
  365. package/src/installer/registry.ts +175 -0
  366. package/src/installer/semver.ts +190 -0
  367. package/src/installer/spec.ts +53 -0
  368. package/src/installer/tar.ts +155 -0
  369. package/src/installer/tree.ts +236 -0
  370. package/src/node/assert.ts +127 -0
  371. package/src/node/buffer.ts +304 -0
  372. package/src/node/builtins.ts +123 -0
  373. package/src/node/callable.ts +25 -0
  374. package/src/node/childProcess.ts +164 -0
  375. package/src/node/crypto.ts +187 -0
  376. package/src/node/esm.ts +196 -0
  377. package/src/node/events.ts +176 -0
  378. package/src/node/fs/index.ts +277 -0
  379. package/src/node/fs/stats.ts +110 -0
  380. package/src/node/fs/sync.ts +335 -0
  381. package/src/node/hashes.ts +171 -0
  382. package/src/node/http/client.ts +173 -0
  383. package/src/node/http/index.ts +122 -0
  384. package/src/node/http/server.ts +308 -0
  385. package/src/node/http/status.ts +13 -0
  386. package/src/node/inspect.ts +166 -0
  387. package/src/node/loader.ts +293 -0
  388. package/src/node/loop.ts +193 -0
  389. package/src/node/misc.ts +250 -0
  390. package/src/node/path.ts +135 -0
  391. package/src/node/process.ts +324 -0
  392. package/src/node/program.ts +170 -0
  393. package/src/node/readline.ts +157 -0
  394. package/src/node/resolve.ts +216 -0
  395. package/src/node/runtime.ts +184 -0
  396. package/src/node/stream/index.ts +196 -0
  397. package/src/node/stream/readable.ts +345 -0
  398. package/src/node/stream/writable.ts +244 -0
  399. package/src/node/urlqs.ts +162 -0
  400. package/src/node/util.ts +145 -0
  401. package/src/node/zlib.ts +145 -0
  402. package/src/packageCdn.ts +11 -1
  403. package/src/projectConfig.ts +34 -7
  404. package/src/shell/builtins.ts +141 -0
  405. package/src/shell/context.ts +89 -0
  406. package/src/shell/devHandoff.ts +49 -0
  407. package/src/shell/exec.ts +268 -0
  408. package/src/shell/expand.ts +145 -0
  409. package/src/shell/fileCommands.ts +206 -0
  410. package/src/shell/lineEditor.ts +233 -0
  411. package/src/shell/npm.ts +229 -0
  412. package/src/shell/parse.ts +295 -0
  413. package/src/shell/program.ts +223 -0
  414. package/src/shell/textCommands.ts +177 -0
  415. package/src/styleModules.ts +15 -4
  416. package/src/svelteCompiler.ts +31 -0
  417. package/src/system/commands.ts +72 -0
  418. package/src/system/index.ts +22 -0
  419. package/src/system/kernel.ts +106 -0
  420. package/src/system/ports.ts +73 -0
  421. package/src/system/process.ts +158 -0
  422. package/src/system/programs.ts +31 -0
  423. package/src/system/terminal.ts +135 -0
  424. package/src/system/types.ts +103 -0
  425. package/src/vfs.ts +92 -28
  426. package/src/vueCompiler.ts +121 -0
@@ -23,19 +23,30 @@ export function rewriteCssUrls(fs: VirtualFileSystem, cssPath: string, css: stri
23
23
  });
24
24
  }
25
25
 
26
+ /**
27
+ * A statement that puts `cssExpression` (JS source evaluating to a string) in a
28
+ * <style> tag keyed by `path`: re-evaluating the module replaces the tag's text
29
+ * instead of adding a second tag. Shared by stylesheets and component styles.
30
+ */
31
+ export function injectStyleStatement(path: string, cssExpression: string): string {
32
+ const id = JSON.stringify(path);
33
+ return [
34
+ `{ let el = document.querySelector('style[data-bfwc=' + JSON.stringify(${id}) + ']');`,
35
+ `if (!el) { el = document.createElement('style'); el.setAttribute('data-bfwc', ${id}); document.head.appendChild(el); }`,
36
+ `el.textContent = ${cssExpression}; }`,
37
+ ].join('\n');
38
+ }
39
+
26
40
  /**
27
41
  * The JS module for an imported stylesheet. `.module.css` exports an identity map
28
42
  * (class name → itself): real scoping needs the build API, so for now CSS Modules
29
43
  * render correctly as long as class names don't collide.
30
44
  */
31
45
  export function cssToModule(path: string, css: string): string {
32
- const id = JSON.stringify(path);
33
46
  const exportsCssModule = /\.module\.css$/i.test(path);
34
47
  return [
35
48
  `const css = ${JSON.stringify(css)};`,
36
- `let el = document.querySelector('style[data-bfwc=' + JSON.stringify(${id}) + ']');`,
37
- `if (!el) { el = document.createElement('style'); el.setAttribute('data-bfwc', ${id}); document.head.appendChild(el); }`,
38
- `el.textContent = css;`,
49
+ injectStyleStatement(path, 'css'),
39
50
  exportsCssModule
40
51
  ? `export default new Proxy({}, { get: (_, key) => typeof key === 'string' ? key : undefined });`
41
52
  : `export default css;`,
@@ -0,0 +1,31 @@
1
+ import type { ComponentCompiler } from './components.js';
2
+
3
+ /**
4
+ * Svelte components through `svelte/compiler`. CSS is always compiled as
5
+ * `external` — Svelte scopes it with hashed class names — and handed back, so the
6
+ * dev server and the build each place it their own way.
7
+ *
8
+ * Svelte 5 strips `<script lang="ts">` itself. Svelte 4 needs a preprocessor for
9
+ * that, which this runtime does not run, so a TS component on Svelte 4 fails with
10
+ * the compiler's own message.
11
+ */
12
+ export interface SvelteCompilerLike {
13
+ VERSION: string;
14
+ compile(
15
+ source: string,
16
+ options: { filename: string; generate: 'client'; css: 'external'; dev: boolean },
17
+ ): { js: { code: string }; css?: { code: string } | null };
18
+ }
19
+
20
+ export function createSvelteCompiler(svelte: SvelteCompilerLike): ComponentCompiler {
21
+ const major = Number.parseInt(svelte.VERSION, 10);
22
+ // Svelte 5 renamed the browser target from 'dom' to 'client'.
23
+ // Typed as Svelte 5's value; Svelte 4 receives its own spelling at runtime.
24
+ const generate = (major >= 5 ? 'client' : 'dom') as 'client';
25
+ return {
26
+ compile(source, { path, dev }) {
27
+ const result = svelte.compile(source, { filename: path, generate, css: 'external', dev });
28
+ return { code: result.js.code, loader: 'js', css: result.css?.code ?? '' };
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Command lookup, shared by the kernel, the browser host and the shell:
3
+ * registered programs first (node, npm, jsh, coreutils), then a path or a
4
+ * `PATH`/`node_modules/.bin` lookup. A found file runs under `node` (a `.bin`
5
+ * shim runs its target directly) or, with a shell shebang, under `jsh`.
6
+ */
7
+ import type { VirtualFileSystem } from '../vfs.js';
8
+ import { dirname, extname, join, normalizePath } from '../paths.js';
9
+ import { readBinLink } from '../installer/bins.js';
10
+ import type { Program } from './types.js';
11
+
12
+ export interface ResolvedCommand {
13
+ /** The registered program that will run (`node`, `jsh`, `ls`…). */
14
+ name: string;
15
+ program: Program;
16
+ args: string[];
17
+ }
18
+
19
+ /** Every `node_modules/.bin` from `cwd` up to the root, nearest first. */
20
+ export function binDirectories(cwd: string): string[] {
21
+ const out: string[] = [];
22
+ for (let dir = normalizePath(cwd); ; dir = dirname(dir)) {
23
+ if (!dir.endsWith('/node_modules')) out.push(join(dir, 'node_modules/.bin'));
24
+ if (dir === '/') return out;
25
+ }
26
+ }
27
+
28
+ /** Find an executable file named `command` on PATH (then node_modules/.bin). */
29
+ export function findExecutable(fs: VirtualFileSystem, command: string, cwd: string, env: Record<string, string | undefined>): string | undefined {
30
+ if (command.includes('/')) {
31
+ const path = command.startsWith('/') ? normalizePath(command) : join(cwd, command);
32
+ return fs.isFile(path) ? path : undefined;
33
+ }
34
+ const dirs = [...(env.PATH ?? '').split(':').filter(Boolean), ...binDirectories(cwd)];
35
+ for (const dir of dirs) {
36
+ const path = join(dir.startsWith('/') ? dir : join(cwd, dir), command);
37
+ if (fs.isFile(path)) return path;
38
+ }
39
+ return undefined;
40
+ }
41
+
42
+ /** How to run a script file: its interpreter and arguments. */
43
+ export function interpreterFor(fs: VirtualFileSystem, path: string): { name: 'node' | 'jsh'; args: string[] } {
44
+ const target = readBinLink(fs, path);
45
+ if (target) return { name: 'node', args: [target] };
46
+ const firstLine = (fs.readText(path) ?? '').split('\n', 1)[0] ?? '';
47
+ if (/^#!.*\b(?:sh|bash|jsh|zsh|dash)\b/.test(firstLine)) return { name: 'jsh', args: [path] };
48
+ if (['.sh', '.bash'].includes(extname(path))) return { name: 'jsh', args: [path] };
49
+ return { name: 'node', args: [path] };
50
+ }
51
+
52
+ export function resolveCommand(
53
+ fs: VirtualFileSystem,
54
+ programs: Readonly<Record<string, Program>>,
55
+ command: string,
56
+ args: string[],
57
+ cwd: string,
58
+ env: Record<string, string | undefined>,
59
+ ): ResolvedCommand | undefined {
60
+ if (!command.includes('/') && Object.prototype.hasOwnProperty.call(programs, command)) {
61
+ return { name: command, program: programs[command]!, args };
62
+ }
63
+ const file = findExecutable(fs, command, cwd, env);
64
+ if (!file) return undefined;
65
+ const { name, args: lead } = interpreterFor(fs, file);
66
+ const program = programs[name];
67
+ return program ? { name, program, args: [...lead, ...args] } : undefined;
68
+ }
69
+
70
+ export function commandNotFound(command: string): Error {
71
+ return Object.assign(new Error(`spawn ${command} ENOENT`), { code: 'ENOENT', errno: -2, syscall: `spawn ${command}`, path: command });
72
+ }
@@ -0,0 +1,22 @@
1
+ export { Kernel, DEFAULT_ENV } from './kernel.js';
2
+ export type { KernelOptions, KernelSpawnOptions } from './kernel.js';
3
+ export type { System, Program, ProgramContext, ProgramIO, TerminalPort, SpawnOptions, ChildHandle, HttpRequestData, HttpResponseData, HttpHandler } from './types.js';
4
+ export { PortRegistry, parsePortPath } from './ports.js';
5
+ export type { PortEvent } from './ports.js';
6
+ export { runProgram, toWebContainerProcess, allocatePid } from './process.js';
7
+ export type { WebContainerProcess, RunProgramOptions } from './process.js';
8
+ export { resolveCommand, findExecutable, binDirectories, interpreterFor, commandNotFound } from './commands.js';
9
+ export type { ResolvedCommand } from './commands.js';
10
+ export { InputQueue, LineDiscipline, toTerminal } from './terminal.js';
11
+ export { defaultPrograms } from './programs.js';
12
+ export { startNode } from '../node/runtime.js';
13
+ export type { NodeInstance, NodeOptions } from '../node/runtime.js';
14
+ export { nodeProgram, parseNodeArgs } from '../node/program.js';
15
+ export { esmToCjs, esmReady } from '../node/esm.js';
16
+ export { NODE_VERSION } from '../node/process.js';
17
+ export { shellProgram } from '../shell/program.js';
18
+ export { Shell } from '../shell/exec.js';
19
+ export type { ShellOptions, ShellIO, JobInput, ShellCommand } from '../shell/exec.js';
20
+ export { parse as parseShell, ShellSyntaxError } from '../shell/parse.js';
21
+ export { LineEditor } from '../shell/lineEditor.js';
22
+ export { matchDevServer } from '../shell/devHandoff.js';
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The in-process kernel: every program runs on the calling thread, sharing one
3
+ * VFS and one port registry. It is what the tests drive, and a host that does
4
+ * not need preemption (a CLI, a server-side sandbox) can use it as is. The
5
+ * browser runtime runs the same programs one-per-Worker instead, so a busy
6
+ * loop cannot freeze the page (see the browser package's node host).
7
+ */
8
+ import { VirtualFileSystem } from '../vfs.js';
9
+ import type { PackageCache } from '../installer/registry.js';
10
+ import type { ChildHandle, HttpRequestData, HttpResponseData, Program, SpawnOptions, System } from './types.js';
11
+ import { PortRegistry } from './ports.js';
12
+ import { commandNotFound, resolveCommand } from './commands.js';
13
+ import { runProgram, toWebContainerProcess, type WebContainerProcess } from './process.js';
14
+
15
+ export interface KernelOptions {
16
+ fs?: VirtualFileSystem;
17
+ /** Programs by command name. The full set comes from `defaultPrograms()`. */
18
+ programs: Record<string, Program>;
19
+ fetch?: System['fetch'];
20
+ /** Base environment for every process. */
21
+ env?: Record<string, string>;
22
+ /** Where a browser reaches a server on `port`. Default `http://localhost:<port>/`. */
23
+ serverUrl?(port: number): string;
24
+ previewUrl?: string;
25
+ registry?: string;
26
+ packageCache?: PackageCache;
27
+ }
28
+
29
+ export interface KernelSpawnOptions extends SpawnOptions {
30
+ /** Kept for `@webcontainer/api` parity; output is always delivered. */
31
+ output?: boolean;
32
+ }
33
+
34
+ type ServerReadyListener = (port: number, url: string) => void;
35
+ type PortListener = (port: number, type: 'open' | 'close', url: string) => void;
36
+
37
+ export const DEFAULT_ENV: Record<string, string> = {
38
+ PATH: '/usr/local/bin:/usr/bin:/bin',
39
+ HOME: '/home/user',
40
+ USER: 'user',
41
+ SHELL: '/bin/jsh',
42
+ TERM: 'xterm-256color',
43
+ COLORTERM: 'truecolor',
44
+ LANG: 'en_US.UTF-8',
45
+ NODE_ENV: 'development',
46
+ };
47
+
48
+ export class Kernel {
49
+ readonly fs: VirtualFileSystem;
50
+ readonly ports = new PortRegistry();
51
+ readonly env: Record<string, string>;
52
+ private readonly serverReady = new Set<ServerReadyListener>();
53
+ private readonly portListeners = new Set<PortListener>();
54
+ private readonly system: System;
55
+
56
+ constructor(private readonly options: KernelOptions) {
57
+ this.fs = options.fs ?? new VirtualFileSystem();
58
+ this.env = { ...DEFAULT_ENV, ...options.env };
59
+ const serverUrl = (port: number) => options.serverUrl?.(port) ?? `http://localhost:${port}/`;
60
+ this.ports.watch(({ type, port }) => {
61
+ const url = serverUrl(port);
62
+ for (const l of this.portListeners) l(port, type, url);
63
+ if (type === 'open') for (const l of this.serverReady) l(port, url);
64
+ });
65
+ this.system = {
66
+ fs: this.fs,
67
+ fetch: options.fetch ?? ((input, init) => globalThis.fetch(input, init)),
68
+ spawn: (command, args, spawnOptions) => this.spawnChild(command, args, spawnOptions),
69
+ listen: (port, handler) => this.ports.listen(port, handler),
70
+ loopback: (port, request) => this.ports.request(port, request),
71
+ serverUrl,
72
+ announceServer: (port, url) => this.serverReady.forEach((l) => l(port, url)),
73
+ previewUrl: options.previewUrl,
74
+ registry: options.registry,
75
+ packageCache: options.packageCache,
76
+ };
77
+ }
78
+
79
+ /** Same shape as `WebContainer#spawn`. */
80
+ spawn(command: string, args: string[] | KernelSpawnOptions = [], options: KernelSpawnOptions = {}): WebContainerProcess {
81
+ if (!Array.isArray(args)) [options, args] = [args, []];
82
+ return toWebContainerProcess(this.spawnChild(command, args, options), !!options.terminal);
83
+ }
84
+
85
+ /** A child as a program's parent sees it — what `System.spawn` returns. */
86
+ spawnChild(command: string, args: string[] = [], options: SpawnOptions = {}): ChildHandle {
87
+ const cwd = options.cwd ?? '/';
88
+ const env = { ...this.env, ...options.env };
89
+ const resolved = resolveCommand(this.fs, this.options.programs, command, args, cwd, env);
90
+ if (!resolved) throw commandNotFound(command);
91
+ return runProgram({ program: resolved.program, args: resolved.args, system: this.system, cwd, env, terminal: options.terminal });
92
+ }
93
+
94
+ /** Send an HTTP request to a virtual server (what the preview service worker does in the browser). */
95
+ request(port: number, request: Partial<HttpRequestData> & { url: string }): Promise<HttpResponseData | undefined> {
96
+ return this.ports.request(port, { method: 'GET', headers: {}, body: null, ...request });
97
+ }
98
+
99
+ on(event: 'server-ready', listener: ServerReadyListener): () => void;
100
+ on(event: 'port', listener: PortListener): () => void;
101
+ on(event: 'server-ready' | 'port', listener: ServerReadyListener | PortListener): () => void {
102
+ const set = (event === 'server-ready' ? this.serverReady : this.portListeners) as Set<typeof listener>;
103
+ set.add(listener);
104
+ return () => set.delete(listener);
105
+ }
106
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The virtual network: which handler serves which port. One registry per
3
+ * runtime (kernel or browser host), shared by every process in it, so a server
4
+ * in one process is reachable from another (`fetch('http://localhost:3000')`)
5
+ * and from the preview service worker (`/__bfwc/<id>/__port/3000/`).
6
+ */
7
+ import type { HttpHandler, HttpRequestData, HttpResponseData } from './types.js';
8
+
9
+ export interface PortEvent {
10
+ type: 'open' | 'close';
11
+ port: number;
12
+ }
13
+
14
+ const EPHEMERAL_START = 49152;
15
+
16
+ export class PortRegistry {
17
+ private readonly handlers = new Map<number, HttpHandler>();
18
+ private readonly listeners = new Set<(event: PortEvent) => void>();
19
+ private nextEphemeral = EPHEMERAL_START;
20
+
21
+ listen(port: number, handler: HttpHandler): { port: number; close(): void } {
22
+ let actual = port;
23
+ if (!actual) {
24
+ while (this.handlers.has(this.nextEphemeral)) this.nextEphemeral++;
25
+ actual = this.nextEphemeral++;
26
+ }
27
+ if (this.handlers.has(actual)) {
28
+ throw Object.assign(new Error(`listen EADDRINUSE: address already in use :::${actual}`), { code: 'EADDRINUSE', errno: -98, syscall: 'listen', address: '::', port: actual });
29
+ }
30
+ this.handlers.set(actual, handler);
31
+ this.emit({ type: 'open', port: actual });
32
+ let open = true;
33
+ return {
34
+ port: actual,
35
+ close: () => {
36
+ if (!open || this.handlers.get(actual) !== handler) return;
37
+ open = false;
38
+ this.handlers.delete(actual);
39
+ this.emit({ type: 'close', port: actual });
40
+ },
41
+ };
42
+ }
43
+
44
+ has(port: number): boolean {
45
+ return this.handlers.has(port);
46
+ }
47
+
48
+ ports(): number[] {
49
+ return [...this.handlers.keys()].sort((a, b) => a - b);
50
+ }
51
+
52
+ /** Dispatch to the server on `port`; undefined when nothing listens there. */
53
+ async request(port: number, request: HttpRequestData): Promise<HttpResponseData | undefined> {
54
+ const handler = this.handlers.get(port);
55
+ return handler ? handler(request) : undefined;
56
+ }
57
+
58
+ watch(listener: (event: PortEvent) => void): () => void {
59
+ this.listeners.add(listener);
60
+ return () => this.listeners.delete(listener);
61
+ }
62
+
63
+ private emit(event: PortEvent): void {
64
+ for (const listener of this.listeners) listener(event);
65
+ }
66
+ }
67
+
68
+ /** `/__port/3000/api?x` → port 3000, path `/api`. Undefined for any other path. */
69
+ export function parsePortPath(path: string): { port: number; path: string } | undefined {
70
+ const m = /^\/__port\/(\d{1,5})(\/.*)?$/.exec(path);
71
+ if (!m) return undefined;
72
+ return { port: Number(m[1]), path: m[2] ?? '/' };
73
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Running one program in-process: its stdin queue, TTY line discipline,
3
+ * output listeners and kill signal, packaged as a {@link ChildHandle}. The core
4
+ * kernel uses it for every process; the browser worker uses it for the one
5
+ * program it hosts. Also the WebContainer-shaped wrapper (`output`/`input`
6
+ * streams) that public `spawn` returns.
7
+ */
8
+ import type { ChildHandle, Program, ProgramIO, SpawnOptions, System } from './types.js';
9
+ import { InputQueue, LineDiscipline, toTerminal } from './terminal.js';
10
+
11
+ let nextPid = 1000;
12
+
13
+ export function allocatePid(): number {
14
+ return nextPid++;
15
+ }
16
+
17
+ export interface RunProgramOptions extends SpawnOptions {
18
+ program: Program;
19
+ args: string[];
20
+ system: System;
21
+ cwd: string;
22
+ env: Record<string, string>;
23
+ pid?: number;
24
+ }
25
+
26
+ /** Decode byte chunks without splitting multi-byte characters across writes. */
27
+ function textSink(emit: (text: string) => void): (chunk: string | Uint8Array) => void {
28
+ const decoder = new TextDecoder();
29
+ return (chunk) => {
30
+ const text = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
31
+ if (text) emit(text);
32
+ };
33
+ }
34
+
35
+ export function runProgram(options: RunProgramOptions): ChildHandle {
36
+ const pid = options.pid ?? allocatePid();
37
+ const stdin = new InputQueue();
38
+ const stdoutListeners = new Set<(chunk: string) => void>();
39
+ const stderrListeners = new Set<(chunk: string) => void>();
40
+ const resizeListeners = new Set<(cols: number, rows: number) => void>();
41
+ const controller = new AbortController();
42
+ const emitOut = (text: string) => stdoutListeners.forEach((l) => l(text));
43
+ const emitErr = (text: string) => stderrListeners.forEach((l) => l(text));
44
+ let settled = false;
45
+ let forceExit: (code: number) => void = () => undefined;
46
+
47
+ const kill = (signal = 'SIGTERM') => {
48
+ if (settled) return;
49
+ if (!controller.signal.aborted) controller.abort(signal);
50
+ else if (signal === 'SIGKILL') forceExit(137);
51
+ };
52
+
53
+ const size = options.terminal ? { ...options.terminal } : undefined;
54
+ const discipline = size
55
+ ? new LineDiscipline({ echo: emitOut, deliver: (chunk) => stdin.push(chunk), signal: (name) => kill(name) })
56
+ : undefined;
57
+
58
+ const io: ProgramIO = {
59
+ stdout: textSink(emitOut),
60
+ stderr: textSink(emitErr),
61
+ onStdin: (listener) => stdin.subscribe(listener),
62
+ terminal: size && discipline
63
+ ? {
64
+ get cols() {
65
+ return size.cols;
66
+ },
67
+ get rows() {
68
+ return size.rows;
69
+ },
70
+ onResize(listener) {
71
+ resizeListeners.add(listener);
72
+ return () => resizeListeners.delete(listener);
73
+ },
74
+ setRawMode(raw) {
75
+ discipline.raw = raw;
76
+ },
77
+ }
78
+ : undefined,
79
+ };
80
+
81
+ const exit = new Promise<number>((resolve) => {
82
+ forceExit = (code) => {
83
+ settled = true;
84
+ resolve(code);
85
+ };
86
+ queueMicrotask(async () => {
87
+ let code: number;
88
+ try {
89
+ code = await options.program({ args: options.args, cwd: options.cwd, env: options.env, io, system: options.system, signal: controller.signal });
90
+ } catch (error) {
91
+ io.stderr(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
92
+ code = 1;
93
+ }
94
+ settled = true;
95
+ resolve(code);
96
+ });
97
+ });
98
+
99
+ return {
100
+ pid,
101
+ onStdout(listener) {
102
+ stdoutListeners.add(listener);
103
+ return () => stdoutListeners.delete(listener);
104
+ },
105
+ onStderr(listener) {
106
+ stderrListeners.add(listener);
107
+ return () => stderrListeners.delete(listener);
108
+ },
109
+ write(data) {
110
+ if (discipline) discipline.input(data);
111
+ else stdin.push(data);
112
+ },
113
+ closeStdin: () => stdin.push(null),
114
+ kill,
115
+ resize(cols, rows) {
116
+ if (!size) return;
117
+ size.cols = cols;
118
+ size.rows = rows;
119
+ resizeListeners.forEach((l) => l(cols, rows));
120
+ },
121
+ exit,
122
+ };
123
+ }
124
+
125
+ /** The public process shape, deliberately the same as `@webcontainer/api`'s `WebContainerProcess`. */
126
+ export interface WebContainerProcess {
127
+ /** stdout and stderr, interleaved as written (`\r\n` line endings when attached to a terminal). */
128
+ readonly output: ReadableStream<string>;
129
+ readonly input: WritableStream<string>;
130
+ readonly exit: Promise<number>;
131
+ kill(signal?: string): void;
132
+ resize(dimensions: { cols: number; rows: number } | number, rows?: number): void;
133
+ }
134
+
135
+ export function toWebContainerProcess(child: ChildHandle, terminal: boolean): WebContainerProcess {
136
+ const output = new ReadableStream<string>({
137
+ start(controller) {
138
+ const push = (chunk: string) => controller.enqueue(terminal ? toTerminal(chunk) : chunk);
139
+ child.onStdout(push);
140
+ child.onStderr(push);
141
+ void child.exit.then(() => controller.close());
142
+ },
143
+ });
144
+ const input = new WritableStream<string>({
145
+ write: (chunk) => child.write(chunk),
146
+ close: () => child.closeStdin(),
147
+ });
148
+ return {
149
+ output,
150
+ input,
151
+ exit: child.exit,
152
+ kill: (signal) => child.kill(signal),
153
+ resize(dimensions, rows) {
154
+ const size = typeof dimensions === 'number' ? { cols: dimensions, rows: rows ?? 24 } : dimensions;
155
+ child.resize?.(size.cols, size.rows);
156
+ },
157
+ };
158
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The command table: every program a process can be. Hosts start from
3
+ * `defaultPrograms()` and may add or replace entries (a custom `git`, say).
4
+ *
5
+ * `npm`/`npx` live in the shell (they run scripts through it), so spawning them
6
+ * directly runs them through a one-shot shell; coreutils run standalone.
7
+ */
8
+ import type { Program } from './types.js';
9
+ import { nodeProgram } from '../node/program.js';
10
+ import { shellProgram } from '../shell/program.js';
11
+ import { COREUTILS } from '../shell/builtins.js';
12
+ import { asProgram } from '../shell/context.js';
13
+
14
+ const quote = (arg: string) => (/^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`);
15
+
16
+ function viaShell(command: string): Program {
17
+ return (ctx) => shellProgram({ ...ctx, args: ['-c', [command, ...ctx.args].map(quote).join(' ')] });
18
+ }
19
+
20
+ export function defaultPrograms(): Record<string, Program> {
21
+ const programs: Record<string, Program> = {
22
+ node: nodeProgram,
23
+ jsh: shellProgram,
24
+ sh: shellProgram,
25
+ bash: shellProgram,
26
+ npm: viaShell('npm'),
27
+ npx: viaShell('npx'),
28
+ };
29
+ for (const [name, command] of Object.entries(COREUTILS)) programs[name] = asProgram(command, name);
30
+ return programs;
31
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * What a kernel's TTY layer does between xterm.js and a program:
3
+ *
4
+ * - InputQueue: stdin as a buffered stream of string chunks (data that arrives
5
+ * before the program subscribes is kept, not dropped).
6
+ * - LineDiscipline: "cooked" mode — echo, backspace, Ctrl-U, Enter delivers the
7
+ * line, Ctrl-C raises SIGINT, Ctrl-D is end-of-file. A program that calls
8
+ * `setRawMode(true)` (the shell, prompts libraries) gets keystrokes untouched.
9
+ * - toTerminal: `\n` → `\r\n` on output (the TTY's ONLCR), since xterm.js
10
+ * only moves to column 0 on `\r`.
11
+ */
12
+
13
+ export class InputQueue {
14
+ private readonly buffered: Array<string | null> = [];
15
+ private readonly listeners = new Set<(chunk: string | null) => void>();
16
+ private ended = false;
17
+
18
+ private draining = false;
19
+
20
+ push(chunk: string | null): void {
21
+ if (this.ended) return;
22
+ if (chunk === null) this.ended = true;
23
+ this.buffered.push(chunk);
24
+ this.drain();
25
+ }
26
+
27
+ subscribe(listener: (chunk: string | null) => void): () => void {
28
+ this.listeners.add(listener);
29
+ this.drain();
30
+ return () => this.listeners.delete(listener);
31
+ }
32
+
33
+ /**
34
+ * Deliver on a microtask: a subscriber has always stored its unsubscribe
35
+ * function by then, and chunks stay buffered until someone is actually
36
+ * listening — one that leaves early does not take data with it.
37
+ */
38
+ private drain(): void {
39
+ if (this.draining || !this.listeners.size || !this.buffered.length) return;
40
+ this.draining = true;
41
+ queueMicrotask(() => {
42
+ this.draining = false;
43
+ while (this.buffered.length && this.listeners.size) {
44
+ const chunk = this.buffered.shift()!;
45
+ for (const listener of [...this.listeners]) listener(chunk);
46
+ }
47
+ });
48
+ }
49
+
50
+ /** Everything until end-of-file. */
51
+ readAll(): Promise<string> {
52
+ return new Promise((resolve) => {
53
+ let text = '';
54
+ const stop = this.subscribe((chunk) => {
55
+ if (chunk === null) {
56
+ stop();
57
+ resolve(text);
58
+ } else text += chunk;
59
+ });
60
+ });
61
+ }
62
+
63
+ get isEnded(): boolean {
64
+ return this.ended;
65
+ }
66
+ }
67
+
68
+ export function toTerminal(text: string): string {
69
+ return text.replace(/\r?\n/g, '\r\n');
70
+ }
71
+
72
+ export interface LineDisciplineOptions {
73
+ /** Echo to the terminal. */
74
+ echo(text: string): void;
75
+ /** A completed line (with its `\n`), or `null` for end-of-file. */
76
+ deliver(chunk: string | null): void;
77
+ /** Ctrl-C / Ctrl-\. */
78
+ signal(name: 'SIGINT' | 'SIGQUIT'): void;
79
+ }
80
+
81
+ export class LineDiscipline {
82
+ raw = false;
83
+ private line = '';
84
+
85
+ constructor(private readonly options: LineDisciplineOptions) {}
86
+
87
+ input(data: string): void {
88
+ if (this.raw) {
89
+ this.options.deliver(data);
90
+ return;
91
+ }
92
+ // Arrow keys and other escape sequences have no meaning in cooked mode.
93
+ const text = data.replace(/\x1b\[[0-9;]*[A-Za-z~]|\x1bO[A-Za-z]/g, '');
94
+ for (const ch of text) {
95
+ switch (ch) {
96
+ case '\r':
97
+ case '\n':
98
+ this.options.echo('\r\n');
99
+ this.options.deliver(this.line + '\n');
100
+ this.line = '';
101
+ break;
102
+ case '\x7f':
103
+ case '\b':
104
+ if (this.line) {
105
+ this.line = [...this.line].slice(0, -1).join('');
106
+ this.options.echo('\b \b');
107
+ }
108
+ break;
109
+ case '\x03':
110
+ this.options.echo('^C\r\n');
111
+ this.line = '';
112
+ this.options.signal('SIGINT');
113
+ break;
114
+ case '\x1c':
115
+ this.options.signal('SIGQUIT');
116
+ break;
117
+ case '\x04':
118
+ if (this.line) {
119
+ this.options.deliver(this.line);
120
+ this.line = '';
121
+ } else this.options.deliver(null);
122
+ break;
123
+ case '\x15':
124
+ this.options.echo('\b \b'.repeat([...this.line].length));
125
+ this.line = '';
126
+ break;
127
+ default:
128
+ if (ch >= ' ' || ch === '\t') {
129
+ this.line += ch;
130
+ this.options.echo(ch);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }