everything-dev 1.53.2 → 2.0.0-rc.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 (713) hide show
  1. package/README.md +2 -11
  2. package/dist/_virtual/_rolldown/runtime.cjs +17 -2
  3. package/dist/_virtual/_rolldown/runtime.mjs +36 -0
  4. package/dist/api/auth-middleware.cjs +161 -0
  5. package/dist/api/auth-middleware.cjs.map +1 -0
  6. package/dist/api/auth-middleware.d.cts +69 -0
  7. package/dist/api/auth-middleware.d.mts +69 -0
  8. package/dist/api/auth-middleware.mjs +160 -0
  9. package/dist/api/auth-middleware.mjs.map +1 -0
  10. package/dist/api-contract.cjs +250 -62
  11. package/dist/api-contract.cjs.map +1 -1
  12. package/dist/api-contract.mjs +246 -65
  13. package/dist/api-contract.mjs.map +1 -1
  14. package/dist/app.cjs +99 -26
  15. package/dist/app.cjs.map +1 -1
  16. package/dist/app.d.cts +3 -0
  17. package/dist/app.d.mts +3 -0
  18. package/dist/app.mjs +100 -27
  19. package/dist/app.mjs.map +1 -1
  20. package/dist/auth-login.cjs +163 -0
  21. package/dist/auth-login.cjs.map +1 -0
  22. package/dist/auth-login.mjs +162 -0
  23. package/dist/auth-login.mjs.map +1 -0
  24. package/dist/auth-session.cjs +64 -0
  25. package/dist/auth-session.cjs.map +1 -0
  26. package/dist/auth-session.mjs +58 -0
  27. package/dist/auth-session.mjs.map +1 -0
  28. package/dist/auth-types-gen.cjs +0 -1
  29. package/dist/auth-types-gen.cjs.map +1 -1
  30. package/dist/build.cjs +37 -173
  31. package/dist/build.cjs.map +1 -1
  32. package/dist/build.mjs +37 -171
  33. package/dist/build.mjs.map +1 -1
  34. package/dist/bundle-cache.cjs +74 -0
  35. package/dist/bundle-cache.cjs.map +1 -0
  36. package/dist/bundle-cache.d.cts +27 -0
  37. package/dist/bundle-cache.d.mts +27 -0
  38. package/dist/bundle-cache.mjs +68 -0
  39. package/dist/bundle-cache.mjs.map +1 -0
  40. package/dist/bundle-fs-resolve.cjs +209 -0
  41. package/dist/bundle-fs-resolve.cjs.map +1 -0
  42. package/dist/bundle-fs-resolve.d.cts +76 -0
  43. package/dist/bundle-fs-resolve.d.mts +76 -0
  44. package/dist/bundle-fs-resolve.mjs +202 -0
  45. package/dist/bundle-fs-resolve.mjs.map +1 -0
  46. package/dist/cdn-deploy.cjs +126 -0
  47. package/dist/cdn-deploy.cjs.map +1 -0
  48. package/dist/cdn-deploy.mjs +124 -0
  49. package/dist/cdn-deploy.mjs.map +1 -0
  50. package/dist/cli/db-doctor.cjs +19 -19
  51. package/dist/cli/db-doctor.cjs.map +1 -1
  52. package/dist/cli/db-doctor.mjs +18 -18
  53. package/dist/cli/db-doctor.mjs.map +1 -1
  54. package/dist/cli/db-repair.cjs +9 -32
  55. package/dist/cli/db-repair.cjs.map +1 -1
  56. package/dist/cli/db-repair.mjs +9 -32
  57. package/dist/cli/db-repair.mjs.map +1 -1
  58. package/dist/cli/framework-version.cjs +7 -3
  59. package/dist/cli/framework-version.cjs.map +1 -1
  60. package/dist/cli/framework-version.mjs +7 -3
  61. package/dist/cli/framework-version.mjs.map +1 -1
  62. package/dist/cli/infra.cjs +159 -251
  63. package/dist/cli/infra.cjs.map +1 -1
  64. package/dist/cli/infra.mjs +154 -242
  65. package/dist/cli/infra.mjs.map +1 -1
  66. package/dist/cli/init.cjs +624 -196
  67. package/dist/cli/init.cjs.map +1 -1
  68. package/dist/cli/init.d.cts +61 -8
  69. package/dist/cli/init.d.mts +61 -8
  70. package/dist/cli/init.mjs +613 -194
  71. package/dist/cli/init.mjs.map +1 -1
  72. package/dist/cli/parse.cjs +8 -7
  73. package/dist/cli/parse.cjs.map +1 -1
  74. package/dist/cli/parse.mjs +9 -7
  75. package/dist/cli/parse.mjs.map +1 -1
  76. package/dist/cli/prompts.cjs +14 -1
  77. package/dist/cli/prompts.cjs.map +1 -1
  78. package/dist/cli/prompts.mjs +14 -1
  79. package/dist/cli/prompts.mjs.map +1 -1
  80. package/dist/cli/prune.cjs +220 -0
  81. package/dist/cli/prune.cjs.map +1 -0
  82. package/dist/cli/prune.mjs +220 -0
  83. package/dist/cli/prune.mjs.map +1 -0
  84. package/dist/cli/snapshot.cjs +2 -1
  85. package/dist/cli/snapshot.cjs.map +1 -1
  86. package/dist/cli/snapshot.mjs +2 -1
  87. package/dist/cli/snapshot.mjs.map +1 -1
  88. package/dist/cli/status.cjs +5 -4
  89. package/dist/cli/status.cjs.map +1 -1
  90. package/dist/cli/status.mjs +6 -5
  91. package/dist/cli/status.mjs.map +1 -1
  92. package/dist/cli/sync.cjs +156 -58
  93. package/dist/cli/sync.cjs.map +1 -1
  94. package/dist/cli/sync.mjs +158 -63
  95. package/dist/cli/sync.mjs.map +1 -1
  96. package/dist/cli/timing.cjs +0 -1
  97. package/dist/cli/timing.cjs.map +1 -1
  98. package/dist/cli/upgrade.cjs +95 -69
  99. package/dist/cli/upgrade.cjs.map +1 -1
  100. package/dist/cli/upgrade.mjs +97 -72
  101. package/dist/cli/upgrade.mjs.map +1 -1
  102. package/dist/cli/utils/helpers.cjs.map +1 -1
  103. package/dist/cli.cjs +320 -72
  104. package/dist/cli.cjs.map +1 -1
  105. package/dist/cli.mjs +316 -68
  106. package/dist/cli.mjs.map +1 -1
  107. package/dist/code-artifacts.cjs +76 -8
  108. package/dist/code-artifacts.cjs.map +1 -1
  109. package/dist/code-artifacts.mjs +76 -10
  110. package/dist/code-artifacts.mjs.map +1 -1
  111. package/dist/commands/build.cjs +65 -0
  112. package/dist/commands/build.cjs.map +1 -0
  113. package/dist/commands/build.mjs +65 -0
  114. package/dist/commands/build.mjs.map +1 -0
  115. package/dist/commands/config.cjs +77 -0
  116. package/dist/commands/config.cjs.map +1 -0
  117. package/dist/commands/config.d.cts +8 -0
  118. package/dist/commands/config.d.mts +8 -0
  119. package/dist/commands/config.mjs +74 -0
  120. package/dist/commands/config.mjs.map +1 -0
  121. package/dist/commands/db.cjs +107 -0
  122. package/dist/commands/db.cjs.map +1 -0
  123. package/dist/commands/db.mjs +107 -0
  124. package/dist/commands/db.mjs.map +1 -0
  125. package/dist/commands/deploy.cjs +336 -0
  126. package/dist/commands/deploy.cjs.map +1 -0
  127. package/dist/commands/deploy.mjs +334 -0
  128. package/dist/commands/deploy.mjs.map +1 -0
  129. package/dist/commands/dev.cjs +75 -0
  130. package/dist/commands/dev.cjs.map +1 -0
  131. package/dist/commands/dev.mjs +73 -0
  132. package/dist/commands/dev.mjs.map +1 -0
  133. package/dist/commands/init.cjs +234 -0
  134. package/dist/commands/init.cjs.map +1 -0
  135. package/dist/commands/init.mjs +231 -0
  136. package/dist/commands/init.mjs.map +1 -0
  137. package/dist/commands/keys.cjs +255 -0
  138. package/dist/commands/keys.cjs.map +1 -0
  139. package/dist/commands/keys.mjs +253 -0
  140. package/dist/commands/keys.mjs.map +1 -0
  141. package/dist/commands/ops.cjs +199 -0
  142. package/dist/commands/ops.cjs.map +1 -0
  143. package/dist/commands/ops.mjs +197 -0
  144. package/dist/commands/ops.mjs.map +1 -0
  145. package/dist/commands/plugins.cjs +211 -0
  146. package/dist/commands/plugins.cjs.map +1 -0
  147. package/dist/commands/plugins.mjs +211 -0
  148. package/dist/commands/plugins.mjs.map +1 -0
  149. package/dist/commands/shared.cjs +8 -0
  150. package/dist/commands/shared.cjs.map +1 -0
  151. package/dist/commands/shared.d.cts +6 -0
  152. package/dist/commands/shared.d.mts +6 -0
  153. package/dist/commands/shared.mjs +8 -0
  154. package/dist/commands/shared.mjs.map +1 -0
  155. package/dist/commands/upgrade.cjs +325 -0
  156. package/dist/commands/upgrade.cjs.map +1 -0
  157. package/dist/commands/upgrade.mjs +323 -0
  158. package/dist/commands/upgrade.mjs.map +1 -0
  159. package/dist/components/dev-render.cjs +340 -0
  160. package/dist/components/dev-render.cjs.map +1 -0
  161. package/dist/components/dev-render.mjs +332 -0
  162. package/dist/components/dev-render.mjs.map +1 -0
  163. package/dist/config.cjs +460 -221
  164. package/dist/config.cjs.map +1 -1
  165. package/dist/config.d.cts +491 -48
  166. package/dist/config.d.mts +491 -48
  167. package/dist/config.mjs +439 -212
  168. package/dist/config.mjs.map +1 -1
  169. package/dist/contract.cjs +327 -50
  170. package/dist/contract.cjs.map +1 -1
  171. package/dist/contract.d.cts +1951 -1062
  172. package/dist/contract.d.mts +1951 -1062
  173. package/dist/contract.meta.cjs +107 -12
  174. package/dist/contract.meta.cjs.map +1 -1
  175. package/dist/contract.meta.d.cts +166 -8
  176. package/dist/contract.meta.d.mts +166 -8
  177. package/dist/contract.meta.mjs +107 -11
  178. package/dist/contract.meta.mjs.map +1 -1
  179. package/dist/contract.mjs +580 -327
  180. package/dist/contract.mjs.map +1 -1
  181. package/dist/dag.cjs +5 -3
  182. package/dist/dag.cjs.map +1 -1
  183. package/dist/dag.mjs +6 -3
  184. package/dist/dag.mjs.map +1 -1
  185. package/dist/db/binding.cjs +80 -0
  186. package/dist/db/binding.cjs.map +1 -0
  187. package/dist/db/binding.d.cts +39 -0
  188. package/dist/db/binding.d.mts +39 -0
  189. package/dist/db/binding.mjs +78 -0
  190. package/dist/db/binding.mjs.map +1 -0
  191. package/dist/db/bindings.cjs +78 -0
  192. package/dist/db/bindings.cjs.map +1 -0
  193. package/dist/db/bindings.d.cts +34 -0
  194. package/dist/db/bindings.d.mts +34 -0
  195. package/dist/db/bindings.mjs +76 -0
  196. package/dist/db/bindings.mjs.map +1 -0
  197. package/dist/db/core.cjs +204 -0
  198. package/dist/db/core.cjs.map +1 -0
  199. package/dist/db/core.d.cts +69 -0
  200. package/dist/db/core.d.mts +69 -0
  201. package/dist/db/core.mjs +192 -0
  202. package/dist/db/core.mjs.map +1 -0
  203. package/dist/db/driver.cjs +167 -0
  204. package/dist/db/driver.cjs.map +1 -0
  205. package/dist/db/driver.d.cts +50 -0
  206. package/dist/db/driver.d.mts +50 -0
  207. package/dist/db/driver.mjs +165 -0
  208. package/dist/db/driver.mjs.map +1 -0
  209. package/dist/db/drizzle-kit.cjs +177 -0
  210. package/dist/db/drizzle-kit.cjs.map +1 -0
  211. package/dist/db/drizzle-kit.d.cts +25 -0
  212. package/dist/db/drizzle-kit.d.mts +25 -0
  213. package/dist/db/drizzle-kit.mjs +175 -0
  214. package/dist/db/drizzle-kit.mjs.map +1 -0
  215. package/dist/db/errors.cjs +27 -0
  216. package/dist/db/errors.cjs.map +1 -0
  217. package/dist/db/errors.d.cts +16 -0
  218. package/dist/db/errors.d.mts +16 -0
  219. package/dist/db/errors.mjs +26 -0
  220. package/dist/db/errors.mjs.map +1 -0
  221. package/dist/db/identity.cjs +55 -0
  222. package/dist/db/identity.cjs.map +1 -0
  223. package/dist/db/identity.d.cts +40 -0
  224. package/dist/db/identity.d.mts +40 -0
  225. package/dist/db/identity.mjs +53 -0
  226. package/dist/db/identity.mjs.map +1 -0
  227. package/dist/db/index.cjs +43 -0
  228. package/dist/db/index.d.cts +9 -0
  229. package/dist/db/index.d.mts +9 -0
  230. package/dist/db/index.mjs +10 -0
  231. package/dist/db/run-migrations.cjs +343 -0
  232. package/dist/db/run-migrations.cjs.map +1 -0
  233. package/dist/db/run-migrations.d.cts +87 -0
  234. package/dist/db/run-migrations.d.mts +87 -0
  235. package/dist/db/run-migrations.mjs +341 -0
  236. package/dist/db/run-migrations.mjs.map +1 -0
  237. package/dist/delegate-signer.cjs +85 -0
  238. package/dist/delegate-signer.cjs.map +1 -0
  239. package/dist/delegate-signer.mjs +84 -0
  240. package/dist/delegate-signer.mjs.map +1 -0
  241. package/dist/deploy-diff.cjs +119 -0
  242. package/dist/deploy-diff.cjs.map +1 -0
  243. package/dist/deploy-diff.mjs +109 -0
  244. package/dist/deploy-diff.mjs.map +1 -0
  245. package/dist/descriptor/constructors.cjs +49 -0
  246. package/dist/descriptor/constructors.cjs.map +1 -0
  247. package/dist/descriptor/constructors.d.cts +27 -0
  248. package/dist/descriptor/constructors.d.mts +27 -0
  249. package/dist/descriptor/constructors.mjs +45 -0
  250. package/dist/descriptor/constructors.mjs.map +1 -0
  251. package/dist/descriptor/index.cjs +24 -0
  252. package/dist/descriptor/index.d.cts +5 -0
  253. package/dist/descriptor/index.d.mts +5 -0
  254. package/dist/descriptor/index.mjs +6 -0
  255. package/dist/descriptor/resolve.cjs +258 -0
  256. package/dist/descriptor/resolve.cjs.map +1 -0
  257. package/dist/descriptor/resolve.d.cts +50 -0
  258. package/dist/descriptor/resolve.d.mts +50 -0
  259. package/dist/descriptor/resolve.mjs +252 -0
  260. package/dist/descriptor/resolve.mjs.map +1 -0
  261. package/dist/descriptor/schema.cjs +109 -0
  262. package/dist/descriptor/schema.cjs.map +1 -0
  263. package/dist/descriptor/schema.d.cts +217 -0
  264. package/dist/descriptor/schema.d.mts +217 -0
  265. package/dist/descriptor/schema.mjs +102 -0
  266. package/dist/descriptor/schema.mjs.map +1 -0
  267. package/dist/descriptor/serialize.cjs +45 -0
  268. package/dist/descriptor/serialize.cjs.map +1 -0
  269. package/dist/descriptor/serialize.d.cts +7 -0
  270. package/dist/descriptor/serialize.d.mts +7 -0
  271. package/dist/descriptor/serialize.mjs +45 -0
  272. package/dist/descriptor/serialize.mjs.map +1 -0
  273. package/dist/dev-log-pipeline.cjs +273 -0
  274. package/dist/dev-log-pipeline.cjs.map +1 -0
  275. package/dist/dev-log-pipeline.mjs +267 -0
  276. package/dist/dev-log-pipeline.mjs.map +1 -0
  277. package/dist/dev-logs.cjs +39 -8
  278. package/dist/dev-logs.cjs.map +1 -1
  279. package/dist/dev-logs.mjs +37 -9
  280. package/dist/dev-logs.mjs.map +1 -1
  281. package/dist/dev-program.cjs +405 -0
  282. package/dist/dev-program.cjs.map +1 -0
  283. package/dist/dev-program.d.cts +10 -0
  284. package/dist/dev-program.d.mts +10 -0
  285. package/dist/dev-program.mjs +395 -0
  286. package/dist/dev-program.mjs.map +1 -0
  287. package/dist/dev-session-data.d.cts +27 -0
  288. package/dist/dev-session-data.d.mts +27 -0
  289. package/dist/dev-session.cjs +178 -105
  290. package/dist/dev-session.cjs.map +1 -1
  291. package/dist/dev-session.mjs +180 -105
  292. package/dist/dev-session.mjs.map +1 -1
  293. package/dist/env/process-env.cjs +16 -0
  294. package/dist/env/process-env.cjs.map +1 -0
  295. package/dist/env/process-env.mjs +13 -0
  296. package/dist/env/process-env.mjs.map +1 -0
  297. package/dist/env/project-env.cjs +144 -0
  298. package/dist/env/project-env.cjs.map +1 -0
  299. package/dist/env/project-env.d.cts +1 -0
  300. package/dist/env/project-env.d.mts +1 -0
  301. package/dist/env/project-env.mjs +132 -0
  302. package/dist/env/project-env.mjs.map +1 -0
  303. package/dist/fastkv.cjs +59 -12
  304. package/dist/fastkv.cjs.map +1 -1
  305. package/dist/fastkv.d.cts +42 -6
  306. package/dist/fastkv.d.mts +42 -6
  307. package/dist/fastkv.mjs +58 -13
  308. package/dist/fastkv.mjs.map +1 -1
  309. package/dist/fingerprint.cjs +57 -0
  310. package/dist/fingerprint.cjs.map +1 -0
  311. package/dist/fingerprint.d.cts +14 -0
  312. package/dist/fingerprint.d.mts +14 -0
  313. package/dist/fingerprint.mjs +55 -0
  314. package/dist/fingerprint.mjs.map +1 -0
  315. package/dist/http-client.cjs +17 -17
  316. package/dist/http-client.cjs.map +1 -1
  317. package/dist/http-client.mjs +17 -17
  318. package/dist/http-client.mjs.map +1 -1
  319. package/dist/image-deploy.cjs +186 -0
  320. package/dist/image-deploy.cjs.map +1 -0
  321. package/dist/image-deploy.mjs +182 -0
  322. package/dist/image-deploy.mjs.map +1 -0
  323. package/dist/index.cjs +66 -18
  324. package/dist/index.d.cts +8 -7
  325. package/dist/index.d.mts +8 -7
  326. package/dist/index.mjs +6 -5
  327. package/dist/infra/docker.cjs +77 -0
  328. package/dist/infra/docker.cjs.map +1 -0
  329. package/dist/infra/docker.d.cts +10 -0
  330. package/dist/infra/docker.d.mts +10 -0
  331. package/dist/infra/docker.mjs +69 -0
  332. package/dist/infra/docker.mjs.map +1 -0
  333. package/dist/infra/materializer.cjs +74 -0
  334. package/dist/infra/materializer.cjs.map +1 -0
  335. package/dist/infra/materializer.mjs +70 -0
  336. package/dist/infra/materializer.mjs.map +1 -0
  337. package/dist/infra/planner.cjs +135 -103
  338. package/dist/infra/planner.cjs.map +1 -1
  339. package/dist/infra/planner.mjs +137 -104
  340. package/dist/infra/planner.mjs.map +1 -1
  341. package/dist/infra/port-ownership.cjs +57 -0
  342. package/dist/infra/port-ownership.cjs.map +1 -0
  343. package/dist/infra/port-ownership.mjs +57 -0
  344. package/dist/infra/port-ownership.mjs.map +1 -0
  345. package/dist/infra/preflight.cjs +45 -33
  346. package/dist/infra/preflight.cjs.map +1 -1
  347. package/dist/infra/preflight.d.cts +1 -0
  348. package/dist/infra/preflight.d.mts +1 -0
  349. package/dist/infra/preflight.mjs +46 -34
  350. package/dist/infra/preflight.mjs.map +1 -1
  351. package/dist/infra/types.cjs.map +1 -1
  352. package/dist/infra/types.d.cts +2 -0
  353. package/dist/infra/types.d.mts +2 -0
  354. package/dist/infra/types.mjs.map +1 -1
  355. package/dist/integrity.cjs +50 -61
  356. package/dist/integrity.cjs.map +1 -1
  357. package/dist/integrity.d.cts +16 -10
  358. package/dist/integrity.d.mts +16 -10
  359. package/dist/integrity.mjs +50 -59
  360. package/dist/integrity.mjs.map +1 -1
  361. package/dist/internal/manifest-normalizer.cjs +8 -2
  362. package/dist/internal/manifest-normalizer.cjs.map +1 -1
  363. package/dist/internal/manifest-normalizer.mjs +9 -3
  364. package/dist/internal/manifest-normalizer.mjs.map +1 -1
  365. package/dist/merge.cjs +19 -1
  366. package/dist/merge.cjs.map +1 -1
  367. package/dist/merge.d.cts +1 -1
  368. package/dist/merge.d.mts +1 -1
  369. package/dist/merge.mjs +19 -2
  370. package/dist/merge.mjs.map +1 -1
  371. package/dist/mf.cjs +190 -3
  372. package/dist/mf.cjs.map +1 -1
  373. package/dist/mf.d.cts +24 -2
  374. package/dist/mf.d.mts +24 -2
  375. package/dist/mf.mjs +189 -4
  376. package/dist/mf.mjs.map +1 -1
  377. package/dist/near-cli.cjs +59 -91
  378. package/dist/near-cli.cjs.map +1 -1
  379. package/dist/near-cli.mjs +58 -90
  380. package/dist/near-cli.mjs.map +1 -1
  381. package/dist/near-signer.cjs +73 -0
  382. package/dist/near-signer.cjs.map +1 -0
  383. package/dist/near-signer.mjs +70 -0
  384. package/dist/near-signer.mjs.map +1 -0
  385. package/dist/network.cjs +0 -1
  386. package/dist/network.cjs.map +1 -1
  387. package/dist/orchestrator.cjs +256 -87
  388. package/dist/orchestrator.cjs.map +1 -1
  389. package/dist/orchestrator.mjs +248 -87
  390. package/dist/orchestrator.mjs.map +1 -1
  391. package/dist/platform-deploy.cjs +81 -0
  392. package/dist/platform-deploy.cjs.map +1 -0
  393. package/dist/platform-deploy.mjs +80 -0
  394. package/dist/platform-deploy.mjs.map +1 -0
  395. package/dist/plugin.cjs +54 -1389
  396. package/dist/plugin.cjs.map +1 -1
  397. package/dist/plugin.d.cts +6 -896
  398. package/dist/plugin.d.mts +6 -896
  399. package/dist/plugin.mjs +53 -1387
  400. package/dist/plugin.mjs.map +1 -1
  401. package/dist/process-kill.cjs +44 -0
  402. package/dist/process-kill.cjs.map +1 -0
  403. package/dist/process-kill.mjs +43 -0
  404. package/dist/process-kill.mjs.map +1 -0
  405. package/dist/process-registry.cjs +64 -9
  406. package/dist/process-registry.cjs.map +1 -1
  407. package/dist/process-registry.mjs +61 -10
  408. package/dist/process-registry.mjs.map +1 -1
  409. package/dist/progress.cjs +45 -0
  410. package/dist/progress.cjs.map +1 -0
  411. package/dist/progress.d.cts +13 -0
  412. package/dist/progress.d.mts +13 -0
  413. package/dist/progress.mjs +42 -0
  414. package/dist/progress.mjs.map +1 -0
  415. package/dist/publish.cjs +513 -79
  416. package/dist/publish.cjs.map +1 -1
  417. package/dist/publish.mjs +515 -82
  418. package/dist/publish.mjs.map +1 -1
  419. package/dist/registry-use.cjs +41 -0
  420. package/dist/registry-use.cjs.map +1 -0
  421. package/dist/registry-use.mjs +40 -0
  422. package/dist/registry-use.mjs.map +1 -0
  423. package/dist/resolution/session.cjs +335 -0
  424. package/dist/resolution/session.cjs.map +1 -0
  425. package/dist/resolution/session.d.cts +105 -0
  426. package/dist/resolution/session.d.mts +105 -0
  427. package/dist/resolution/session.mjs +331 -0
  428. package/dist/resolution/session.mjs.map +1 -0
  429. package/dist/rollback.cjs +131 -0
  430. package/dist/rollback.cjs.map +1 -0
  431. package/dist/rollback.mjs +128 -0
  432. package/dist/rollback.mjs.map +1 -0
  433. package/dist/sdk.cjs +7 -7
  434. package/dist/sdk.d.cts +3 -3
  435. package/dist/sdk.d.mts +3 -3
  436. package/dist/sdk.mjs +2 -2
  437. package/dist/service-descriptor.cjs +67 -34
  438. package/dist/service-descriptor.cjs.map +1 -1
  439. package/dist/service-descriptor.d.cts +4 -1
  440. package/dist/service-descriptor.d.mts +3 -0
  441. package/dist/service-descriptor.mjs +63 -35
  442. package/dist/service-descriptor.mjs.map +1 -1
  443. package/dist/shared-deps.cjs +17 -16
  444. package/dist/shared-deps.cjs.map +1 -1
  445. package/dist/shared-deps.mjs +17 -16
  446. package/dist/shared-deps.mjs.map +1 -1
  447. package/dist/start-config-source.cjs +24 -0
  448. package/dist/start-config-source.cjs.map +1 -0
  449. package/dist/start-config-source.d.cts +29 -0
  450. package/dist/start-config-source.d.mts +29 -0
  451. package/dist/start-config-source.mjs +22 -0
  452. package/dist/start-config-source.mjs.map +1 -0
  453. package/dist/storage-upload.cjs +145 -0
  454. package/dist/storage-upload.cjs.map +1 -0
  455. package/dist/storage-upload.mjs +143 -0
  456. package/dist/storage-upload.mjs.map +1 -0
  457. package/dist/types.cjs +126 -20
  458. package/dist/types.cjs.map +1 -1
  459. package/dist/types.d.cts +867 -585
  460. package/dist/types.d.mts +867 -585
  461. package/dist/types.mjs +307 -208
  462. package/dist/types.mjs.map +1 -1
  463. package/dist/ui/api.cjs +105 -0
  464. package/dist/ui/api.cjs.map +1 -0
  465. package/dist/ui/api.d.cts +31 -0
  466. package/dist/ui/api.d.mts +31 -0
  467. package/dist/ui/api.mjs +99 -0
  468. package/dist/ui/api.mjs.map +1 -0
  469. package/dist/ui/auth-guards.cjs +50 -0
  470. package/dist/ui/auth-guards.cjs.map +1 -0
  471. package/dist/ui/auth-guards.d.cts +39 -0
  472. package/dist/ui/auth-guards.d.mts +39 -0
  473. package/dist/ui/auth-guards.mjs +48 -0
  474. package/dist/ui/auth-guards.mjs.map +1 -0
  475. package/dist/ui/auth.cjs +197 -0
  476. package/dist/ui/auth.cjs.map +1 -0
  477. package/dist/ui/auth.d.cts +56 -0
  478. package/dist/ui/auth.d.mts +56 -0
  479. package/dist/ui/auth.mjs +179 -0
  480. package/dist/ui/auth.mjs.map +1 -0
  481. package/dist/ui/entry.cjs +16 -0
  482. package/dist/ui/entry.cjs.map +1 -0
  483. package/dist/ui/entry.d.cts +12 -0
  484. package/dist/ui/entry.d.mts +12 -0
  485. package/dist/ui/entry.mjs +15 -0
  486. package/dist/ui/entry.mjs.map +1 -0
  487. package/dist/ui/head.cjs +14 -7
  488. package/dist/ui/head.cjs.map +1 -1
  489. package/dist/ui/head.mjs +15 -7
  490. package/dist/ui/head.mjs.map +1 -1
  491. package/dist/ui/hydrate.cjs +265 -0
  492. package/dist/ui/hydrate.cjs.map +1 -0
  493. package/dist/ui/hydrate.d.cts +48 -0
  494. package/dist/ui/hydrate.d.mts +48 -0
  495. package/dist/ui/hydrate.mjs +263 -0
  496. package/dist/ui/hydrate.mjs.map +1 -0
  497. package/dist/ui/i18n.cjs +153 -0
  498. package/dist/ui/i18n.cjs.map +1 -0
  499. package/dist/ui/i18n.d.cts +29 -0
  500. package/dist/ui/i18n.d.mts +29 -0
  501. package/dist/ui/i18n.mjs +140 -0
  502. package/dist/ui/i18n.mjs.map +1 -0
  503. package/dist/ui/index.cjs +19 -1
  504. package/dist/ui/index.d.cts +6 -3
  505. package/dist/ui/index.d.mts +6 -3
  506. package/dist/ui/index.mjs +5 -2
  507. package/dist/ui/locale.cjs +53 -0
  508. package/dist/ui/locale.cjs.map +1 -0
  509. package/dist/ui/locale.d.cts +18 -0
  510. package/dist/ui/locale.d.mts +18 -0
  511. package/dist/ui/locale.mjs +49 -0
  512. package/dist/ui/locale.mjs.map +1 -0
  513. package/dist/ui/manifest/index.cjs +25 -0
  514. package/dist/ui/manifest/index.cjs.map +1 -0
  515. package/dist/ui/manifest/index.d.cts +9 -0
  516. package/dist/ui/manifest/index.d.mts +9 -0
  517. package/dist/ui/manifest/index.mjs +13 -0
  518. package/dist/ui/manifest/index.mjs.map +1 -0
  519. package/dist/ui/manifest-generator.cjs +8 -0
  520. package/dist/ui/manifest-generator.d.cts +1 -0
  521. package/dist/ui/manifest-generator.d.mts +1 -0
  522. package/dist/ui/manifest-generator.mjs +3 -0
  523. package/dist/ui/message-catalogs.cjs +8 -0
  524. package/dist/ui/message-catalogs.cjs.map +1 -0
  525. package/dist/ui/message-catalogs.d.cts +5 -0
  526. package/dist/ui/message-catalogs.d.mts +5 -0
  527. package/dist/ui/message-catalogs.mjs +8 -0
  528. package/dist/ui/message-catalogs.mjs.map +1 -0
  529. package/dist/ui/metadata.cjs +0 -1
  530. package/dist/ui/metadata.cjs.map +1 -1
  531. package/dist/ui/metadata.d.cts +1 -1
  532. package/dist/ui/metadata.d.mts +1 -1
  533. package/dist/ui/plugin-path.cjs +37 -0
  534. package/dist/ui/plugin-path.cjs.map +1 -0
  535. package/dist/ui/plugin-path.d.cts +25 -0
  536. package/dist/ui/plugin-path.d.mts +25 -0
  537. package/dist/ui/plugin-path.mjs +35 -0
  538. package/dist/ui/plugin-path.mjs.map +1 -0
  539. package/dist/ui/router-client.cjs +61 -0
  540. package/dist/ui/router-client.cjs.map +1 -0
  541. package/dist/ui/router-client.d.cts +20 -0
  542. package/dist/ui/router-client.d.mts +20 -0
  543. package/dist/ui/router-client.mjs +60 -0
  544. package/dist/ui/router-client.mjs.map +1 -0
  545. package/dist/ui/router-defaults.cjs +48 -0
  546. package/dist/ui/router-defaults.cjs.map +1 -0
  547. package/dist/ui/router-defaults.d.cts +8 -0
  548. package/dist/ui/router-defaults.d.mts +8 -0
  549. package/dist/ui/router-defaults.mjs +45 -0
  550. package/dist/ui/router-defaults.mjs.map +1 -0
  551. package/dist/ui/router-error.cjs +70 -0
  552. package/dist/ui/router-error.cjs.map +1 -0
  553. package/dist/ui/router-error.d.cts +19 -0
  554. package/dist/ui/router-error.d.mts +19 -0
  555. package/dist/ui/router-error.mjs +69 -0
  556. package/dist/ui/router-error.mjs.map +1 -0
  557. package/dist/ui/router-server.cjs +153 -0
  558. package/dist/ui/router-server.cjs.map +1 -0
  559. package/dist/ui/router-server.d.cts +38 -0
  560. package/dist/ui/router-server.d.mts +38 -0
  561. package/dist/ui/router-server.mjs +152 -0
  562. package/dist/ui/router-server.mjs.map +1 -0
  563. package/dist/ui/router.cjs +4 -16
  564. package/dist/ui/router.cjs.map +1 -1
  565. package/dist/ui/router.mjs +4 -15
  566. package/dist/ui/router.mjs.map +1 -1
  567. package/dist/ui/runtime.cjs +5 -1
  568. package/dist/ui/runtime.cjs.map +1 -1
  569. package/dist/ui/runtime.d.cts +2 -1
  570. package/dist/ui/runtime.d.mts +2 -1
  571. package/dist/ui/runtime.mjs +5 -1
  572. package/dist/ui/runtime.mjs.map +1 -1
  573. package/dist/ui/slot.cjs +34 -0
  574. package/dist/ui/slot.cjs.map +1 -0
  575. package/dist/ui/slot.d.cts +43 -0
  576. package/dist/ui/slot.d.mts +43 -0
  577. package/dist/ui/slot.mjs +34 -0
  578. package/dist/ui/slot.mjs.map +1 -0
  579. package/dist/ui/tenant.cjs +300 -0
  580. package/dist/ui/tenant.cjs.map +1 -0
  581. package/dist/ui/tenant.d.cts +122 -0
  582. package/dist/ui/tenant.d.mts +122 -0
  583. package/dist/ui/tenant.mjs +279 -0
  584. package/dist/ui/tenant.mjs.map +1 -0
  585. package/dist/ui/types.d.cts +32 -2
  586. package/dist/ui/types.d.mts +32 -2
  587. package/dist/ui/version-check.cjs +47 -0
  588. package/dist/ui/version-check.cjs.map +1 -0
  589. package/dist/ui/version-check.d.cts +23 -0
  590. package/dist/ui/version-check.d.mts +23 -0
  591. package/dist/ui/version-check.mjs +46 -0
  592. package/dist/ui/version-check.mjs.map +1 -0
  593. package/dist/utils/banner.cjs +2 -2
  594. package/dist/utils/banner.cjs.map +1 -1
  595. package/dist/utils/banner.mjs +2 -2
  596. package/dist/utils/banner.mjs.map +1 -1
  597. package/dist/utils/linkify.cjs +1 -1
  598. package/dist/utils/linkify.cjs.map +1 -1
  599. package/dist/utils/linkify.mjs +1 -0
  600. package/dist/utils/linkify.mjs.map +1 -1
  601. package/dist/utils/run.cjs +6 -6
  602. package/dist/utils/run.cjs.map +1 -1
  603. package/dist/utils/run.mjs +6 -6
  604. package/dist/utils/run.mjs.map +1 -1
  605. package/dist/utils/save-config.cjs +12 -0
  606. package/dist/utils/save-config.cjs.map +1 -1
  607. package/dist/utils/save-config.mjs +12 -0
  608. package/dist/utils/save-config.mjs.map +1 -1
  609. package/dist/utils/snapshot-hash.cjs +10 -0
  610. package/dist/utils/snapshot-hash.cjs.map +1 -0
  611. package/dist/utils/snapshot-hash.mjs +10 -0
  612. package/dist/utils/snapshot-hash.mjs.map +1 -0
  613. package/dist/utils/string.cjs +0 -1
  614. package/dist/utils/string.cjs.map +1 -1
  615. package/dist/utils/theme.cjs +30 -12
  616. package/dist/utils/theme.cjs.map +1 -1
  617. package/dist/utils/theme.mjs +30 -11
  618. package/dist/utils/theme.mjs.map +1 -1
  619. package/dist/version-manifest-deploy.cjs +50 -0
  620. package/dist/version-manifest-deploy.cjs.map +1 -0
  621. package/dist/version-manifest-deploy.mjs +49 -0
  622. package/dist/version-manifest-deploy.mjs.map +1 -0
  623. package/dist/version-manifest-resolve.cjs +42 -0
  624. package/dist/version-manifest-resolve.cjs.map +1 -0
  625. package/dist/version-manifest-resolve.d.cts +38 -0
  626. package/dist/version-manifest-resolve.d.mts +38 -0
  627. package/dist/version-manifest-resolve.mjs +40 -0
  628. package/dist/version-manifest-resolve.mjs.map +1 -0
  629. package/dist/version-status.cjs +18 -0
  630. package/dist/version-status.cjs.map +1 -0
  631. package/dist/version-status.d.cts +22 -0
  632. package/dist/version-status.d.mts +22 -0
  633. package/dist/version-status.mjs +17 -0
  634. package/dist/version-status.mjs.map +1 -0
  635. package/dist/workspace-catalog.cjs +53 -0
  636. package/dist/workspace-catalog.cjs.map +1 -0
  637. package/dist/workspace-catalog.mjs +51 -0
  638. package/dist/workspace-catalog.mjs.map +1 -0
  639. package/dist/workspace.cjs +237 -0
  640. package/dist/workspace.cjs.map +1 -0
  641. package/dist/workspace.mjs +233 -0
  642. package/dist/workspace.mjs.map +1 -0
  643. package/package.json +179 -27
  644. package/skills/add-a-route/SKILL.md +132 -0
  645. package/skills/api-and-auth/SKILL.md +87 -21
  646. package/skills/api-and-auth/references/middleware.md +1 -1
  647. package/skills/cli-reference/SKILL.md +48 -21
  648. package/skills/code-style/SKILL.md +8 -0
  649. package/skills/dev-workflow/SKILL.md +50 -26
  650. package/skills/extends-config/SKILL.md +31 -19
  651. package/skills/init-upgrade/SKILL.md +29 -16
  652. package/skills/plugin-development/SKILL.md +46 -112
  653. package/skills/plugin-development/references/database.md +82 -0
  654. package/skills/plugin-development/references/request-context.md +67 -0
  655. package/skills/publish-sync/SKILL.md +99 -22
  656. package/skills/registry/SKILL.md +82 -0
  657. package/skills/super-app/SKILL.md +25 -15
  658. package/skills/talk-to-the-app/SKILL.md +138 -0
  659. package/skills/ui-integration/SKILL.md +93 -11
  660. package/dist/cli/db-studio.cjs +0 -131
  661. package/dist/cli/db-studio.cjs.map +0 -1
  662. package/dist/cli/db-studio.mjs +0 -127
  663. package/dist/cli/db-studio.mjs.map +0 -1
  664. package/dist/cli/init.d.cts.map +0 -1
  665. package/dist/cli/init.d.mts.map +0 -1
  666. package/dist/components/dev-view.cjs +0 -309
  667. package/dist/components/dev-view.cjs.map +0 -1
  668. package/dist/components/dev-view.mjs +0 -309
  669. package/dist/components/dev-view.mjs.map +0 -1
  670. package/dist/components/streaming-view.cjs +0 -134
  671. package/dist/components/streaming-view.cjs.map +0 -1
  672. package/dist/components/streaming-view.mjs +0 -132
  673. package/dist/components/streaming-view.mjs.map +0 -1
  674. package/dist/config.d.cts.map +0 -1
  675. package/dist/config.d.mts.map +0 -1
  676. package/dist/contract.d.cts.map +0 -1
  677. package/dist/contract.d.mts.map +0 -1
  678. package/dist/contract.meta.d.cts.map +0 -1
  679. package/dist/contract.meta.d.mts.map +0 -1
  680. package/dist/dag.d.cts.map +0 -1
  681. package/dist/dag.d.mts.map +0 -1
  682. package/dist/db.cjs +0 -81
  683. package/dist/db.cjs.map +0 -1
  684. package/dist/db.d.cts +0 -30
  685. package/dist/db.d.cts.map +0 -1
  686. package/dist/db.d.mts +0 -30
  687. package/dist/db.d.mts.map +0 -1
  688. package/dist/db.mjs +0 -75
  689. package/dist/db.mjs.map +0 -1
  690. package/dist/fastkv.d.cts.map +0 -1
  691. package/dist/fastkv.d.mts.map +0 -1
  692. package/dist/integrity.d.cts.map +0 -1
  693. package/dist/integrity.d.mts.map +0 -1
  694. package/dist/merge.d.cts.map +0 -1
  695. package/dist/merge.d.mts.map +0 -1
  696. package/dist/mf.d.cts.map +0 -1
  697. package/dist/mf.d.mts.map +0 -1
  698. package/dist/plugin.d.cts.map +0 -1
  699. package/dist/plugin.d.mts.map +0 -1
  700. package/dist/service-descriptor.d.cts.map +0 -1
  701. package/dist/service-descriptor.d.mts.map +0 -1
  702. package/dist/types.d.cts.map +0 -1
  703. package/dist/types.d.mts.map +0 -1
  704. package/dist/ui/head.d.cts.map +0 -1
  705. package/dist/ui/head.d.mts.map +0 -1
  706. package/dist/ui/metadata.d.cts.map +0 -1
  707. package/dist/ui/metadata.d.mts.map +0 -1
  708. package/dist/ui/router.d.cts.map +0 -1
  709. package/dist/ui/router.d.mts.map +0 -1
  710. package/dist/ui/runtime.d.cts.map +0 -1
  711. package/dist/ui/runtime.d.mts.map +0 -1
  712. package/dist/ui/types.d.cts.map +0 -1
  713. package/dist/ui/types.d.mts.map +0 -1
package/dist/app.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { buildRuntimeConfig as buildRuntimeConfig$1, getProjectRoot, resolveLocalDevelopmentPath } from "./config.mjs";
1
+ import { resolveLocalDevelopmentPath } from "./config.mjs";
2
2
  import { claimedPorts } from "./process-registry.mjs";
3
3
  import { existsSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -9,38 +9,51 @@ import { createServer } from "node:net";
9
9
  const PROBE_TIMEOUT_MS = 250;
10
10
  const MAX_PORT_SCAN_STEPS = 1e3;
11
11
  const PARALLEL_PROBE_WINDOW = 8;
12
+ const BLOCK_STEP = 100;
12
13
  var PortAllocationError = class extends Data.TaggedError("PortAllocationError") {};
13
- var PortAllocator = class extends Context.Tag("PortAllocator")() {};
14
- function detectLocalPackages(bosConfig, runtimeConfig) {
14
+ var PortAllocator = class extends Context.Service()("PortAllocator") {};
15
+ function detectLocalPackages(bosConfig, runtimeConfig, root = process.cwd()) {
15
16
  const packages = [];
16
- const configDir = getProjectRoot();
17
+ const configDir = root;
17
18
  const uiLocalPath = runtimeConfig?.ui.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.ui.development, configDir);
18
- if (uiLocalPath && existsSync(join(uiLocalPath, "package.json"))) packages.push("ui");
19
+ if (uiLocalPath && existsSync(join(uiLocalPath, "package.json"))) packages.push({
20
+ name: "ui",
21
+ dir: uiLocalPath
22
+ });
19
23
  const apiLocalPath = runtimeConfig?.api.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.api.development, configDir);
20
- if (apiLocalPath && existsSync(join(apiLocalPath, "package.json"))) packages.push("api");
24
+ if (apiLocalPath && existsSync(join(apiLocalPath, "package.json"))) packages.push({
25
+ name: "api",
26
+ dir: apiLocalPath
27
+ });
21
28
  const hostLocalPath = runtimeConfig?.host?.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.host.development, configDir);
22
- if (hostLocalPath && existsSync(join(hostLocalPath, "package.json"))) packages.push("host");
23
- else if (existsSync(join(configDir, "host", "package.json"))) packages.push("host");
29
+ if (hostLocalPath && existsSync(join(hostLocalPath, "package.json"))) packages.push({
30
+ name: "host",
31
+ dir: hostLocalPath
32
+ });
33
+ else if (existsSync(join(configDir, "host", "package.json"))) packages.push({
34
+ name: "host",
35
+ dir: join(configDir, "host")
36
+ });
24
37
  for (const [pluginId, pluginConfig] of Object.entries(runtimeConfig?.plugins ?? {})) {
25
- if (pluginConfig.localPath && existsSync(join(pluginConfig.localPath, "package.json"))) packages.push(`plugin:${pluginId}`);
26
- if (pluginConfig.ui?.localPath && existsSync(join(pluginConfig.ui.localPath, "package.json"))) packages.push(`plugin-ui:${pluginId}`);
38
+ if (pluginConfig.localPath && existsSync(join(pluginConfig.localPath, "package.json"))) packages.push({
39
+ name: `plugin:${pluginId}`,
40
+ dir: pluginConfig.localPath
41
+ });
42
+ if (pluginConfig.ui?.localPath && existsSync(join(pluginConfig.ui.localPath, "package.json"))) packages.push({
43
+ name: `plugin-ui:${pluginId}`,
44
+ dir: pluginConfig.ui.localPath
45
+ });
27
46
  }
28
47
  const authLocalPath = runtimeConfig?.auth?.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.auth?.development, configDir);
29
- if (authLocalPath && existsSync(join(authLocalPath, "package.json"))) packages.push("auth");
30
- return packages;
31
- }
32
- async function buildRuntimeConfig(bosConfig, options) {
33
- return buildRuntimeConfig$1(bosConfig, getProjectRoot(), options.env ?? "development", {
34
- hostSource: options.hostSource,
35
- uiSource: options.uiSource,
36
- apiSource: options.apiSource,
37
- authSource: options.authSource,
38
- proxy: options.proxy,
39
- plugins: options.plugins
48
+ if (authLocalPath && existsSync(join(authLocalPath, "package.json"))) packages.push({
49
+ name: "auth",
50
+ dir: authLocalPath
40
51
  });
52
+ return packages;
41
53
  }
54
+ const scheduleTimeout = (ms, fn) => setTimeout(fn, ms);
42
55
  function probePortBindable(port) {
43
- return Effect.async((resume) => {
56
+ return Effect.callback((resume) => {
44
57
  const server = createServer();
45
58
  server.once("listening", () => {
46
59
  server.close(() => {
@@ -52,13 +65,13 @@ function probePortBindable(port) {
52
65
  resume(Effect.succeed(false));
53
66
  });
54
67
  server.listen(port, "127.0.0.1");
55
- const timer = setTimeout(() => {
68
+ const timer = scheduleTimeout(PROBE_TIMEOUT_MS, () => {
56
69
  server.removeAllListeners();
57
70
  try {
58
71
  server.close();
59
72
  } catch {}
60
73
  resume(Effect.succeed(false));
61
- }, PROBE_TIMEOUT_MS);
74
+ });
62
75
  server.once("listening", () => clearTimeout(timer));
63
76
  server.once("error", () => clearTimeout(timer));
64
77
  });
@@ -76,7 +89,7 @@ function pickAvailablePort(preferred, usedPorts, budget) {
76
89
  cause: budget ? `No free port in budget [${budget.min}, ${budget.max}] starting from ${preferred}` : `No free port found starting from ${preferred} within ${MAX_PORT_SCAN_STEPS} steps`
77
90
  }));
78
91
  while (true) {
79
- if (port >= ceiling || steps > MAX_PORT_SCAN_STEPS) yield* fail();
92
+ if (port >= ceiling || steps > MAX_PORT_SCAN_STEPS) return yield* fail();
80
93
  const candidates = [];
81
94
  for (let i = 0; i < PARALLEL_PROBE_WINDOW && port + i < ceiling; i++) {
82
95
  const candidate = port + i;
@@ -102,9 +115,69 @@ function pickAvailablePort(preferred, usedPorts, budget) {
102
115
  }
103
116
  const PortAllocatorLive = Layer.sync(PortAllocator, () => {
104
117
  const usedPorts = claimedPorts();
105
- return { pickAvailable: (preferred, budget) => pickAvailablePort(preferred, usedPorts, budget) };
118
+ return {
119
+ pickAvailable: (preferred, budget) => pickAvailablePort(preferred, usedPorts, budget),
120
+ acquireBlock: (request) => acquireBlockPort(request, usedPorts)
121
+ };
106
122
  });
123
+ function acquireBlockPort(request, usedPorts) {
124
+ const step = request.step ?? BLOCK_STEP;
125
+ const fail = (cause) => Effect.fail(new PortAllocationError({
126
+ preferred: request.base,
127
+ cause
128
+ }));
129
+ return Effect.gen(function* () {
130
+ let base = request.base;
131
+ let steps = 0;
132
+ let firstConflicts = [];
133
+ while (steps <= MAX_PORT_SCAN_STEPS) {
134
+ const shift = base - request.base;
135
+ const candidates = request.entries.map((entry) => ({
136
+ ...entry,
137
+ port: entry.pinned ? entry.preferred : entry.preferred + shift
138
+ }));
139
+ let pinnedHeldByListener;
140
+ for (const candidate of candidates.filter((c) => c.pinned)) {
141
+ if (!(yield* probePortBindable(candidate.port))) {
142
+ pinnedHeldByListener = candidate;
143
+ break;
144
+ }
145
+ usedPorts.delete(candidate.port);
146
+ }
147
+ if (pinnedHeldByListener) return yield* fail(`explicitly-requested port ${pinnedHeldByListener.port} (${pinnedHeldByListener.key}) is occupied by a listener`);
148
+ const unpinned = candidates.filter((c) => !c.pinned);
149
+ const claimConflicts = unpinned.filter((c) => usedPorts.has(c.port)).map((c) => ({
150
+ key: c.key,
151
+ port: c.port,
152
+ claimed: true
153
+ }));
154
+ if (shift === 0) firstConflicts = claimConflicts;
155
+ if (claimConflicts.length === 0) {
156
+ const busy = (yield* Effect.forEach(unpinned, (c) => probePortBindable(c.port).pipe(Effect.map((free) => ({
157
+ ...c,
158
+ free
159
+ }))), { concurrency: "unbounded" })).filter((r) => !r.free);
160
+ if (shift === 0) firstConflicts = busy.map((b) => ({
161
+ key: b.key,
162
+ port: b.port,
163
+ claimed: false
164
+ }));
165
+ if (busy.length === 0) {
166
+ for (const candidate of candidates) usedPorts.add(candidate.port);
167
+ return {
168
+ base,
169
+ ports: Object.fromEntries(candidates.map((c) => [c.key, c.port])),
170
+ conflicts: firstConflicts
171
+ };
172
+ }
173
+ }
174
+ base += step;
175
+ steps += 1;
176
+ }
177
+ return yield* fail(`no free port block within ${MAX_PORT_SCAN_STEPS} steps of base ${request.base} (step ${step})`);
178
+ });
179
+ }
107
180
 
108
181
  //#endregion
109
- export { PortAllocationError, PortAllocator, PortAllocatorLive, buildRuntimeConfig, detectLocalPackages };
182
+ export { PortAllocationError, PortAllocator, PortAllocatorLive, detectLocalPackages, probePortBindable };
110
183
  //# sourceMappingURL=app.mjs.map
package/dist/app.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.mjs","names":["configBuildRuntimeConfig"],"sources":["../src/app.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { createServer } from \"node:net\";\nimport { join } from \"node:path\";\nimport { Context, Data, Effect, Layer } from \"effect\";\nimport type { DevPortState } from \"./cli/infra\";\nimport {\n buildRuntimeConfig as configBuildRuntimeConfig,\n getProjectRoot,\n resolveLocalDevelopmentPath,\n} from \"./config\";\nimport { claimedPorts } from \"./process-registry\";\nimport type { AppOrchestrator } from \"./service-descriptor\";\nimport type { BosConfig, RuntimeConfig, RuntimePluginConfig } from \"./types\";\n\nexport type { AppOrchestrator };\n\nconst DEFAULT_HOST_PORT = 3000;\nconst DEFAULT_API_PORT = 3001;\nconst DEFAULT_AUTH_PORT = 3002;\nconst DEFAULT_UI_PORT = 3003;\nconst DEFAULT_PLUGIN_PORT_START = 3010;\n\nconst PROBE_TIMEOUT_MS = 250;\nconst MAX_PORT_SCAN_STEPS = 1000;\nconst PARALLEL_PROBE_WINDOW = 8;\n\nexport type PortBudget = { min: number; max: number };\n\nexport class PortAllocationError extends Data.TaggedError(\"PortAllocationError\")<{\n preferred: number;\n budget?: PortBudget;\n cause?: unknown;\n}> {}\n\nexport class PortAllocator extends Context.Tag(\"PortAllocator\")<\n PortAllocator,\n {\n pickAvailable: (\n preferred: number,\n budget?: PortBudget,\n ) => Effect.Effect<number, PortAllocationError>;\n }\n>() {}\n\nexport function detectLocalPackages(\n bosConfig?: BosConfig,\n runtimeConfig?: RuntimeConfig,\n): string[] {\n const packages: string[] = [];\n const configDir = getProjectRoot();\n\n const uiLocalPath =\n runtimeConfig?.ui.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.ui.development, configDir);\n if (uiLocalPath && existsSync(join(uiLocalPath, \"package.json\"))) {\n packages.push(\"ui\");\n }\n\n const apiLocalPath =\n runtimeConfig?.api.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.api.development, configDir);\n if (apiLocalPath && existsSync(join(apiLocalPath, \"package.json\"))) {\n packages.push(\"api\");\n }\n\n const hostLocalPath =\n runtimeConfig?.host?.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.host.development, configDir);\n if (hostLocalPath && existsSync(join(hostLocalPath, \"package.json\"))) {\n packages.push(\"host\");\n } else if (existsSync(join(configDir, \"host\", \"package.json\"))) {\n packages.push(\"host\");\n }\n\n for (const [pluginId, pluginConfig] of Object.entries(runtimeConfig?.plugins ?? {})) {\n if (pluginConfig.localPath && existsSync(join(pluginConfig.localPath, \"package.json\"))) {\n packages.push(`plugin:${pluginId}`);\n }\n if (pluginConfig.ui?.localPath && existsSync(join(pluginConfig.ui.localPath, \"package.json\"))) {\n packages.push(`plugin-ui:${pluginId}`);\n }\n }\n\n const authLocalPath =\n runtimeConfig?.auth?.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.auth?.development, configDir);\n if (authLocalPath && existsSync(join(authLocalPath, \"package.json\"))) {\n packages.push(\"auth\");\n }\n\n return packages;\n}\n\nexport async function buildRuntimeConfig(\n bosConfig: BosConfig,\n options: {\n hostSource?: \"local\" | \"remote\";\n uiSource?: \"local\" | \"remote\";\n apiSource?: \"local\" | \"remote\";\n authSource?: \"local\" | \"remote\";\n proxy?: string;\n env?: \"development\" | \"production\";\n plugins?: Record<string, RuntimePluginConfig>;\n },\n): Promise<RuntimeConfig> {\n return configBuildRuntimeConfig(bosConfig, getProjectRoot(), options.env ?? \"development\", {\n hostSource: options.hostSource,\n uiSource: options.uiSource,\n apiSource: options.apiSource,\n authSource: options.authSource,\n proxy: options.proxy,\n plugins: options.plugins,\n });\n}\n\nfunction probePortBindable(port: number): Effect.Effect<boolean> {\n return Effect.async<boolean>((resume) => {\n const server = createServer();\n\n server.once(\"listening\", () => {\n server.close(() => {\n resume(Effect.succeed(true));\n });\n });\n\n server.once(\"error\", () => {\n server.removeAllListeners();\n // EADDRINUSE, EACCES, or any other bind error → not available\n resume(Effect.succeed(false));\n });\n\n server.listen(port, \"127.0.0.1\");\n\n const timer = setTimeout(() => {\n server.removeAllListeners();\n try {\n server.close();\n } catch {\n // ignore\n }\n resume(Effect.succeed(false));\n }, PROBE_TIMEOUT_MS);\n\n server.once(\"listening\", () => clearTimeout(timer));\n server.once(\"error\", () => clearTimeout(timer));\n });\n}\n\nfunction pickAvailablePort(\n preferred: number,\n usedPorts: Set<number>,\n budget?: PortBudget,\n): Effect.Effect<number, PortAllocationError> {\n return Effect.gen(function* () {\n const within = (candidate: number): boolean =>\n !budget || (candidate >= budget.min && candidate <= budget.max);\n\n let port = preferred;\n if (!within(port)) {\n port = budget ? budget.min : port;\n }\n\n const ceiling = budget ? budget.max + 1 : Number.MAX_SAFE_INTEGER;\n let steps = 0;\n\n const fail = () =>\n Effect.fail(\n new PortAllocationError({\n preferred,\n budget,\n cause: budget\n ? `No free port in budget [${budget.min}, ${budget.max}] starting from ${preferred}`\n : `No free port found starting from ${preferred} within ${MAX_PORT_SCAN_STEPS} steps`,\n }),\n );\n\n while (true) {\n if (port >= ceiling || steps > MAX_PORT_SCAN_STEPS) {\n yield* fail();\n }\n\n const candidates: number[] = [];\n for (let i = 0; i < PARALLEL_PROBE_WINDOW && port + i < ceiling; i++) {\n const candidate = port + i;\n if (!usedPorts.has(candidate)) {\n candidates.push(candidate);\n }\n }\n\n if (candidates.length === 0) {\n port += PARALLEL_PROBE_WINDOW;\n steps += PARALLEL_PROBE_WINDOW;\n continue;\n }\n\n const results = yield* Effect.forEach(\n candidates,\n (c) => probePortBindable(c).pipe(Effect.map((free) => ({ port: c, free }))),\n { concurrency: \"unbounded\" },\n );\n\n const firstFree = results.find((r) => r.free);\n if (firstFree) {\n usedPorts.add(firstFree.port);\n return firstFree.port;\n }\n\n port += PARALLEL_PROBE_WINDOW;\n steps += PARALLEL_PROBE_WINDOW;\n }\n });\n}\n\nexport const PortAllocatorLive: Layer.Layer<PortAllocator> = Layer.sync(PortAllocator, () => {\n const usedPorts = claimedPorts();\n return {\n pickAvailable: (preferred, budget) => pickAvailablePort(preferred, usedPorts, budget),\n };\n});\n\nfunction withLocalRuntimeUrl<\n T extends { url: string; entry: string; port?: number; localPath?: string },\n>(entry: T, port: number): T {\n const url = `http://localhost:${port}`;\n return {\n ...entry,\n url,\n entry: `${url}/mf-manifest.json`,\n port,\n };\n}\n\nexport interface DevPortOptions {\n hostPort?: number;\n apiPort?: number;\n uiPort?: number;\n authPort?: number;\n pluginPortStart?: number;\n ssr?: boolean;\n portBudget?: PortBudget;\n}\n\nexport interface PreparedDevRuntime {\n runtimeConfig: RuntimeConfig;\n devPorts: DevPortState;\n}\n\nexport function prepareDevelopmentRuntimeConfig(\n runtimeConfig: RuntimeConfig,\n options?: DevPortOptions,\n): Effect.Effect<PreparedDevRuntime, PortAllocationError, PortAllocator> {\n return Effect.gen(function* () {\n const allocator = yield* PortAllocator;\n const budget = options?.portBudget;\n\n const pickedHostPort = yield* allocator.pickAvailable(\n options?.hostPort ?? DEFAULT_HOST_PORT,\n budget,\n );\n\n const hostIsLocal = runtimeConfig.host.source === \"local\";\n const next: RuntimeConfig = {\n ...runtimeConfig,\n host: hostIsLocal\n ? {\n ...runtimeConfig.host,\n url: `http://localhost:${pickedHostPort}`,\n port: pickedHostPort,\n }\n : { ...runtimeConfig.host },\n ui: { ...runtimeConfig.ui },\n api: { ...runtimeConfig.api },\n auth: runtimeConfig.auth ? { ...runtimeConfig.auth } : undefined,\n plugins: runtimeConfig.plugins ? { ...runtimeConfig.plugins } : undefined,\n };\n\n const devPorts: DevPortState = {\n host: hostIsLocal ? pickedHostPort : undefined,\n api: undefined,\n ui: undefined,\n auth: undefined,\n pluginPortStart: undefined,\n };\n\n if (next.api.source === \"local\" && next.api.localPath) {\n const apiPort = yield* allocator.pickAvailable(\n options?.apiPort ?? next.api.port ?? DEFAULT_API_PORT,\n budget,\n );\n next.api = withLocalRuntimeUrl(next.api, apiPort);\n devPorts.api = apiPort;\n }\n\n if (next.auth?.source === \"local\" && next.auth.localPath) {\n const authPort = yield* allocator.pickAvailable(\n options?.authPort ?? next.auth.port ?? DEFAULT_AUTH_PORT,\n budget,\n );\n next.auth = withLocalRuntimeUrl(next.auth, authPort);\n devPorts.auth = authPort;\n }\n\n if (next.ui.source === \"local\" && next.ui.localPath) {\n const uiPort = yield* allocator.pickAvailable(\n options?.uiPort ?? next.ui.port ?? DEFAULT_UI_PORT,\n budget,\n );\n next.ui = withLocalRuntimeUrl(next.ui, uiPort);\n devPorts.ui = uiPort;\n if (options?.ssr) {\n const ssrPort = yield* allocator.pickAvailable(uiPort + 1, budget);\n next.ui.ssrUrl = `http://localhost:${ssrPort}`;\n } else {\n next.ui.ssrUrl = undefined;\n }\n }\n\n if (next.plugins) {\n const entries = Object.entries(next.plugins).sort(([a], [b]) => a.localeCompare(b));\n let pluginBasePort = options?.pluginPortStart ?? DEFAULT_PLUGIN_PORT_START;\n let firstLocalPluginPort: number | undefined;\n\n for (const [pluginId, plugin] of entries) {\n if (plugin.source === \"local\" && plugin.localPath) {\n const pluginPort = yield* allocator.pickAvailable(plugin.port ?? pluginBasePort, budget);\n next.plugins[pluginId] = withLocalRuntimeUrl(plugin, pluginPort);\n if (firstLocalPluginPort === undefined) firstLocalPluginPort = pluginPort;\n pluginBasePort = pluginPort + 1;\n }\n\n if (plugin.ui?.source === \"local\" && plugin.ui.localPath) {\n const pluginUiPort = yield* allocator.pickAvailable(\n plugin.ui.port ?? pluginBasePort,\n budget,\n );\n next.plugins[pluginId] = {\n ...next.plugins[pluginId]!,\n ui: withLocalRuntimeUrl(plugin.ui, pluginUiPort),\n };\n if (firstLocalPluginPort === undefined) firstLocalPluginPort = pluginUiPort;\n pluginBasePort = pluginUiPort + 1;\n }\n }\n\n devPorts.pluginPortStart = firstLocalPluginPort;\n }\n\n return { runtimeConfig: next, devPorts };\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app.mjs","names":[],"sources":["../src/app.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { createServer } from \"node:net\";\nimport { join } from \"node:path\";\nimport { Context, Data, Effect, Layer } from \"effect\";\nimport { resolveLocalDevelopmentPath } from \"./config\";\nimport { claimedPorts } from \"./process-registry\";\nimport type { AppOrchestrator } from \"./service-descriptor\";\nimport type { BosConfig, RuntimeConfig } from \"./types\";\n\nexport type { AppOrchestrator };\n\nconst PROBE_TIMEOUT_MS = 250;\nconst MAX_PORT_SCAN_STEPS = 1000;\nconst PARALLEL_PROBE_WINDOW = 8;\nconst BLOCK_STEP = 100;\n\nexport type PortBudget = { min: number; max: number };\n\nexport class PortAllocationError extends Data.TaggedError(\"PortAllocationError\")<{\n preferred: number;\n budget?: PortBudget;\n cause?: unknown;\n}> {}\n\nexport interface PortBlockEntry {\n key: string;\n preferred: number;\n pinned: boolean;\n}\n\nexport interface PortBlockRequest {\n base: number;\n step?: number;\n entries: PortBlockEntry[];\n}\n\nexport interface PortBlockAllocation {\n base: number;\n ports: Record<string, number>;\n conflicts: Array<{ key: string; port: number; claimed: boolean }>;\n}\n\nexport class PortAllocator extends Context.Service<\n PortAllocator,\n {\n pickAvailable: (\n preferred: number,\n budget?: PortBudget,\n ) => Effect.Effect<number, PortAllocationError>;\n acquireBlock: (\n request: PortBlockRequest,\n ) => Effect.Effect<PortBlockAllocation, PortAllocationError>;\n }\n>()(\"PortAllocator\") {}\n\nexport interface LocalPackage {\n readonly name: string;\n readonly dir: string;\n}\n\nexport function detectLocalPackages(\n bosConfig?: BosConfig,\n runtimeConfig?: RuntimeConfig,\n root: string = process.cwd(),\n): LocalPackage[] {\n const packages: LocalPackage[] = [];\n const configDir = root;\n\n const uiLocalPath =\n runtimeConfig?.ui.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.ui.development, configDir);\n if (uiLocalPath && existsSync(join(uiLocalPath, \"package.json\"))) {\n packages.push({ name: \"ui\", dir: uiLocalPath });\n }\n\n const apiLocalPath =\n runtimeConfig?.api.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.api.development, configDir);\n if (apiLocalPath && existsSync(join(apiLocalPath, \"package.json\"))) {\n packages.push({ name: \"api\", dir: apiLocalPath });\n }\n\n const hostLocalPath =\n runtimeConfig?.host?.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.host.development, configDir);\n if (hostLocalPath && existsSync(join(hostLocalPath, \"package.json\"))) {\n packages.push({ name: \"host\", dir: hostLocalPath });\n } else if (existsSync(join(configDir, \"host\", \"package.json\"))) {\n packages.push({ name: \"host\", dir: join(configDir, \"host\") });\n }\n\n for (const [pluginId, pluginConfig] of Object.entries(runtimeConfig?.plugins ?? {})) {\n if (pluginConfig.localPath && existsSync(join(pluginConfig.localPath, \"package.json\"))) {\n packages.push({ name: `plugin:${pluginId}`, dir: pluginConfig.localPath });\n }\n if (pluginConfig.ui?.localPath && existsSync(join(pluginConfig.ui.localPath, \"package.json\"))) {\n packages.push({ name: `plugin-ui:${pluginId}`, dir: pluginConfig.ui.localPath });\n }\n }\n\n const authLocalPath =\n runtimeConfig?.auth?.localPath ??\n resolveLocalDevelopmentPath(bosConfig?.app.auth?.development, configDir);\n if (authLocalPath && existsSync(join(authLocalPath, \"package.json\"))) {\n packages.push({ name: \"auth\", dir: authLocalPath });\n }\n\n return packages;\n}\n\nconst scheduleTimeout = (ms: number, fn: () => void): ReturnType<typeof setTimeout> =>\n setTimeout(fn, ms);\n\nexport function probePortBindable(port: number): Effect.Effect<boolean> {\n return Effect.callback<boolean>((resume) => {\n const server = createServer();\n\n server.once(\"listening\", () => {\n server.close(() => {\n resume(Effect.succeed(true));\n });\n });\n\n server.once(\"error\", () => {\n server.removeAllListeners();\n // EADDRINUSE, EACCES, or any other bind error → not available\n resume(Effect.succeed(false));\n });\n\n server.listen(port, \"127.0.0.1\");\n\n const timer = scheduleTimeout(PROBE_TIMEOUT_MS, () => {\n server.removeAllListeners();\n try {\n server.close();\n } catch {\n // ignore\n }\n resume(Effect.succeed(false));\n });\n\n server.once(\"listening\", () => clearTimeout(timer));\n server.once(\"error\", () => clearTimeout(timer));\n });\n}\n\nfunction pickAvailablePort(\n preferred: number,\n usedPorts: Set<number>,\n budget?: PortBudget,\n): Effect.Effect<number, PortAllocationError> {\n return Effect.gen(function* () {\n const within = (candidate: number): boolean =>\n !budget || (candidate >= budget.min && candidate <= budget.max);\n\n let port = preferred;\n if (!within(port)) {\n port = budget ? budget.min : port;\n }\n\n const ceiling = budget ? budget.max + 1 : Number.MAX_SAFE_INTEGER;\n let steps = 0;\n\n const fail = () =>\n Effect.fail(\n new PortAllocationError({\n preferred,\n budget,\n cause: budget\n ? `No free port in budget [${budget.min}, ${budget.max}] starting from ${preferred}`\n : `No free port found starting from ${preferred} within ${MAX_PORT_SCAN_STEPS} steps`,\n }),\n );\n\n while (true) {\n if (port >= ceiling || steps > MAX_PORT_SCAN_STEPS) {\n return yield* fail();\n }\n\n const candidates: number[] = [];\n for (let i = 0; i < PARALLEL_PROBE_WINDOW && port + i < ceiling; i++) {\n const candidate = port + i;\n if (!usedPorts.has(candidate)) {\n candidates.push(candidate);\n }\n }\n\n if (candidates.length === 0) {\n port += PARALLEL_PROBE_WINDOW;\n steps += PARALLEL_PROBE_WINDOW;\n continue;\n }\n\n const results = yield* Effect.forEach(\n candidates,\n (c) => probePortBindable(c).pipe(Effect.map((free) => ({ port: c, free }))),\n { concurrency: \"unbounded\" },\n );\n\n const firstFree = results.find((r) => r.free);\n if (firstFree) {\n usedPorts.add(firstFree.port);\n return firstFree.port;\n }\n\n port += PARALLEL_PROBE_WINDOW;\n steps += PARALLEL_PROBE_WINDOW;\n }\n });\n}\n\nexport const PortAllocatorLive: Layer.Layer<PortAllocator> = Layer.sync(PortAllocator, () => {\n const usedPorts = claimedPorts();\n return {\n pickAvailable: (preferred, budget) => pickAvailablePort(preferred, usedPorts, budget),\n acquireBlock: (request) => acquireBlockPort(request, usedPorts),\n };\n});\n\n// acquireBlockPort validates and acquires a whole port block atomically: every\n// entry is probed before any is yielded; on any conflict the entire block\n// steps (base += step) and retries. Pinned entries (explicit CLI flags) are\n// absolute — an actual listener on one fails loudly instead of silently\n// drifting a port the user named; a registry claim without a listener is\n// stale (PID reuse across container restarts) and never wedges allocation.\nfunction acquireBlockPort(\n request: PortBlockRequest,\n usedPorts: Set<number>,\n): Effect.Effect<PortBlockAllocation, PortAllocationError> {\n const step = request.step ?? BLOCK_STEP;\n const fail = (cause: string) =>\n Effect.fail(\n new PortAllocationError({\n preferred: request.base,\n cause,\n }),\n );\n\n return Effect.gen(function* () {\n let base = request.base;\n let steps = 0;\n let firstConflicts: PortBlockAllocation[\"conflicts\"] = [];\n while (steps <= MAX_PORT_SCAN_STEPS) {\n const shift = base - request.base;\n const candidates = request.entries.map((entry) => ({\n ...entry,\n port: entry.pinned ? entry.preferred : entry.preferred + shift,\n }));\n\n // Pinned entries are absolute (ADR 0012): an actual listener on one\n // fails loudly instead of drifting a port the user named. Verified\n // against the real socket every iteration — a registry claim without\n // a listener is stale (PID reuse across container restarts) and must\n // never wedge a pinned allocation.\n let pinnedHeldByListener: (typeof candidates)[number] | undefined;\n for (const candidate of candidates.filter((c) => c.pinned)) {\n const bindable = yield* probePortBindable(candidate.port);\n if (!bindable) {\n pinnedHeldByListener = candidate;\n break;\n }\n usedPorts.delete(candidate.port);\n }\n if (pinnedHeldByListener) {\n return yield* fail(\n `explicitly-requested port ${pinnedHeldByListener.port} (${pinnedHeldByListener.key}) is occupied by a listener`,\n );\n }\n\n const unpinned = candidates.filter((c) => !c.pinned);\n const claimConflicts = unpinned\n .filter((c) => usedPorts.has(c.port))\n .map((c) => ({ key: c.key, port: c.port, claimed: true }));\n if (shift === 0) {\n firstConflicts = claimConflicts;\n }\n\n if (claimConflicts.length === 0) {\n const results = yield* Effect.forEach(\n unpinned,\n (c) => probePortBindable(c.port).pipe(Effect.map((free) => ({ ...c, free }))),\n { concurrency: \"unbounded\" },\n );\n const busy = results.filter((r) => !r.free);\n if (shift === 0) {\n firstConflicts = busy.map((b) => ({ key: b.key, port: b.port, claimed: false }));\n }\n if (busy.length === 0) {\n for (const candidate of candidates) usedPorts.add(candidate.port);\n return {\n base,\n ports: Object.fromEntries(candidates.map((c) => [c.key, c.port])),\n conflicts: firstConflicts,\n };\n }\n }\n\n base += step;\n steps += 1;\n }\n return yield* fail(\n `no free port block within ${MAX_PORT_SCAN_STEPS} steps of base ${request.base} (step ${step})`,\n );\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,163 @@
1
+ let node_os = require("node:os");
2
+ let execa = require("execa");
3
+
4
+ //#region src/auth-login.ts
5
+ const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
6
+ const DEFAULT_CLIENT_ID = "bos-cli";
7
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
8
+ const SLOW_DOWN_PENALTY_SECONDS = 5;
9
+ async function authFetch(siteUrl, path, body, cookiePair) {
10
+ const headers = {
11
+ "content-type": "application/json",
12
+ origin: new URL(siteUrl).origin
13
+ };
14
+ if (cookiePair) headers.cookie = cookiePair;
15
+ const response = await fetch(`${siteUrl.replace(/\/$/, "")}/api/auth${path}`, {
16
+ method: "POST",
17
+ headers,
18
+ body: JSON.stringify(body)
19
+ });
20
+ return {
21
+ status: response.status,
22
+ json: await response.json().catch(() => ({})),
23
+ setCookie: response.headers.get("set-cookie")
24
+ };
25
+ }
26
+ /**
27
+ * CLI login via the OAuth 2.0 Device Flow (RFC 8628) — the same flow the
28
+ * site's QR pairing uses. The CLI requests a device code, the user approves
29
+ * at `/login/device` in any browser (same machine or not), and the CLI polls
30
+ * the token endpoint until the grant completes.
31
+ */
32
+ async function startDeviceLogin(opts) {
33
+ const siteUrl = opts.siteUrl.replace(/\/$/, "");
34
+ const clientId = DEFAULT_CLIENT_ID;
35
+ const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {
36
+ method: "POST",
37
+ headers: { "content-type": "application/json" },
38
+ body: JSON.stringify({ client_id: clientId })
39
+ });
40
+ if (!codeResponse.ok) throw new Error(`Device login could not start (${codeResponse.status}) — is ${siteUrl} running the platform auth plugin?`);
41
+ const code = await codeResponse.json();
42
+ const verificationUrl = buildVerificationUrl(siteUrl, code, opts);
43
+ let abortReason = null;
44
+ let rejectPending;
45
+ const pendingReject = new Promise((_, reject) => {
46
+ rejectPending = reject;
47
+ });
48
+ const waitForApproval = (timeoutMs = 6e5) => {
49
+ const deadline = Date.now() + timeoutMs;
50
+ let intervalSeconds = code.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;
51
+ const poll = async () => {
52
+ if (abortReason) throw new Error(abortReason);
53
+ if (Date.now() > deadline) throw new Error("Login timed out — the device code expired before approval");
54
+ const { status, json } = await authFetch(siteUrl, "/device/token", {
55
+ grant_type: DEVICE_CODE_GRANT_TYPE,
56
+ device_code: code.device_code,
57
+ client_id: clientId
58
+ });
59
+ if (status === 200) {
60
+ const { access_token: sessionToken } = json;
61
+ return await completeLogin(siteUrl, sessionToken, opts);
62
+ }
63
+ const error = json.error;
64
+ if (error === "authorization_pending") {} else if (error === "slow_down") intervalSeconds += SLOW_DOWN_PENALTY_SECONDS;
65
+ else if (error === "expired_token") throw new Error("Login timed out — the device code expired before approval");
66
+ else if (error === "access_denied") throw new Error("Login request was denied on the approval page");
67
+ else throw new Error(json.error_description ?? `Login failed (${error ?? status})`);
68
+ await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1e3));
69
+ return poll();
70
+ };
71
+ return Promise.race([pendingReject, poll()]);
72
+ };
73
+ return {
74
+ userCode: code.user_code,
75
+ verificationUrl,
76
+ waitForApproval,
77
+ abort: (reason) => {
78
+ abortReason = reason ?? "Login aborted";
79
+ rejectPending(new Error(abortReason));
80
+ }
81
+ };
82
+ }
83
+ function buildVerificationUrl(siteUrl, code, opts) {
84
+ const base = code.verification_uri_complete ?? code.verification_uri ?? "/login/device";
85
+ const url = new URL(base, siteUrl);
86
+ const params = url.searchParams;
87
+ params.set("user_code", code.user_code);
88
+ if (opts.account) params.set("account", opts.account);
89
+ if (opts.device) params.set("device", opts.device);
90
+ if (opts.delegate) {
91
+ params.set("pubKey", opts.delegate.pubKey);
92
+ params.set("contract", opts.delegate.contract);
93
+ params.set("network", opts.delegate.network);
94
+ }
95
+ return url.toString();
96
+ }
97
+ async function completeLogin(siteUrl, sessionToken, opts) {
98
+ const claim = await authFetch(siteUrl, "/device-link/claim", {
99
+ token: sessionToken,
100
+ client_id: DEFAULT_CLIENT_ID
101
+ });
102
+ if (claim.status !== 200) {
103
+ const detail = claim.json.message;
104
+ throw new Error(`Approved session could not be claimed${detail ? `: ${detail}` : ` (${claim.status})`}`);
105
+ }
106
+ const cookiePair = claim.setCookie?.split(";")[0];
107
+ if (!cookiePair?.includes("=")) throw new Error("Session claim returned no session cookie");
108
+ const apiKey = await mintApiKey(siteUrl, cookiePair, opts);
109
+ return {
110
+ sessionToken,
111
+ accountId: await resolveNearAccount(siteUrl, cookiePair),
112
+ apiKey
113
+ };
114
+ }
115
+ async function mintApiKey(siteUrl, cookiePair, opts) {
116
+ const { status, json } = await authFetch(siteUrl, "/api-key/create", {
117
+ configId: "user-keys",
118
+ name: `bos login — ${opts.device ?? "cli"} — ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ")}`,
119
+ ...opts.expiresIn ? { expiresIn: opts.expiresIn } : {}
120
+ }, cookiePair);
121
+ if (status !== 200) {
122
+ const detail = json.message;
123
+ throw new Error(`CLI credential could not be created${detail ? `: ${detail}` : ` (${status})`}`);
124
+ }
125
+ const created = json;
126
+ if (!created.key) throw new Error("API key creation returned no key");
127
+ return {
128
+ key: created.key,
129
+ id: created.id
130
+ };
131
+ }
132
+ async function resolveNearAccount(siteUrl, cookiePair) {
133
+ try {
134
+ const response = await fetch(`${siteUrl.replace(/\/$/, "")}/api/auth/near/list-accounts`, { headers: { cookie: cookiePair } });
135
+ if (!response.ok) return null;
136
+ return (await response.json()).activeAccount?.accountId ?? null;
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+ async function openInBrowser(url) {
142
+ const os = (0, node_os.platform)();
143
+ try {
144
+ if (os === "darwin") await (0, execa.execa)("open", [url], { stdio: "ignore" });
145
+ else if (os === "win32") await (0, execa.execa)("cmd", [
146
+ "/c",
147
+ "start",
148
+ "",
149
+ url
150
+ ], {
151
+ stdio: "ignore",
152
+ windowsVerbatimArguments: true
153
+ });
154
+ else await (0, execa.execa)("xdg-open", [url], { stdio: "ignore" });
155
+ } catch {
156
+ throw new Error(`Failed to open browser. Open this URL manually:\n ${url}`);
157
+ }
158
+ }
159
+
160
+ //#endregion
161
+ exports.openInBrowser = openInBrowser;
162
+ exports.startDeviceLogin = startDeviceLogin;
163
+ //# sourceMappingURL=auth-login.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-login.cjs","names":["platform"],"sources":["../src/auth-login.ts"],"sourcesContent":["import { platform } from \"node:os\";\nimport { execa } from \"execa\";\n\nconst DEVICE_CODE_GRANT_TYPE = \"urn:ietf:params:oauth:grant-type:device_code\";\nconst DEFAULT_CLIENT_ID = \"bos-cli\";\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\nconst SLOW_DOWN_PENALTY_SECONDS = 5;\n\nexport interface DeviceLoginOptions {\n siteUrl: string;\n /** label for the approving device, shown on the approval page */\n device?: string;\n /** the NEAR account the CLI expects to authenticate as (approval-page copy only) */\n account?: string;\n /** delegate mode — the approval page adds this scoped function-call key to the user's account */\n delegate?: { pubKey: string; contract: string; network: string };\n /** seconds the minted API key stays valid (login mode) */\n expiresIn?: number;\n}\n\nexport interface DeviceApproval {\n sessionToken: string;\n /** the NEAR account signed in on the approving browser (login mode) */\n accountId: string | null;\n /** the minted CLI credential (login mode) */\n apiKey: { key: string; id: string } | null;\n}\n\nexport interface DeviceLoginHandle {\n userCode: string;\n verificationUrl: string;\n waitForApproval: (timeoutMs?: number) => Promise<DeviceApproval>;\n abort: (reason?: string) => void;\n}\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri?: string;\n verification_uri_complete?: string;\n expires_in?: number;\n interval?: number;\n}\n\ninterface DeviceTokenSuccess {\n access_token: string;\n token_type: \"Bearer\";\n}\n\ninterface DeviceTokenError {\n error:\n | \"authorization_pending\"\n | \"slow_down\"\n | \"expired_token\"\n | \"access_denied\"\n | \"invalid_grant\"\n | \"invalid_request\"\n | \"invalid_client\";\n error_description?: string;\n}\n\ninterface ApiKeyCreated {\n id: string;\n key: string;\n}\n\ninterface NearAccountsResponse {\n accounts: { accountId: string }[];\n activeAccount: { accountId: string } | null;\n}\n\nasync function authFetch(\n siteUrl: string,\n path: string,\n body: unknown,\n cookiePair?: string,\n): Promise<{ status: number; json: unknown; setCookie: string | null }> {\n // Better Auth's origin check demands an Origin on cookie-bearing\n // requests — present the site's own origin, exactly as a browser would.\n const origin = new URL(siteUrl).origin;\n const headers: Record<string, string> = { \"content-type\": \"application/json\", origin };\n if (cookiePair) headers.cookie = cookiePair;\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n return {\n status: response.status,\n json: await response.json().catch(() => ({})),\n setCookie: response.headers.get(\"set-cookie\"),\n };\n}\n\n/**\n * CLI login via the OAuth 2.0 Device Flow (RFC 8628) — the same flow the\n * site's QR pairing uses. The CLI requests a device code, the user approves\n * at `/login/device` in any browser (same machine or not), and the CLI polls\n * the token endpoint until the grant completes.\n */\nexport async function startDeviceLogin(opts: DeviceLoginOptions): Promise<DeviceLoginHandle> {\n const siteUrl = opts.siteUrl.replace(/\\/$/, \"\");\n const clientId = DEFAULT_CLIENT_ID;\n\n const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ client_id: clientId }),\n });\n if (!codeResponse.ok) {\n throw new Error(\n `Device login could not start (${codeResponse.status}) — is ${siteUrl} running the platform auth plugin?`,\n );\n }\n const code = (await codeResponse.json()) as DeviceCodeResponse;\n\n const verificationUrl = buildVerificationUrl(siteUrl, code, opts);\n\n let abortReason: string | null = null;\n let rejectPending!: (reason?: Error) => void;\n const pendingReject = new Promise<never>((_, reject) => {\n rejectPending = reject;\n });\n\n const waitForApproval = (timeoutMs = 10 * 60_000): Promise<DeviceApproval> => {\n const deadline = Date.now() + timeoutMs;\n let intervalSeconds = code.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;\n\n const poll = async (): Promise<DeviceApproval> => {\n if (abortReason) throw new Error(abortReason);\n if (Date.now() > deadline) {\n throw new Error(\"Login timed out — the device code expired before approval\");\n }\n\n const { status, json } = await authFetch(siteUrl, \"/device/token\", {\n grant_type: DEVICE_CODE_GRANT_TYPE,\n device_code: code.device_code,\n client_id: clientId,\n });\n\n if (status === 200) {\n const { access_token: sessionToken } = json as DeviceTokenSuccess;\n return await completeLogin(siteUrl, sessionToken, opts);\n }\n\n const error = (json as DeviceTokenError).error;\n if (error === \"authorization_pending\") {\n // keep polling\n } else if (error === \"slow_down\") {\n intervalSeconds += SLOW_DOWN_PENALTY_SECONDS;\n } else if (error === \"expired_token\") {\n throw new Error(\"Login timed out — the device code expired before approval\");\n } else if (error === \"access_denied\") {\n throw new Error(\"Login request was denied on the approval page\");\n } else {\n throw new Error(\n (json as DeviceTokenError).error_description ?? `Login failed (${error ?? status})`,\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1000));\n return poll();\n };\n\n return Promise.race([pendingReject, poll()]);\n };\n\n return {\n userCode: code.user_code,\n verificationUrl,\n waitForApproval,\n abort: (reason) => {\n abortReason = reason ?? \"Login aborted\";\n rejectPending(new Error(abortReason));\n },\n };\n}\n\nfunction buildVerificationUrl(\n siteUrl: string,\n code: DeviceCodeResponse,\n opts: DeviceLoginOptions,\n): string {\n const base = code.verification_uri_complete ?? code.verification_uri ?? \"/login/device\";\n const url = new URL(base, siteUrl);\n const params = url.searchParams;\n params.set(\"user_code\", code.user_code);\n if (opts.account) params.set(\"account\", opts.account);\n if (opts.device) params.set(\"device\", opts.device);\n if (opts.delegate) {\n params.set(\"pubKey\", opts.delegate.pubKey);\n params.set(\"contract\", opts.delegate.contract);\n params.set(\"network\", opts.delegate.network);\n }\n return url.toString();\n}\n\nasync function completeLogin(\n siteUrl: string,\n sessionToken: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval> {\n // The token endpoint returns the raw session token; Better Auth session\n // cookies carry an HMAC signature, so exchange the token for a signed\n // session cookie via the device-link claim endpoint (the QR pairing path).\n const claim = await authFetch(siteUrl, \"/device-link/claim\", {\n token: sessionToken,\n client_id: DEFAULT_CLIENT_ID,\n });\n if (claim.status !== 200) {\n const detail = (claim.json as { message?: string }).message;\n throw new Error(\n `Approved session could not be claimed${detail ? `: ${detail}` : ` (${claim.status})`}`,\n );\n }\n const cookiePair = claim.setCookie?.split(\";\")[0];\n if (!cookiePair?.includes(\"=\")) {\n throw new Error(\"Session claim returned no session cookie\");\n }\n\n const apiKey = await mintApiKey(siteUrl, cookiePair, opts);\n const accountId = await resolveNearAccount(siteUrl, cookiePair);\n return { sessionToken, accountId, apiKey };\n}\n\nasync function mintApiKey(\n siteUrl: string,\n cookiePair: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval[\"apiKey\"]> {\n const { status, json } = await authFetch(\n siteUrl,\n \"/api-key/create\",\n {\n configId: \"user-keys\",\n name: `bos login — ${opts.device ?? \"cli\"} — ${new Date().toISOString().slice(0, 16).replace(\"T\", \" \")}`,\n ...(opts.expiresIn ? { expiresIn: opts.expiresIn } : {}),\n },\n cookiePair,\n );\n if (status !== 200) {\n const detail = (json as { message?: string }).message;\n throw new Error(\n `CLI credential could not be created${detail ? `: ${detail}` : ` (${status})`}`,\n );\n }\n const created = json as ApiKeyCreated;\n if (!created.key) throw new Error(\"API key creation returned no key\");\n return { key: created.key, id: created.id };\n}\n\nasync function resolveNearAccount(siteUrl: string, cookiePair: string): Promise<string | null> {\n try {\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth/near/list-accounts`, {\n headers: { cookie: cookiePair },\n });\n if (!response.ok) return null;\n const data = (await response.json()) as NearAccountsResponse;\n return data.activeAccount?.accountId ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function openInBrowser(url: string): Promise<void> {\n const os = platform();\n try {\n if (os === \"darwin\") {\n await execa(\"open\", [url], { stdio: \"ignore\" });\n } else if (os === \"win32\") {\n await execa(\"cmd\", [\"/c\", \"start\", \"\", url], {\n stdio: \"ignore\",\n windowsVerbatimArguments: true,\n });\n } else {\n await execa(\"xdg-open\", [url], { stdio: \"ignore\" });\n }\n } catch {\n throw new Error(`Failed to open browser. Open this URL manually:\\n ${url}`);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ import { platform } from "node:os";
2
+ import { execa } from "execa";
3
+
4
+ //#region src/auth-login.ts
5
+ const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
6
+ const DEFAULT_CLIENT_ID = "bos-cli";
7
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
8
+ const SLOW_DOWN_PENALTY_SECONDS = 5;
9
+ async function authFetch(siteUrl, path, body, cookiePair) {
10
+ const headers = {
11
+ "content-type": "application/json",
12
+ origin: new URL(siteUrl).origin
13
+ };
14
+ if (cookiePair) headers.cookie = cookiePair;
15
+ const response = await fetch(`${siteUrl.replace(/\/$/, "")}/api/auth${path}`, {
16
+ method: "POST",
17
+ headers,
18
+ body: JSON.stringify(body)
19
+ });
20
+ return {
21
+ status: response.status,
22
+ json: await response.json().catch(() => ({})),
23
+ setCookie: response.headers.get("set-cookie")
24
+ };
25
+ }
26
+ /**
27
+ * CLI login via the OAuth 2.0 Device Flow (RFC 8628) — the same flow the
28
+ * site's QR pairing uses. The CLI requests a device code, the user approves
29
+ * at `/login/device` in any browser (same machine or not), and the CLI polls
30
+ * the token endpoint until the grant completes.
31
+ */
32
+ async function startDeviceLogin(opts) {
33
+ const siteUrl = opts.siteUrl.replace(/\/$/, "");
34
+ const clientId = DEFAULT_CLIENT_ID;
35
+ const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {
36
+ method: "POST",
37
+ headers: { "content-type": "application/json" },
38
+ body: JSON.stringify({ client_id: clientId })
39
+ });
40
+ if (!codeResponse.ok) throw new Error(`Device login could not start (${codeResponse.status}) — is ${siteUrl} running the platform auth plugin?`);
41
+ const code = await codeResponse.json();
42
+ const verificationUrl = buildVerificationUrl(siteUrl, code, opts);
43
+ let abortReason = null;
44
+ let rejectPending;
45
+ const pendingReject = new Promise((_, reject) => {
46
+ rejectPending = reject;
47
+ });
48
+ const waitForApproval = (timeoutMs = 6e5) => {
49
+ const deadline = Date.now() + timeoutMs;
50
+ let intervalSeconds = code.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;
51
+ const poll = async () => {
52
+ if (abortReason) throw new Error(abortReason);
53
+ if (Date.now() > deadline) throw new Error("Login timed out — the device code expired before approval");
54
+ const { status, json } = await authFetch(siteUrl, "/device/token", {
55
+ grant_type: DEVICE_CODE_GRANT_TYPE,
56
+ device_code: code.device_code,
57
+ client_id: clientId
58
+ });
59
+ if (status === 200) {
60
+ const { access_token: sessionToken } = json;
61
+ return await completeLogin(siteUrl, sessionToken, opts);
62
+ }
63
+ const error = json.error;
64
+ if (error === "authorization_pending") {} else if (error === "slow_down") intervalSeconds += SLOW_DOWN_PENALTY_SECONDS;
65
+ else if (error === "expired_token") throw new Error("Login timed out — the device code expired before approval");
66
+ else if (error === "access_denied") throw new Error("Login request was denied on the approval page");
67
+ else throw new Error(json.error_description ?? `Login failed (${error ?? status})`);
68
+ await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1e3));
69
+ return poll();
70
+ };
71
+ return Promise.race([pendingReject, poll()]);
72
+ };
73
+ return {
74
+ userCode: code.user_code,
75
+ verificationUrl,
76
+ waitForApproval,
77
+ abort: (reason) => {
78
+ abortReason = reason ?? "Login aborted";
79
+ rejectPending(new Error(abortReason));
80
+ }
81
+ };
82
+ }
83
+ function buildVerificationUrl(siteUrl, code, opts) {
84
+ const base = code.verification_uri_complete ?? code.verification_uri ?? "/login/device";
85
+ const url = new URL(base, siteUrl);
86
+ const params = url.searchParams;
87
+ params.set("user_code", code.user_code);
88
+ if (opts.account) params.set("account", opts.account);
89
+ if (opts.device) params.set("device", opts.device);
90
+ if (opts.delegate) {
91
+ params.set("pubKey", opts.delegate.pubKey);
92
+ params.set("contract", opts.delegate.contract);
93
+ params.set("network", opts.delegate.network);
94
+ }
95
+ return url.toString();
96
+ }
97
+ async function completeLogin(siteUrl, sessionToken, opts) {
98
+ const claim = await authFetch(siteUrl, "/device-link/claim", {
99
+ token: sessionToken,
100
+ client_id: DEFAULT_CLIENT_ID
101
+ });
102
+ if (claim.status !== 200) {
103
+ const detail = claim.json.message;
104
+ throw new Error(`Approved session could not be claimed${detail ? `: ${detail}` : ` (${claim.status})`}`);
105
+ }
106
+ const cookiePair = claim.setCookie?.split(";")[0];
107
+ if (!cookiePair?.includes("=")) throw new Error("Session claim returned no session cookie");
108
+ const apiKey = await mintApiKey(siteUrl, cookiePair, opts);
109
+ return {
110
+ sessionToken,
111
+ accountId: await resolveNearAccount(siteUrl, cookiePair),
112
+ apiKey
113
+ };
114
+ }
115
+ async function mintApiKey(siteUrl, cookiePair, opts) {
116
+ const { status, json } = await authFetch(siteUrl, "/api-key/create", {
117
+ configId: "user-keys",
118
+ name: `bos login — ${opts.device ?? "cli"} — ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ")}`,
119
+ ...opts.expiresIn ? { expiresIn: opts.expiresIn } : {}
120
+ }, cookiePair);
121
+ if (status !== 200) {
122
+ const detail = json.message;
123
+ throw new Error(`CLI credential could not be created${detail ? `: ${detail}` : ` (${status})`}`);
124
+ }
125
+ const created = json;
126
+ if (!created.key) throw new Error("API key creation returned no key");
127
+ return {
128
+ key: created.key,
129
+ id: created.id
130
+ };
131
+ }
132
+ async function resolveNearAccount(siteUrl, cookiePair) {
133
+ try {
134
+ const response = await fetch(`${siteUrl.replace(/\/$/, "")}/api/auth/near/list-accounts`, { headers: { cookie: cookiePair } });
135
+ if (!response.ok) return null;
136
+ return (await response.json()).activeAccount?.accountId ?? null;
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+ async function openInBrowser(url) {
142
+ const os = platform();
143
+ try {
144
+ if (os === "darwin") await execa("open", [url], { stdio: "ignore" });
145
+ else if (os === "win32") await execa("cmd", [
146
+ "/c",
147
+ "start",
148
+ "",
149
+ url
150
+ ], {
151
+ stdio: "ignore",
152
+ windowsVerbatimArguments: true
153
+ });
154
+ else await execa("xdg-open", [url], { stdio: "ignore" });
155
+ } catch {
156
+ throw new Error(`Failed to open browser. Open this URL manually:\n ${url}`);
157
+ }
158
+ }
159
+
160
+ //#endregion
161
+ export { openInBrowser, startDeviceLogin };
162
+ //# sourceMappingURL=auth-login.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-login.mjs","names":[],"sources":["../src/auth-login.ts"],"sourcesContent":["import { platform } from \"node:os\";\nimport { execa } from \"execa\";\n\nconst DEVICE_CODE_GRANT_TYPE = \"urn:ietf:params:oauth:grant-type:device_code\";\nconst DEFAULT_CLIENT_ID = \"bos-cli\";\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\nconst SLOW_DOWN_PENALTY_SECONDS = 5;\n\nexport interface DeviceLoginOptions {\n siteUrl: string;\n /** label for the approving device, shown on the approval page */\n device?: string;\n /** the NEAR account the CLI expects to authenticate as (approval-page copy only) */\n account?: string;\n /** delegate mode — the approval page adds this scoped function-call key to the user's account */\n delegate?: { pubKey: string; contract: string; network: string };\n /** seconds the minted API key stays valid (login mode) */\n expiresIn?: number;\n}\n\nexport interface DeviceApproval {\n sessionToken: string;\n /** the NEAR account signed in on the approving browser (login mode) */\n accountId: string | null;\n /** the minted CLI credential (login mode) */\n apiKey: { key: string; id: string } | null;\n}\n\nexport interface DeviceLoginHandle {\n userCode: string;\n verificationUrl: string;\n waitForApproval: (timeoutMs?: number) => Promise<DeviceApproval>;\n abort: (reason?: string) => void;\n}\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri?: string;\n verification_uri_complete?: string;\n expires_in?: number;\n interval?: number;\n}\n\ninterface DeviceTokenSuccess {\n access_token: string;\n token_type: \"Bearer\";\n}\n\ninterface DeviceTokenError {\n error:\n | \"authorization_pending\"\n | \"slow_down\"\n | \"expired_token\"\n | \"access_denied\"\n | \"invalid_grant\"\n | \"invalid_request\"\n | \"invalid_client\";\n error_description?: string;\n}\n\ninterface ApiKeyCreated {\n id: string;\n key: string;\n}\n\ninterface NearAccountsResponse {\n accounts: { accountId: string }[];\n activeAccount: { accountId: string } | null;\n}\n\nasync function authFetch(\n siteUrl: string,\n path: string,\n body: unknown,\n cookiePair?: string,\n): Promise<{ status: number; json: unknown; setCookie: string | null }> {\n // Better Auth's origin check demands an Origin on cookie-bearing\n // requests — present the site's own origin, exactly as a browser would.\n const origin = new URL(siteUrl).origin;\n const headers: Record<string, string> = { \"content-type\": \"application/json\", origin };\n if (cookiePair) headers.cookie = cookiePair;\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n return {\n status: response.status,\n json: await response.json().catch(() => ({})),\n setCookie: response.headers.get(\"set-cookie\"),\n };\n}\n\n/**\n * CLI login via the OAuth 2.0 Device Flow (RFC 8628) — the same flow the\n * site's QR pairing uses. The CLI requests a device code, the user approves\n * at `/login/device` in any browser (same machine or not), and the CLI polls\n * the token endpoint until the grant completes.\n */\nexport async function startDeviceLogin(opts: DeviceLoginOptions): Promise<DeviceLoginHandle> {\n const siteUrl = opts.siteUrl.replace(/\\/$/, \"\");\n const clientId = DEFAULT_CLIENT_ID;\n\n const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ client_id: clientId }),\n });\n if (!codeResponse.ok) {\n throw new Error(\n `Device login could not start (${codeResponse.status}) — is ${siteUrl} running the platform auth plugin?`,\n );\n }\n const code = (await codeResponse.json()) as DeviceCodeResponse;\n\n const verificationUrl = buildVerificationUrl(siteUrl, code, opts);\n\n let abortReason: string | null = null;\n let rejectPending!: (reason?: Error) => void;\n const pendingReject = new Promise<never>((_, reject) => {\n rejectPending = reject;\n });\n\n const waitForApproval = (timeoutMs = 10 * 60_000): Promise<DeviceApproval> => {\n const deadline = Date.now() + timeoutMs;\n let intervalSeconds = code.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;\n\n const poll = async (): Promise<DeviceApproval> => {\n if (abortReason) throw new Error(abortReason);\n if (Date.now() > deadline) {\n throw new Error(\"Login timed out — the device code expired before approval\");\n }\n\n const { status, json } = await authFetch(siteUrl, \"/device/token\", {\n grant_type: DEVICE_CODE_GRANT_TYPE,\n device_code: code.device_code,\n client_id: clientId,\n });\n\n if (status === 200) {\n const { access_token: sessionToken } = json as DeviceTokenSuccess;\n return await completeLogin(siteUrl, sessionToken, opts);\n }\n\n const error = (json as DeviceTokenError).error;\n if (error === \"authorization_pending\") {\n // keep polling\n } else if (error === \"slow_down\") {\n intervalSeconds += SLOW_DOWN_PENALTY_SECONDS;\n } else if (error === \"expired_token\") {\n throw new Error(\"Login timed out — the device code expired before approval\");\n } else if (error === \"access_denied\") {\n throw new Error(\"Login request was denied on the approval page\");\n } else {\n throw new Error(\n (json as DeviceTokenError).error_description ?? `Login failed (${error ?? status})`,\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1000));\n return poll();\n };\n\n return Promise.race([pendingReject, poll()]);\n };\n\n return {\n userCode: code.user_code,\n verificationUrl,\n waitForApproval,\n abort: (reason) => {\n abortReason = reason ?? \"Login aborted\";\n rejectPending(new Error(abortReason));\n },\n };\n}\n\nfunction buildVerificationUrl(\n siteUrl: string,\n code: DeviceCodeResponse,\n opts: DeviceLoginOptions,\n): string {\n const base = code.verification_uri_complete ?? code.verification_uri ?? \"/login/device\";\n const url = new URL(base, siteUrl);\n const params = url.searchParams;\n params.set(\"user_code\", code.user_code);\n if (opts.account) params.set(\"account\", opts.account);\n if (opts.device) params.set(\"device\", opts.device);\n if (opts.delegate) {\n params.set(\"pubKey\", opts.delegate.pubKey);\n params.set(\"contract\", opts.delegate.contract);\n params.set(\"network\", opts.delegate.network);\n }\n return url.toString();\n}\n\nasync function completeLogin(\n siteUrl: string,\n sessionToken: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval> {\n // The token endpoint returns the raw session token; Better Auth session\n // cookies carry an HMAC signature, so exchange the token for a signed\n // session cookie via the device-link claim endpoint (the QR pairing path).\n const claim = await authFetch(siteUrl, \"/device-link/claim\", {\n token: sessionToken,\n client_id: DEFAULT_CLIENT_ID,\n });\n if (claim.status !== 200) {\n const detail = (claim.json as { message?: string }).message;\n throw new Error(\n `Approved session could not be claimed${detail ? `: ${detail}` : ` (${claim.status})`}`,\n );\n }\n const cookiePair = claim.setCookie?.split(\";\")[0];\n if (!cookiePair?.includes(\"=\")) {\n throw new Error(\"Session claim returned no session cookie\");\n }\n\n const apiKey = await mintApiKey(siteUrl, cookiePair, opts);\n const accountId = await resolveNearAccount(siteUrl, cookiePair);\n return { sessionToken, accountId, apiKey };\n}\n\nasync function mintApiKey(\n siteUrl: string,\n cookiePair: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval[\"apiKey\"]> {\n const { status, json } = await authFetch(\n siteUrl,\n \"/api-key/create\",\n {\n configId: \"user-keys\",\n name: `bos login — ${opts.device ?? \"cli\"} — ${new Date().toISOString().slice(0, 16).replace(\"T\", \" \")}`,\n ...(opts.expiresIn ? { expiresIn: opts.expiresIn } : {}),\n },\n cookiePair,\n );\n if (status !== 200) {\n const detail = (json as { message?: string }).message;\n throw new Error(\n `CLI credential could not be created${detail ? `: ${detail}` : ` (${status})`}`,\n );\n }\n const created = json as ApiKeyCreated;\n if (!created.key) throw new Error(\"API key creation returned no key\");\n return { key: created.key, id: created.id };\n}\n\nasync function resolveNearAccount(siteUrl: string, cookiePair: string): Promise<string | null> {\n try {\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth/near/list-accounts`, {\n headers: { cookie: cookiePair },\n });\n if (!response.ok) return null;\n const data = (await response.json()) as NearAccountsResponse;\n return data.activeAccount?.accountId ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function openInBrowser(url: string): Promise<void> {\n const os = platform();\n try {\n if (os === \"darwin\") {\n await execa(\"open\", [url], { stdio: \"ignore\" });\n } else if (os === \"win32\") {\n await execa(\"cmd\", [\"/c\", \"start\", \"\", url], {\n stdio: \"ignore\",\n windowsVerbatimArguments: true,\n });\n } else {\n await execa(\"xdg-open\", [url], { stdio: \"ignore\" });\n }\n } catch {\n throw new Error(`Failed to open browser. Open this URL manually:\\n ${url}`);\n }\n}\n"],"mappings":"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"}